summaryrefslogtreecommitdiff
path: root/chromium/cc
diff options
context:
space:
mode:
authorZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
committerZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
commit679147eead574d186ebf3069647b4c23e8ccace6 (patch)
treefc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/cc
downloadqtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz
Initial import.
Diffstat (limited to 'chromium/cc')
-rw-r--r--chromium/cc/DEPS16
-rw-r--r--chromium/cc/OWNERS44
-rw-r--r--chromium/cc/PRESUBMIT.py193
-rw-r--r--chromium/cc/animation/animation.cc238
-rw-r--r--chromium/cc/animation/animation.h200
-rw-r--r--chromium/cc/animation/animation_curve.cc39
-rw-r--r--chromium/cc/animation/animation_curve.h68
-rw-r--r--chromium/cc/animation/animation_delegate.h21
-rw-r--r--chromium/cc/animation/animation_events.cc23
-rw-r--r--chromium/cc/animation/animation_events.h39
-rw-r--r--chromium/cc/animation/animation_id_provider.cc19
-rw-r--r--chromium/cc/animation/animation_id_provider.h25
-rw-r--r--chromium/cc/animation/animation_registrar.cc57
-rw-r--r--chromium/cc/animation/animation_registrar.h65
-rw-r--r--chromium/cc/animation/animation_unittest.cc205
-rw-r--r--chromium/cc/animation/keyframed_animation_curve.cc270
-rw-r--r--chromium/cc/animation/keyframed_animation_curve.h178
-rw-r--r--chromium/cc/animation/keyframed_animation_curve_unittest.cc336
-rw-r--r--chromium/cc/animation/layer_animation_controller.cc729
-rw-r--r--chromium/cc/animation/layer_animation_controller.h168
-rw-r--r--chromium/cc/animation/layer_animation_controller_unittest.cc1121
-rw-r--r--chromium/cc/animation/layer_animation_event_observer.h23
-rw-r--r--chromium/cc/animation/layer_animation_value_observer.h24
-rw-r--r--chromium/cc/animation/scrollbar_animation_controller.h33
-rw-r--r--chromium/cc/animation/scrollbar_animation_controller_linear_fade.cc95
-rw-r--r--chromium/cc/animation/scrollbar_animation_controller_linear_fade.h56
-rw-r--r--chromium/cc/animation/scrollbar_animation_controller_linear_fade_unittest.cc161
-rw-r--r--chromium/cc/animation/timing_function.cc118
-rw-r--r--chromium/cc/animation/timing_function.h85
-rw-r--r--chromium/cc/animation/timing_function_unittest.cc71
-rw-r--r--chromium/cc/animation/transform_operation.cc180
-rw-r--r--chromium/cc/animation/transform_operation.h63
-rw-r--r--chromium/cc/animation/transform_operations.cc193
-rw-r--r--chromium/cc/animation/transform_operations.h83
-rw-r--r--chromium/cc/animation/transform_operations_unittest.cc711
-rw-r--r--chromium/cc/base/cc_export.h29
-rw-r--r--chromium/cc/base/completion_event.h63
-rw-r--r--chromium/cc/base/float_quad_unittest.cc65
-rw-r--r--chromium/cc/base/invalidation_region.cc48
-rw-r--r--chromium/cc/base/invalidation_region.h34
-rw-r--r--chromium/cc/base/math_util.cc576
-rw-r--r--chromium/cc/base/math_util.h175
-rw-r--r--chromium/cc/base/math_util_unittest.cc122
-rw-r--r--chromium/cc/base/region.cc133
-rw-r--r--chromium/cc/base/region.h135
-rw-r--r--chromium/cc/base/region_unittest.cc454
-rw-r--r--chromium/cc/base/scoped_ptr_algorithm.h30
-rw-r--r--chromium/cc/base/scoped_ptr_deque.h137
-rw-r--r--chromium/cc/base/scoped_ptr_hash_map.h157
-rw-r--r--chromium/cc/base/scoped_ptr_vector.h180
-rw-r--r--chromium/cc/base/scoped_ptr_vector_unittest.cc72
-rw-r--r--chromium/cc/base/switches.cc150
-rw-r--r--chromium/cc/base/switches.h73
-rw-r--r--chromium/cc/base/tiling_data.cc408
-rw-r--r--chromium/cc/base/tiling_data.h148
-rw-r--r--chromium/cc/base/tiling_data_unittest.cc1171
-rw-r--r--chromium/cc/base/util.h29
-rw-r--r--chromium/cc/base/util_unittest.cc67
-rw-r--r--chromium/cc/cc.gyp393
-rw-r--r--chromium/cc/cc_perftests.isolate14
-rw-r--r--chromium/cc/cc_tests.gyp350
-rw-r--r--chromium/cc/debug/OWNERS4
-rw-r--r--chromium/cc/debug/benchmark_instrumentation.h32
-rw-r--r--chromium/cc/debug/debug_colors.cc290
-rw-r--r--chromium/cc/debug/debug_colors.h128
-rw-r--r--chromium/cc/debug/debug_rect_history.cc236
-rw-r--r--chromium/cc/debug/debug_rect_history.h114
-rw-r--r--chromium/cc/debug/devtools_instrumentation.h75
-rw-r--r--chromium/cc/debug/fake_web_graphics_context_3d.cc347
-rw-r--r--chromium/cc/debug/fake_web_graphics_context_3d.h611
-rw-r--r--chromium/cc/debug/frame_rate_counter.cc138
-rw-r--r--chromium/cc/debug/frame_rate_counter.h57
-rw-r--r--chromium/cc/debug/layer_tree_debug_state.cc113
-rw-r--r--chromium/cc/debug/layer_tree_debug_state.h54
-rw-r--r--chromium/cc/debug/overdraw_metrics.cc268
-rw-r--r--chromium/cc/debug/overdraw_metrics.h115
-rw-r--r--chromium/cc/debug/paint_time_counter.cc51
-rw-r--r--chromium/cc/debug/paint_time_counter.h47
-rw-r--r--chromium/cc/debug/rendering_stats.cc99
-rw-r--r--chromium/cc/debug/rendering_stats.h70
-rw-r--r--chromium/cc/debug/rendering_stats_instrumentation.cc183
-rw-r--r--chromium/cc/debug/rendering_stats_instrumentation.h74
-rw-r--r--chromium/cc/debug/ring_buffer.h122
-rw-r--r--chromium/cc/debug/test_context_provider.cc107
-rw-r--r--chromium/cc/debug/test_context_provider.h58
-rw-r--r--chromium/cc/debug/test_web_graphics_context_3d.cc654
-rw-r--r--chromium/cc/debug/test_web_graphics_context_3d.h293
-rw-r--r--chromium/cc/debug/traced_picture.cc35
-rw-r--r--chromium/cc/debug/traced_picture.h35
-rw-r--r--chromium/cc/debug/traced_value.cc53
-rw-r--r--chromium/cc/debug/traced_value.h46
-rw-r--r--chromium/cc/input/input_handler.h141
-rw-r--r--chromium/cc/input/layer_scroll_offset_delegate.h41
-rw-r--r--chromium/cc/input/page_scale_animation.cc232
-rw-r--r--chromium/cc/input/page_scale_animation.h110
-rw-r--r--chromium/cc/input/scrollbar.h40
-rw-r--r--chromium/cc/input/top_controls_manager.cc230
-rw-r--r--chromium/cc/input/top_controls_manager.h99
-rw-r--r--chromium/cc/input/top_controls_manager_client.h23
-rw-r--r--chromium/cc/input/top_controls_manager_unittest.cc290
-rw-r--r--chromium/cc/input/top_controls_state.h17
-rw-r--r--chromium/cc/layers/append_quads_data.h37
-rw-r--r--chromium/cc/layers/compositing_reasons.h60
-rw-r--r--chromium/cc/layers/content_layer.cc148
-rw-r--r--chromium/cc/layers/content_layer.h74
-rw-r--r--chromium/cc/layers/content_layer_client.h35
-rw-r--r--chromium/cc/layers/content_layer_unittest.cc60
-rw-r--r--chromium/cc/layers/contents_scaling_layer.cc54
-rw-r--r--chromium/cc/layers/contents_scaling_layer.h45
-rw-r--r--chromium/cc/layers/contents_scaling_layer_unittest.cc77
-rw-r--r--chromium/cc/layers/delegated_renderer_layer.cc114
-rw-r--r--chromium/cc/layers/delegated_renderer_layer.h58
-rw-r--r--chromium/cc/layers/delegated_renderer_layer_client.h23
-rw-r--r--chromium/cc/layers/delegated_renderer_layer_impl.cc489
-rw-r--r--chromium/cc/layers/delegated_renderer_layer_impl.h103
-rw-r--r--chromium/cc/layers/delegated_renderer_layer_impl_unittest.cc1254
-rw-r--r--chromium/cc/layers/draw_properties.h104
-rw-r--r--chromium/cc/layers/heads_up_display_layer.cc58
-rw-r--r--chromium/cc/layers/heads_up_display_layer.h37
-rw-r--r--chromium/cc/layers/heads_up_display_layer_impl.cc696
-rw-r--r--chromium/cc/layers/heads_up_display_layer_impl.h127
-rw-r--r--chromium/cc/layers/heads_up_display_unittest.cc100
-rw-r--r--chromium/cc/layers/image_layer.cc96
-rw-r--r--chromium/cc/layers/image_layer.h57
-rw-r--r--chromium/cc/layers/io_surface_layer.cc52
-rw-r--r--chromium/cc/layers/io_surface_layer.h39
-rw-r--r--chromium/cc/layers/io_surface_layer_impl.cc145
-rw-r--r--chromium/cc/layers/io_surface_layer_impl.h55
-rw-r--r--chromium/cc/layers/layer.cc982
-rw-r--r--chromium/cc/layers/layer.h530
-rw-r--r--chromium/cc/layers/layer_impl.cc1150
-rw-r--r--chromium/cc/layers/layer_impl.h584
-rw-r--r--chromium/cc/layers/layer_impl_unittest.cc562
-rw-r--r--chromium/cc/layers/layer_iterator.cc225
-rw-r--r--chromium/cc/layers/layer_iterator.h304
-rw-r--r--chromium/cc/layers/layer_iterator_unittest.cc271
-rw-r--r--chromium/cc/layers/layer_lists.cc60
-rw-r--r--chromium/cc/layers/layer_lists.h48
-rw-r--r--chromium/cc/layers/layer_position_constraint.cc29
-rw-r--r--chromium/cc/layers/layer_position_constraint.h38
-rw-r--r--chromium/cc/layers/layer_position_constraint_unittest.cc1098
-rw-r--r--chromium/cc/layers/layer_unittest.cc1037
-rw-r--r--chromium/cc/layers/nine_patch_layer.cc116
-rw-r--r--chromium/cc/layers/nine_patch_layer.h62
-rw-r--r--chromium/cc/layers/nine_patch_layer_impl.cc305
-rw-r--r--chromium/cc/layers/nine_patch_layer_impl.h66
-rw-r--r--chromium/cc/layers/nine_patch_layer_impl_unittest.cc155
-rw-r--r--chromium/cc/layers/nine_patch_layer_unittest.cc146
-rw-r--r--chromium/cc/layers/paint_properties.h23
-rw-r--r--chromium/cc/layers/picture_image_layer.cc58
-rw-r--r--chromium/cc/layers/picture_image_layer.h45
-rw-r--r--chromium/cc/layers/picture_image_layer_impl.cc52
-rw-r--r--chromium/cc/layers/picture_image_layer_impl.h41
-rw-r--r--chromium/cc/layers/picture_image_layer_impl_unittest.cc89
-rw-r--r--chromium/cc/layers/picture_layer.cc133
-rw-r--r--chromium/cc/layers/picture_layer.h58
-rw-r--r--chromium/cc/layers/picture_layer_impl.cc1038
-rw-r--r--chromium/cc/layers/picture_layer_impl.h134
-rw-r--r--chromium/cc/layers/picture_layer_impl_unittest.cc1011
-rw-r--r--chromium/cc/layers/quad_sink.h36
-rw-r--r--chromium/cc/layers/render_pass_sink.h24
-rw-r--r--chromium/cc/layers/render_surface.cc43
-rw-r--r--chromium/cc/layers/render_surface.h150
-rw-r--r--chromium/cc/layers/render_surface_impl.cc236
-rw-r--r--chromium/cc/layers/render_surface_impl.h186
-rw-r--r--chromium/cc/layers/render_surface_unittest.cc179
-rw-r--r--chromium/cc/layers/scrollbar_layer.cc247
-rw-r--r--chromium/cc/layers/scrollbar_layer.h86
-rw-r--r--chromium/cc/layers/scrollbar_layer_impl.cc322
-rw-r--r--chromium/cc/layers/scrollbar_layer_impl.h102
-rw-r--r--chromium/cc/layers/scrollbar_layer_unittest.cc614
-rw-r--r--chromium/cc/layers/scrollbar_theme_painter.h36
-rw-r--r--chromium/cc/layers/solid_color_layer.cc30
-rw-r--r--chromium/cc/layers/solid_color_layer.h35
-rw-r--r--chromium/cc/layers/solid_color_layer_impl.cc53
-rw-r--r--chromium/cc/layers/solid_color_layer_impl.h41
-rw-r--r--chromium/cc/layers/solid_color_layer_impl_unittest.cc169
-rw-r--r--chromium/cc/layers/texture_layer.cc322
-rw-r--r--chromium/cc/layers/texture_layer.h160
-rw-r--r--chromium/cc/layers/texture_layer_client.h36
-rw-r--r--chromium/cc/layers/texture_layer_impl.cc242
-rw-r--r--chromium/cc/layers/texture_layer_impl.h93
-rw-r--r--chromium/cc/layers/texture_layer_unittest.cc1123
-rw-r--r--chromium/cc/layers/tiled_layer.cc901
-rw-r--r--chromium/cc/layers/tiled_layer.h142
-rw-r--r--chromium/cc/layers/tiled_layer_impl.cc321
-rw-r--r--chromium/cc/layers/tiled_layer_impl.h77
-rw-r--r--chromium/cc/layers/tiled_layer_impl_unittest.cc304
-rw-r--r--chromium/cc/layers/tiled_layer_unittest.cc1942
-rw-r--r--chromium/cc/layers/video_frame_provider.h63
-rw-r--r--chromium/cc/layers/video_frame_provider_client_impl.cc90
-rw-r--r--chromium/cc/layers/video_frame_provider_client_impl.h63
-rw-r--r--chromium/cc/layers/video_layer.cc39
-rw-r--r--chromium/cc/layers/video_layer.h42
-rw-r--r--chromium/cc/layers/video_layer_impl.cc311
-rw-r--r--chromium/cc/layers/video_layer_impl.h69
-rw-r--r--chromium/cc/output/begin_frame_args.cc66
-rw-r--r--chromium/cc/output/begin_frame_args.h56
-rw-r--r--chromium/cc/output/compositor_frame.cc20
-rw-r--r--chromium/cc/output/compositor_frame.h35
-rw-r--r--chromium/cc/output/compositor_frame_ack.cc14
-rw-r--r--chromium/cc/output/compositor_frame_ack.h30
-rw-r--r--chromium/cc/output/compositor_frame_metadata.cc20
-rw-r--r--chromium/cc/output/compositor_frame_metadata.h46
-rw-r--r--chromium/cc/output/context_provider.h51
-rw-r--r--chromium/cc/output/copy_output_request.cc50
-rw-r--r--chromium/cc/output/copy_output_request.h86
-rw-r--r--chromium/cc/output/copy_output_result.cc42
-rw-r--r--chromium/cc/output/copy_output_result.h55
-rw-r--r--chromium/cc/output/delegated_frame_data.cc13
-rw-r--r--chromium/cc/output/delegated_frame_data.h30
-rw-r--r--chromium/cc/output/delegating_renderer.cc222
-rw-r--r--chromium/cc/output/delegating_renderer.h66
-rw-r--r--chromium/cc/output/delegating_renderer_unittest.cc145
-rw-r--r--chromium/cc/output/direct_renderer.cc388
-rw-r--r--chromium/cc/output/direct_renderer.h144
-rw-r--r--chromium/cc/output/filter_operation.cc257
-rw-r--r--chromium/cc/output/filter_operation.h193
-rw-r--r--chromium/cc/output/filter_operations.cc157
-rw-r--r--chromium/cc/output/filter_operations.h78
-rw-r--r--chromium/cc/output/filter_operations_unittest.cc628
-rw-r--r--chromium/cc/output/geometry_binding.cc122
-rw-r--r--chromium/cc/output/geometry_binding.h42
-rw-r--r--chromium/cc/output/gl_frame_data.cc13
-rw-r--r--chromium/cc/output/gl_frame_data.h31
-rw-r--r--chromium/cc/output/gl_renderer.cc3165
-rw-r--r--chromium/cc/output/gl_renderer.h481
-rw-r--r--chromium/cc/output/gl_renderer_draw_cache.cc14
-rw-r--r--chromium/cc/output/gl_renderer_draw_cache.h56
-rw-r--r--chromium/cc/output/gl_renderer_unittest.cc1588
-rw-r--r--chromium/cc/output/managed_memory_policy.cc80
-rw-r--r--chromium/cc/output/managed_memory_policy.h45
-rw-r--r--chromium/cc/output/output_surface.cc461
-rw-r--r--chromium/cc/output/output_surface.h203
-rw-r--r--chromium/cc/output/output_surface_client.h54
-rw-r--r--chromium/cc/output/output_surface_unittest.cc428
-rw-r--r--chromium/cc/output/program_binding.cc150
-rw-r--r--chromium/cc/output/program_binding.h98
-rw-r--r--chromium/cc/output/render_surface_filters.cc462
-rw-r--r--chromium/cc/output/render_surface_filters.h37
-rw-r--r--chromium/cc/output/render_surface_filters_unittest.cc140
-rw-r--r--chromium/cc/output/renderer.cc17
-rw-r--r--chromium/cc/output/renderer.h92
-rw-r--r--chromium/cc/output/renderer_pixeltest.cc1548
-rw-r--r--chromium/cc/output/shader.cc1606
-rw-r--r--chromium/cc/output/shader.h745
-rw-r--r--chromium/cc/output/shader_unittest.cc47
-rw-r--r--chromium/cc/output/software_frame_data.cc15
-rw-r--r--chromium/cc/output/software_frame_data.h27
-rw-r--r--chromium/cc/output/software_output_device.cc59
-rw-r--r--chromium/cc/output/software_output_device.h55
-rw-r--r--chromium/cc/output/software_renderer.cc522
-rw-r--r--chromium/cc/output/software_renderer.h113
-rw-r--r--chromium/cc/output/software_renderer_unittest.cc270
-rw-r--r--chromium/cc/quads/checkerboard_draw_quad.cc53
-rw-r--r--chromium/cc/quads/checkerboard_draw_quad.h44
-rw-r--r--chromium/cc/quads/content_draw_quad_base.cc58
-rw-r--r--chromium/cc/quads/content_draw_quad_base.h49
-rw-r--r--chromium/cc/quads/debug_border_draw_quad.cc61
-rw-r--r--chromium/cc/quads/debug_border_draw_quad.h47
-rw-r--r--chromium/cc/quads/draw_quad.cc141
-rw-r--r--chromium/cc/quads/draw_quad.h143
-rw-r--r--chromium/cc/quads/draw_quad_unittest.cc916
-rw-r--r--chromium/cc/quads/io_surface_draw_quad.cc79
-rw-r--r--chromium/cc/quads/io_surface_draw_quad.h56
-rw-r--r--chromium/cc/quads/picture_draw_quad.cc83
-rw-r--r--chromium/cc/quads/picture_draw_quad.h66
-rw-r--r--chromium/cc/quads/render_pass.cc109
-rw-r--r--chromium/cc/quads/render_pass.h147
-rw-r--r--chromium/cc/quads/render_pass_draw_quad.cc115
-rw-r--r--chromium/cc/quads/render_pass_draw_quad.h80
-rw-r--r--chromium/cc/quads/render_pass_unittest.cc86
-rw-r--r--chromium/cc/quads/shared_quad_state.cc61
-rw-r--r--chromium/cc/quads/shared_quad_state.h51
-rw-r--r--chromium/cc/quads/solid_color_draw_quad.cc59
-rw-r--r--chromium/cc/quads/solid_color_draw_quad.h47
-rw-r--r--chromium/cc/quads/stream_video_draw_quad.cc61
-rw-r--r--chromium/cc/quads/stream_video_draw_quad.h48
-rw-r--r--chromium/cc/quads/texture_draw_quad.cc174
-rw-r--r--chromium/cc/quads/texture_draw_quad.h65
-rw-r--r--chromium/cc/quads/tile_draw_quad.cc67
-rw-r--r--chromium/cc/quads/tile_draw_quad.h49
-rw-r--r--chromium/cc/quads/yuv_video_draw_quad.cc85
-rw-r--r--chromium/cc/quads/yuv_video_draw_quad.h60
-rw-r--r--chromium/cc/resources/bitmap_content_layer_updater.cc118
-rw-r--r--chromium/cc/resources/bitmap_content_layer_updater.h78
-rw-r--r--chromium/cc/resources/bitmap_skpicture_content_layer_updater.cc89
-rw-r--r--chromium/cc/resources/bitmap_skpicture_content_layer_updater.h57
-rw-r--r--chromium/cc/resources/caching_bitmap_content_layer_updater.cc61
-rw-r--r--chromium/cc/resources/caching_bitmap_content_layer_updater.h46
-rw-r--r--chromium/cc/resources/content_layer_updater.cc74
-rw-r--r--chromium/cc/resources/content_layer_updater.h51
-rw-r--r--chromium/cc/resources/image_layer_updater.cc68
-rw-r--r--chromium/cc/resources/image_layer_updater.h60
-rw-r--r--chromium/cc/resources/image_raster_worker_pool.cc226
-rw-r--r--chromium/cc/resources/image_raster_worker_pool.h55
-rw-r--r--chromium/cc/resources/layer_painter.h29
-rw-r--r--chromium/cc/resources/layer_quad.cc67
-rw-r--r--chromium/cc/resources/layer_quad.h114
-rw-r--r--chromium/cc/resources/layer_quad_unittest.cc42
-rw-r--r--chromium/cc/resources/layer_tiling_data.cc135
-rw-r--r--chromium/cc/resources/layer_tiling_data.h107
-rw-r--r--chromium/cc/resources/layer_updater.cc16
-rw-r--r--chromium/cc/resources/layer_updater.h73
-rw-r--r--chromium/cc/resources/managed_tile_state.cc131
-rw-r--r--chromium/cc/resources/managed_tile_state.h168
-rw-r--r--chromium/cc/resources/memory_history.cc39
-rw-r--r--chromium/cc/resources/memory_history.h55
-rw-r--r--chromium/cc/resources/picture.cc472
-rw-r--r--chromium/cc/resources/picture.h157
-rw-r--r--chromium/cc/resources/picture_layer_tiling.cc815
-rw-r--r--chromium/cc/resources/picture_layer_tiling.h201
-rw-r--r--chromium/cc/resources/picture_layer_tiling_set.cc351
-rw-r--r--chromium/cc/resources/picture_layer_tiling_set.h130
-rw-r--r--chromium/cc/resources/picture_layer_tiling_set_unittest.cc421
-rw-r--r--chromium/cc/resources/picture_layer_tiling_unittest.cc1413
-rw-r--r--chromium/cc/resources/picture_pile.cc177
-rw-r--r--chromium/cc/resources/picture_pile.h60
-rw-r--r--chromium/cc/resources/picture_pile_base.cc203
-rw-r--r--chromium/cc/resources/picture_pile_base.h82
-rw-r--r--chromium/cc/resources/picture_pile_impl.cc389
-rw-r--r--chromium/cc/resources/picture_pile_impl.h153
-rw-r--r--chromium/cc/resources/picture_pile_impl_unittest.cc800
-rw-r--r--chromium/cc/resources/picture_pile_unittest.cc211
-rw-r--r--chromium/cc/resources/picture_unittest.cc381
-rw-r--r--chromium/cc/resources/pixel_buffer_raster_worker_pool.cc683
-rw-r--r--chromium/cc/resources/pixel_buffer_raster_worker_pool.h82
-rw-r--r--chromium/cc/resources/platform_color.h64
-rw-r--r--chromium/cc/resources/prioritized_resource.cc201
-rw-r--r--chromium/cc/resources/prioritized_resource.h180
-rw-r--r--chromium/cc/resources/prioritized_resource_manager.cc550
-rw-r--r--chromium/cc/resources/prioritized_resource_manager.h241
-rw-r--r--chromium/cc/resources/prioritized_resource_unittest.cc801
-rw-r--r--chromium/cc/resources/prioritized_tile_set.cc145
-rw-r--r--chromium/cc/resources/prioritized_tile_set.h62
-rw-r--r--chromium/cc/resources/prioritized_tile_set_unittest.cc725
-rw-r--r--chromium/cc/resources/priority_calculator.cc125
-rw-r--r--chromium/cc/resources/priority_calculator.h47
-rw-r--r--chromium/cc/resources/raster_mode.cc32
-rw-r--r--chromium/cc/resources/raster_mode.h31
-rw-r--r--chromium/cc/resources/raster_worker_pool.cc543
-rw-r--r--chromium/cc/resources/raster_worker_pool.h278
-rw-r--r--chromium/cc/resources/raster_worker_pool_perftest.cc240
-rw-r--r--chromium/cc/resources/raster_worker_pool_unittest.cc280
-rw-r--r--chromium/cc/resources/resource.cc39
-rw-r--r--chromium/cc/resources/resource.h49
-rw-r--r--chromium/cc/resources/resource_pool.cc121
-rw-r--r--chromium/cc/resources/resource_pool.h76
-rw-r--r--chromium/cc/resources/resource_provider.cc1446
-rw-r--r--chromium/cc/resources/resource_provider.h451
-rw-r--r--chromium/cc/resources/resource_provider_unittest.cc1743
-rw-r--r--chromium/cc/resources/resource_update.cc51
-rw-r--r--chromium/cc/resources/resource_update.h46
-rw-r--r--chromium/cc/resources/resource_update_controller.cc180
-rw-r--r--chromium/cc/resources/resource_update_controller.h90
-rw-r--r--chromium/cc/resources/resource_update_controller_unittest.cc519
-rw-r--r--chromium/cc/resources/resource_update_queue.cc56
-rw-r--r--chromium/cc/resources/resource_update_queue.h43
-rw-r--r--chromium/cc/resources/scoped_resource.cc48
-rw-r--r--chromium/cc/resources/scoped_resource.h49
-rw-r--r--chromium/cc/resources/scoped_resource_unittest.cc105
-rw-r--r--chromium/cc/resources/scoped_ui_resource.cc43
-rw-r--r--chromium/cc/resources/scoped_ui_resource.h46
-rw-r--r--chromium/cc/resources/skpicture_content_layer_updater.cc56
-rw-r--r--chromium/cc/resources/skpicture_content_layer_updater.h53
-rw-r--r--chromium/cc/resources/sync_point_helper.cc48
-rw-r--r--chromium/cc/resources/sync_point_helper.h44
-rw-r--r--chromium/cc/resources/texture_mailbox.cc122
-rw-r--r--chromium/cc/resources/texture_mailbox.h76
-rw-r--r--chromium/cc/resources/tile.cc65
-rw-r--r--chromium/cc/resources/tile.h158
-rw-r--r--chromium/cc/resources/tile_manager.cc847
-rw-r--r--chromium/cc/resources/tile_manager.h171
-rw-r--r--chromium/cc/resources/tile_manager_perftest.cc141
-rw-r--r--chromium/cc/resources/tile_manager_unittest.cc471
-rw-r--r--chromium/cc/resources/tile_priority.cc198
-rw-r--r--chromium/cc/resources/tile_priority.h179
-rw-r--r--chromium/cc/resources/tile_priority_unittest.cc67
-rw-r--r--chromium/cc/resources/transferable_resource.cc20
-rw-r--r--chromium/cc/resources/transferable_resource.h33
-rw-r--r--chromium/cc/resources/ui_resource_bitmap.cc26
-rw-r--r--chromium/cc/resources/ui_resource_bitmap.h50
-rw-r--r--chromium/cc/resources/ui_resource_client.h34
-rw-r--r--chromium/cc/resources/video_resource_updater.cc405
-rw-r--r--chromium/cc/resources/video_resource_updater.h116
-rw-r--r--chromium/cc/resources/video_resource_updater_unittest.cc79
-rw-r--r--chromium/cc/resources/worker_pool.cc433
-rw-r--r--chromium/cc/resources/worker_pool.h143
-rw-r--r--chromium/cc/resources/worker_pool_perftest.cc251
-rw-r--r--chromium/cc/resources/worker_pool_unittest.cc394
-rw-r--r--chromium/cc/scheduler/delay_based_time_source.cc253
-rw-r--r--chromium/cc/scheduler/delay_based_time_source.h78
-rw-r--r--chromium/cc/scheduler/delay_based_time_source_unittest.cc510
-rw-r--r--chromium/cc/scheduler/frame_rate_controller.cc172
-rw-r--r--chromium/cc/scheduler/frame_rate_controller.h100
-rw-r--r--chromium/cc/scheduler/frame_rate_controller_unittest.cc186
-rw-r--r--chromium/cc/scheduler/rate_limiter.cc61
-rw-r--r--chromium/cc/scheduler/rate_limiter.h61
-rw-r--r--chromium/cc/scheduler/rolling_time_delta_history.cc65
-rw-r--r--chromium/cc/scheduler/rolling_time_delta_history.h44
-rw-r--r--chromium/cc/scheduler/rolling_time_delta_history_unittest.cc109
-rw-r--r--chromium/cc/scheduler/scheduler.cc244
-rw-r--r--chromium/cc/scheduler/scheduler.h138
-rw-r--r--chromium/cc/scheduler/scheduler_settings.cc17
-rw-r--r--chromium/cc/scheduler/scheduler_settings.h25
-rw-r--r--chromium/cc/scheduler/scheduler_state_machine.cc500
-rw-r--r--chromium/cc/scheduler/scheduler_state_machine.h212
-rw-r--r--chromium/cc/scheduler/scheduler_state_machine_unittest.cc1391
-rw-r--r--chromium/cc/scheduler/scheduler_unittest.cc627
-rw-r--r--chromium/cc/scheduler/texture_uploader.cc350
-rw-r--r--chromium/cc/scheduler/texture_uploader.h125
-rw-r--r--chromium/cc/scheduler/texture_uploader_unittest.cc247
-rw-r--r--chromium/cc/scheduler/time_source.h51
-rw-r--r--chromium/cc/trees/damage_tracker.cc400
-rw-r--r--chromium/cc/trees/damage_tracker.h80
-rw-r--r--chromium/cc/trees/damage_tracker_unittest.cc1349
-rw-r--r--chromium/cc/trees/layer_sorter.cc469
-rw-r--r--chromium/cc/trees/layer_sorter.h114
-rw-r--r--chromium/cc/trees/layer_sorter_unittest.cc326
-rw-r--r--chromium/cc/trees/layer_tree_host.cc1181
-rw-r--r--chromium/cc/trees/layer_tree_host.h420
-rw-r--r--chromium/cc/trees/layer_tree_host_client.h58
-rw-r--r--chromium/cc/trees/layer_tree_host_common.cc1955
-rw-r--r--chromium/cc/trees/layer_tree_host_common.h253
-rw-r--r--chromium/cc/trees/layer_tree_host_common_unittest.cc8310
-rw-r--r--chromium/cc/trees/layer_tree_host_impl.cc2533
-rw-r--r--chromium/cc/trees/layer_tree_host_impl.h566
-rw-r--r--chromium/cc/trees/layer_tree_host_impl_unittest.cc6326
-rw-r--r--chromium/cc/trees/layer_tree_host_perftest.cc292
-rw-r--r--chromium/cc/trees/layer_tree_host_pixeltest_filters.cc154
-rw-r--r--chromium/cc/trees/layer_tree_host_pixeltest_masks.cc272
-rw-r--r--chromium/cc/trees/layer_tree_host_pixeltest_on_demand_raster.cc112
-rw-r--r--chromium/cc/trees/layer_tree_host_pixeltest_readback.cc886
-rw-r--r--chromium/cc/trees/layer_tree_host_unittest.cc4353
-rw-r--r--chromium/cc/trees/layer_tree_host_unittest_animation.cc806
-rw-r--r--chromium/cc/trees/layer_tree_host_unittest_context.cc1905
-rw-r--r--chromium/cc/trees/layer_tree_host_unittest_damage.cc391
-rw-r--r--chromium/cc/trees/layer_tree_host_unittest_delegated.cc1475
-rw-r--r--chromium/cc/trees/layer_tree_host_unittest_occlusion.cc477
-rw-r--r--chromium/cc/trees/layer_tree_host_unittest_picture.cc119
-rw-r--r--chromium/cc/trees/layer_tree_host_unittest_scroll.cc1028
-rw-r--r--chromium/cc/trees/layer_tree_host_unittest_video.cc98
-rw-r--r--chromium/cc/trees/layer_tree_impl.cc640
-rw-r--r--chromium/cc/trees/layer_tree_impl.h261
-rw-r--r--chromium/cc/trees/layer_tree_settings.cc68
-rw-r--r--chromium/cc/trees/layer_tree_settings.h73
-rw-r--r--chromium/cc/trees/occlusion_tracker.cc746
-rw-r--r--chromium/cc/trees/occlusion_tracker.h176
-rw-r--r--chromium/cc/trees/occlusion_tracker_unittest.cc4933
-rw-r--r--chromium/cc/trees/proxy.cc84
-rw-r--r--chromium/cc/trees/proxy.h148
-rw-r--r--chromium/cc/trees/quad_culler.cc118
-rw-r--r--chromium/cc/trees/quad_culler.h49
-rw-r--r--chromium/cc/trees/quad_culler_unittest.cc936
-rw-r--r--chromium/cc/trees/single_thread_proxy.cc498
-rw-r--r--chromium/cc/trees/single_thread_proxy.h183
-rw-r--r--chromium/cc/trees/thread_proxy.cc1519
-rw-r--r--chromium/cc/trees/thread_proxy.h272
-rw-r--r--chromium/cc/trees/tree_synchronizer.cc262
-rw-r--r--chromium/cc/trees/tree_synchronizer.h56
-rw-r--r--chromium/cc/trees/tree_synchronizer_unittest.cc535
461 files changed, 136706 insertions, 0 deletions
diff --git a/chromium/cc/DEPS b/chromium/cc/DEPS
new file mode 100644
index 00000000000..ad58ebab32d
--- /dev/null
+++ b/chromium/cc/DEPS
@@ -0,0 +1,16 @@
+include_rules = [
+ "+gpu/GLES2",
+ "+gpu/command_buffer/common/mailbox.h",
+ "+media",
+ "+skia/ext",
+ "+third_party/skia/include",
+ "+third_party/khronos/GLES2/gl2.h",
+ "+third_party/khronos/GLES2/gl2ext.h",
+ "+ui/base",
+ "+ui/gfx",
+ "+ui/gl",
+ # DO NOT ADD ANY NEW WEBKIT HEADERS TO THIS LIST.
+ # TODO(danakj): Drop dependencies on WebKit Platform API from cc.
+ "+third_party/WebKit/public/platform/WebGraphicsContext3D.h",
+ "+third_party/WebKit/public/platform/WebGraphicsMemoryAllocation.h",
+]
diff --git a/chromium/cc/OWNERS b/chromium/cc/OWNERS
new file mode 100644
index 00000000000..e90c2f30dd9
--- /dev/null
+++ b/chromium/cc/OWNERS
@@ -0,0 +1,44 @@
+# For patches touching specific topics, try the topic-specific OWNERS at the
+# bottom of the file. For patches that touch multiple areas or if you aren't
+# sure, try the more general OWNERS at the top.
+#
+# Folks listed as unofficial can't do OWNERS approvals but are good people to
+# ask for informal reviews.
+
+enne@chromium.org
+jamesr@chromium.org
+nduca@chromium.org
+
+# layers / quads / passes
+enne@chromium.org
+danakj@chromium.org
+
+# ubercompositor
+piman@chromium.org
+danakj@chromium.org
+
+# resource management
+# unofficial: epenner@chromium.org
+ccameron@chromium.org
+
+# input, gestures, scrolling
+aelias@chromium.org
+jamesr@chromium.org
+
+# scheduling and texture uploading
+brianderson@chromium.org
+reveman@chromium.org
+
+# tiles and tile management
+reveman@chromium.org
+vmpstr@chromium.org
+
+# math / geometry / layer_tree_host_common
+shawnsingh@chromium.org
+enne@chromium.org
+
+# animation
+vollick@chromium.org
+
+per-file *.isolate=csharp@chromium.org
+per-file *.isolate=maruel@chromium.org
diff --git a/chromium/cc/PRESUBMIT.py b/chromium/cc/PRESUBMIT.py
new file mode 100644
index 00000000000..bcf71ef0d52
--- /dev/null
+++ b/chromium/cc/PRESUBMIT.py
@@ -0,0 +1,193 @@
+# Copyright (c) 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.
+
+"""Top-level presubmit script for cc.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for
+details on the presubmit API built into gcl.
+"""
+
+import re
+import string
+
+CC_SOURCE_FILES=(r'^cc/.*\.(cc|h)$',)
+CC_PERF_TEST =(r'^.*_perftest.*\.(cc|h)$',)
+
+def CheckChangeLintsClean(input_api, output_api):
+ input_api.cpplint._cpplint_state.ResetErrorCounts() # reset global state
+ source_filter = lambda x: input_api.FilterSourceFile(
+ x, white_list=CC_SOURCE_FILES, black_list=None)
+ files = [f.AbsoluteLocalPath() for f in
+ input_api.AffectedSourceFiles(source_filter)]
+ level = 1 # strict, but just warn
+
+ for file_name in files:
+ input_api.cpplint.ProcessFile(file_name, level)
+
+ if not input_api.cpplint._cpplint_state.error_count:
+ return []
+
+ return [output_api.PresubmitPromptWarning(
+ 'Changelist failed cpplint.py check.')]
+
+def CheckAsserts(input_api, output_api, white_list=CC_SOURCE_FILES, black_list=None):
+ black_list = tuple(black_list or input_api.DEFAULT_BLACK_LIST)
+ source_file_filter = lambda x: input_api.FilterSourceFile(x, white_list, black_list)
+
+ assert_files = []
+ notreached_files = []
+
+ for f in input_api.AffectedSourceFiles(source_file_filter):
+ contents = input_api.ReadFile(f, 'rb')
+ # WebKit ASSERT() is not allowed.
+ if re.search(r"\bASSERT\(", contents):
+ assert_files.append(f.LocalPath())
+ # WebKit ASSERT_NOT_REACHED() is not allowed.
+ if re.search(r"ASSERT_NOT_REACHED\(", contents):
+ notreached_files.append(f.LocalPath())
+
+ if assert_files:
+ return [output_api.PresubmitError(
+ 'These files use ASSERT instead of using DCHECK:',
+ items=assert_files)]
+ if notreached_files:
+ return [output_api.PresubmitError(
+ 'These files use ASSERT_NOT_REACHED instead of using NOTREACHED:',
+ items=notreached_files)]
+ return []
+
+def CheckStdAbs(input_api, output_api,
+ white_list=CC_SOURCE_FILES, black_list=None):
+ black_list = tuple(black_list or input_api.DEFAULT_BLACK_LIST)
+ source_file_filter = lambda x: input_api.FilterSourceFile(x,
+ white_list,
+ black_list)
+
+ using_std_abs_files = []
+ found_fabs_files = []
+ missing_std_prefix_files = []
+
+ for f in input_api.AffectedSourceFiles(source_file_filter):
+ contents = input_api.ReadFile(f, 'rb')
+ if re.search(r"using std::f?abs;", contents):
+ using_std_abs_files.append(f.LocalPath())
+ if re.search(r"\bfabsf?\(", contents):
+ found_fabs_files.append(f.LocalPath());
+ # The following regular expression in words says:
+ # "if there is no 'std::' behind an 'abs(' or 'absf(',
+ # or if there is no 'std::' behind a 'fabs(' or 'fabsf(',
+ # then it's a match."
+ if re.search(r"((?<!std::)(\babsf?\()|(?<!std::)(\bfabsf?\())", contents):
+ missing_std_prefix_files.append(f.LocalPath())
+
+ result = []
+ if using_std_abs_files:
+ result.append(output_api.PresubmitError(
+ 'These files have "using std::abs" which is not permitted.',
+ items=using_std_abs_files))
+ if found_fabs_files:
+ result.append(output_api.PresubmitError(
+ 'std::abs() should be used instead of std::fabs() for consistency.',
+ items=found_fabs_files))
+ if missing_std_prefix_files:
+ result.append(output_api.PresubmitError(
+ 'These files use abs(), absf(), fabs(), or fabsf() without qualifying '
+ 'the std namespace. Please use std::abs() in all places.',
+ items=missing_std_prefix_files))
+ return result
+
+def CheckSpamLogging(input_api,
+ output_api,
+ white_list=CC_SOURCE_FILES,
+ black_list=None):
+ black_list = tuple(black_list or input_api.DEFAULT_BLACK_LIST)
+ source_file_filter = lambda x: input_api.FilterSourceFile(x,
+ white_list,
+ black_list)
+
+ log_info = []
+ printf = []
+
+ for f in input_api.AffectedSourceFiles(source_file_filter):
+ contents = input_api.ReadFile(f, 'rb')
+ if re.search(r"\bD?LOG\s*\(\s*INFO\s*\)", contents):
+ log_info.append(f.LocalPath())
+ if re.search(r"\bf?printf\(", contents):
+ printf.append(f.LocalPath())
+
+ if log_info:
+ return [output_api.PresubmitError(
+ 'These files spam the console log with LOG(INFO):',
+ items=log_info)]
+ if printf:
+ return [output_api.PresubmitError(
+ 'These files spam the console log with printf/fprintf:',
+ items=printf)]
+ return []
+
+def CheckPassByValue(input_api,
+ output_api,
+ white_list=CC_SOURCE_FILES,
+ black_list=None):
+ black_list = tuple(black_list or input_api.DEFAULT_BLACK_LIST)
+ source_file_filter = lambda x: input_api.FilterSourceFile(x,
+ white_list,
+ black_list)
+
+ local_errors = []
+
+ # Well-defined simple classes containing only <= 4 ints, or <= 2 floats.
+ pass_by_value_types = ['base::Time',
+ 'base::TimeTicks',
+ 'gfx::Point',
+ 'gfx::PointF',
+ 'gfx::Rect',
+ 'gfx::Size',
+ 'gfx::SizeF',
+ 'gfx::Vector2d',
+ 'gfx::Vector2dF',
+ ]
+
+ for f in input_api.AffectedSourceFiles(source_file_filter):
+ contents = input_api.ReadFile(f, 'rb')
+ match = re.search(
+ r'\bconst +' + '(?P<type>(%s))&' %
+ string.join(pass_by_value_types, '|'),
+ contents)
+ if match:
+ local_errors.append(output_api.PresubmitError(
+ '%s passes %s by const ref instead of by value.' %
+ (f.LocalPath(), match.group('type'))))
+ return local_errors
+
+def CheckTodos(input_api, output_api):
+ errors = []
+
+ source_file_filter = lambda x: x
+ for f in input_api.AffectedSourceFiles(source_file_filter):
+ contents = input_api.ReadFile(f, 'rb')
+ if ('FIX'+'ME') in contents:
+ errors.append(f.LocalPath())
+
+ if errors:
+ return [output_api.PresubmitError(
+ 'All TODO comments should be of the form TODO(name).',
+ items=errors)]
+ return []
+
+
+def CheckChangeOnUpload(input_api, output_api):
+ results = []
+ results += CheckAsserts(input_api, output_api)
+ results += CheckStdAbs(input_api, output_api)
+ results += CheckSpamLogging(input_api, output_api, black_list=CC_PERF_TEST)
+ results += CheckPassByValue(input_api, output_api)
+ results += CheckChangeLintsClean(input_api, output_api)
+ results += CheckTodos(input_api, output_api)
+ return results
+
+def GetPreferredTrySlaves(project, change):
+ return [
+ 'linux_layout_rel',
+ ]
diff --git a/chromium/cc/animation/animation.cc b/chromium/cc/animation/animation.cc
new file mode 100644
index 00000000000..214b8bfc47f
--- /dev/null
+++ b/chromium/cc/animation/animation.cc
@@ -0,0 +1,238 @@
+// 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 "cc/animation/animation.h"
+
+#include <cmath>
+
+#include "base/debug/trace_event.h"
+#include "base/strings/string_util.h"
+#include "cc/animation/animation_curve.h"
+
+namespace {
+
+// This should match the RunState enum.
+static const char* const s_runStateNames[] = {
+ "WaitingForNextTick",
+ "WaitingForTargetAvailability",
+ "WaitingForStartTime",
+ "WaitingForDeletion",
+ "Starting",
+ "Running",
+ "Paused",
+ "Finished",
+ "Aborted"
+};
+
+COMPILE_ASSERT(static_cast<int>(cc::Animation::RunStateEnumSize) ==
+ arraysize(s_runStateNames),
+ RunState_names_match_enum);
+
+// This should match the TargetProperty enum.
+static const char* const s_targetPropertyNames[] = {
+ "Transform",
+ "Opacity"
+};
+
+COMPILE_ASSERT(static_cast<int>(cc::Animation::TargetPropertyEnumSize) ==
+ arraysize(s_targetPropertyNames),
+ TargetProperty_names_match_enum);
+
+} // namespace
+
+namespace cc {
+
+scoped_ptr<Animation> Animation::Create(
+ scoped_ptr<AnimationCurve> curve,
+ int animation_id,
+ int group_id,
+ TargetProperty target_property) {
+ return make_scoped_ptr(new Animation(curve.Pass(),
+ animation_id,
+ group_id,
+ target_property)); }
+
+Animation::Animation(scoped_ptr<AnimationCurve> curve,
+ int animation_id,
+ int group_id,
+ TargetProperty target_property)
+ : curve_(curve.Pass()),
+ id_(animation_id),
+ group_(group_id),
+ target_property_(target_property),
+ run_state_(WaitingForTargetAvailability),
+ iterations_(1),
+ start_time_(0),
+ alternates_direction_(false),
+ time_offset_(0),
+ needs_synchronized_start_time_(false),
+ received_finished_event_(false),
+ suspended_(false),
+ pause_time_(0),
+ total_paused_time_(0),
+ is_controlling_instance_(false),
+ is_impl_only_(false) {}
+
+Animation::~Animation() {
+ if (run_state_ == Running || run_state_ == Paused)
+ SetRunState(Aborted, 0);
+}
+
+void Animation::SetRunState(RunState run_state, double monotonic_time) {
+ if (suspended_)
+ return;
+
+ char name_buffer[256];
+ base::snprintf(name_buffer,
+ sizeof(name_buffer),
+ "%s-%d%s",
+ s_targetPropertyNames[target_property_],
+ group_,
+ is_controlling_instance_ ? "(impl)" : "");
+
+ bool is_waiting_to_start = run_state_ == WaitingForNextTick ||
+ run_state_ == WaitingForTargetAvailability ||
+ run_state_ == WaitingForStartTime ||
+ run_state_ == Starting;
+
+ if (is_waiting_to_start && run_state == Running) {
+ TRACE_EVENT_ASYNC_BEGIN1(
+ "cc", "Animation", this, "Name", TRACE_STR_COPY(name_buffer));
+ }
+
+ bool was_finished = is_finished();
+
+ const char* old_run_state_name = s_runStateNames[run_state_];
+
+ if (run_state == Running && run_state_ == Paused)
+ total_paused_time_ += monotonic_time - pause_time_;
+ else if (run_state == Paused)
+ pause_time_ = monotonic_time;
+ run_state_ = run_state;
+
+ const char* new_run_state_name = s_runStateNames[run_state];
+
+ if (!was_finished && is_finished())
+ TRACE_EVENT_ASYNC_END0("cc", "Animation", this);
+
+ char state_buffer[256];
+ base::snprintf(state_buffer,
+ sizeof(state_buffer),
+ "%s->%s",
+ old_run_state_name,
+ new_run_state_name);
+
+ TRACE_EVENT_INSTANT2("cc",
+ "LayerAnimationController::SetRunState",
+ TRACE_EVENT_SCOPE_THREAD,
+ "Name",
+ TRACE_STR_COPY(name_buffer),
+ "State",
+ TRACE_STR_COPY(state_buffer));
+}
+
+void Animation::Suspend(double monotonic_time) {
+ SetRunState(Paused, monotonic_time);
+ suspended_ = true;
+}
+
+void Animation::Resume(double monotonic_time) {
+ suspended_ = false;
+ SetRunState(Running, monotonic_time);
+}
+
+bool Animation::IsFinishedAt(double monotonic_time) const {
+ if (is_finished())
+ return true;
+
+ if (needs_synchronized_start_time_)
+ return false;
+
+ return run_state_ == Running &&
+ iterations_ >= 0 &&
+ iterations_ * curve_->Duration() <= (monotonic_time -
+ start_time() -
+ total_paused_time_);
+}
+
+double Animation::TrimTimeToCurrentIteration(double monotonic_time) const {
+ double trimmed = monotonic_time + time_offset_;
+
+ // If we're paused, time is 'stuck' at the pause time.
+ if (run_state_ == Paused)
+ trimmed = pause_time_;
+
+ // Returned time should always be relative to the start time and should
+ // subtract all time spent paused.
+ trimmed -= start_time_ + total_paused_time_;
+
+ // Zero is always the start of the animation.
+ if (trimmed <= 0)
+ return 0;
+
+ // Always return zero if we have no iterations.
+ if (!iterations_)
+ return 0;
+
+ // Don't attempt to trim if we have no duration.
+ if (curve_->Duration() <= 0)
+ return 0;
+
+ // If less than an iteration duration, just return trimmed.
+ if (trimmed < curve_->Duration())
+ return trimmed;
+
+ // If greater than or equal to the total duration, return iteration duration.
+ if (iterations_ >= 0 && trimmed >= curve_->Duration() * iterations_) {
+ if (alternates_direction_ && !(iterations_ % 2))
+ return 0;
+ return curve_->Duration();
+ }
+
+ // We need to know the current iteration if we're alternating.
+ int iteration = static_cast<int>(trimmed / curve_->Duration());
+
+ // Calculate x where trimmed = x + n * curve_->Duration() for some positive
+ // integer n.
+ trimmed = fmod(trimmed, curve_->Duration());
+
+ // If we're alternating and on an odd iteration, reverse the direction.
+ if (alternates_direction_ && iteration % 2 == 1)
+ return curve_->Duration() - trimmed;
+
+ return trimmed;
+}
+
+scoped_ptr<Animation> Animation::Clone(InstanceType instance_type) const {
+ return CloneAndInitialize(instance_type, run_state_, start_time_);
+}
+
+scoped_ptr<Animation> Animation::CloneAndInitialize(InstanceType instance_type,
+ RunState initial_run_state,
+ double start_time) const {
+ scoped_ptr<Animation> to_return(
+ new Animation(curve_->Clone(), id_, group_, target_property_));
+ to_return->run_state_ = initial_run_state;
+ to_return->iterations_ = iterations_;
+ to_return->start_time_ = start_time;
+ to_return->pause_time_ = pause_time_;
+ to_return->total_paused_time_ = total_paused_time_;
+ to_return->time_offset_ = time_offset_;
+ to_return->alternates_direction_ = alternates_direction_;
+ to_return->is_controlling_instance_ = instance_type == ControllingInstance;
+ return to_return.Pass();
+}
+
+void Animation::PushPropertiesTo(Animation* other) const {
+ // Currently, we only push changes due to pausing and resuming animations on
+ // the main thread.
+ if (run_state_ == Animation::Paused ||
+ other->run_state_ == Animation::Paused) {
+ other->run_state_ = run_state_;
+ other->pause_time_ = pause_time_;
+ other->total_paused_time_ = total_paused_time_;
+ }
+}
+
+} // namespace cc
diff --git a/chromium/cc/animation/animation.h b/chromium/cc/animation/animation.h
new file mode 100644
index 00000000000..990b9ef82cb
--- /dev/null
+++ b/chromium/cc/animation/animation.h
@@ -0,0 +1,200 @@
+// 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.
+
+#ifndef CC_ANIMATION_ANIMATION_H_
+#define CC_ANIMATION_ANIMATION_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+
+namespace cc {
+
+class AnimationCurve;
+
+// An Animation, contains all the state required to play an AnimationCurve.
+// Specifically, the affected property, the run state (paused, finished, etc.),
+// loop count, last pause time, and the total time spent paused.
+class CC_EXPORT Animation {
+ public:
+ // Animations begin in one of the 'waiting' states. Animations waiting for the
+ // next tick will start the next time the controller animates. Animations
+ // waiting for target availibility will run as soon as their target property
+ // is free (and all the animations animating with it are also able to run).
+ // Animations waiting for their start time to come have be scheduled to run at
+ // a particular point in time. When this time arrives, the controller will
+ // move the animations into the Starting state, and then into the Running
+ // state. Running animations may toggle between Running and Paused, and may be
+ // stopped by moving into either the Aborted or Finished states. A Finished
+ // animation was allowed to run to completion, but an Aborted animation was
+ // not.
+ enum RunState {
+ WaitingForNextTick = 0,
+ WaitingForTargetAvailability,
+ WaitingForStartTime,
+ WaitingForDeletion,
+ Starting,
+ Running,
+ Paused,
+ Finished,
+ Aborted,
+ // This sentinel must be last.
+ RunStateEnumSize
+ };
+
+ enum TargetProperty {
+ Transform = 0,
+ Opacity,
+ // This sentinel must be last.
+ TargetPropertyEnumSize
+ };
+
+ static scoped_ptr<Animation> Create(scoped_ptr<AnimationCurve> curve,
+ int animation_id,
+ int group_id,
+ TargetProperty target_property);
+
+ virtual ~Animation();
+
+ int id() const { return id_; }
+ int group() const { return group_; }
+ TargetProperty target_property() const { return target_property_; }
+
+ RunState run_state() const { return run_state_; }
+ void SetRunState(RunState run_state, double monotonic_time);
+
+ // This is the number of times that the animation will play. If this
+ // value is zero the animation will not play. If it is negative, then
+ // the animation will loop indefinitely.
+ int iterations() const { return iterations_; }
+ void set_iterations(int n) { iterations_ = n; }
+
+ double start_time() const { return start_time_; }
+ void set_start_time(double monotonic_time) { start_time_ = monotonic_time; }
+ bool has_set_start_time() const { return !!start_time_; }
+
+ double time_offset() const { return time_offset_; }
+ void set_time_offset(double monotonic_time) { time_offset_ = monotonic_time; }
+
+ void Suspend(double monotonic_time);
+ void Resume(double monotonic_time);
+
+ // If alternates_direction is true, on odd numbered iterations we reverse the
+ // curve.
+ bool alternates_direction() const { return alternates_direction_; }
+ void set_alternates_direction(bool alternates) {
+ alternates_direction_ = alternates;
+ }
+
+ bool IsFinishedAt(double monotonic_time) const;
+ bool is_finished() const {
+ return run_state_ == Finished ||
+ run_state_ == Aborted ||
+ run_state_ == WaitingForDeletion;
+ }
+
+ AnimationCurve* curve() { return curve_.get(); }
+ const AnimationCurve* curve() const { return curve_.get(); }
+
+ // If this is true, even if the animation is running, it will not be tickable
+ // until it is given a start time. This is true for animations running on the
+ // main thread.
+ bool needs_synchronized_start_time() const {
+ return needs_synchronized_start_time_;
+ }
+ void set_needs_synchronized_start_time(bool needs_synchronized_start_time) {
+ needs_synchronized_start_time_ = needs_synchronized_start_time;
+ }
+
+ // This is true for animations running on the main thread when the Finished
+ // event sent by the corresponding impl animation has been received.
+ bool received_finished_event() const {
+ return received_finished_event_;
+ }
+ void set_received_finished_event(bool received_finished_event) {
+ received_finished_event_ = received_finished_event;
+ }
+
+ // Takes the given absolute time, and using the start time and the number
+ // of iterations, returns the relative time in the current iteration.
+ double TrimTimeToCurrentIteration(double monotonic_time) const;
+
+ enum InstanceType {
+ ControllingInstance = 0,
+ NonControllingInstance
+ };
+
+ scoped_ptr<Animation> Clone(InstanceType instance_type) const;
+ scoped_ptr<Animation> CloneAndInitialize(InstanceType instance_type,
+ RunState initial_run_state,
+ double start_time) const;
+ bool is_controlling_instance() const { return is_controlling_instance_; }
+
+ void PushPropertiesTo(Animation* other) const;
+
+ void set_is_impl_only(bool is_impl_only) { is_impl_only_ = is_impl_only; }
+ bool is_impl_only() const { return is_impl_only_; }
+
+ private:
+ Animation(scoped_ptr<AnimationCurve> curve,
+ int animation_id,
+ int group_id,
+ TargetProperty target_property);
+
+ scoped_ptr<AnimationCurve> curve_;
+
+ // IDs are not necessarily unique.
+ int id_;
+
+ // Animations that must be run together are called 'grouped' and have the same
+ // group id. Grouped animations are guaranteed to start at the same time and
+ // no other animations may animate any of the group's target properties until
+ // all animations in the group have finished animating. Note: an active
+ // animation's group id and target property uniquely identify that animation.
+ int group_;
+
+ TargetProperty target_property_;
+ RunState run_state_;
+ int iterations_;
+ double start_time_;
+ bool alternates_direction_;
+
+ // The time offset effectively pushes the start of the animation back in time.
+ // This is used for resuming paused animations -- an animation is added with a
+ // non-zero time offset, causing the animation to skip ahead to the desired
+ // point in time.
+ double time_offset_;
+
+ bool needs_synchronized_start_time_;
+ bool received_finished_event_;
+
+ // When an animation is suspended, it behaves as if it is paused and it also
+ // ignores all run state changes until it is resumed. This is used for testing
+ // purposes.
+ bool suspended_;
+
+ // These are used in TrimTimeToCurrentIteration to account for time
+ // spent while paused. This is not included in AnimationState since it
+ // there is absolutely no need for clients of this controller to know
+ // about these values.
+ double pause_time_;
+ double total_paused_time_;
+
+ // Animations lead dual lives. An active animation will be conceptually owned
+ // by two controllers, one on the impl thread and one on the main. In reality,
+ // there will be two separate Animation instances for the same animation. They
+ // will have the same group id and the same target property (these two values
+ // uniquely identify an animation). The instance on the impl thread is the
+ // instance that ultimately controls the values of the animating layer and so
+ // we will refer to it as the 'controlling instance'.
+ bool is_controlling_instance_;
+
+ bool is_impl_only_;
+
+ DISALLOW_COPY_AND_ASSIGN(Animation);
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_ANIMATION_H_
diff --git a/chromium/cc/animation/animation_curve.cc b/chromium/cc/animation/animation_curve.cc
new file mode 100644
index 00000000000..cf04da7f396
--- /dev/null
+++ b/chromium/cc/animation/animation_curve.cc
@@ -0,0 +1,39 @@
+// 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 "cc/animation/animation_curve.h"
+
+#include "base/logging.h"
+
+namespace cc {
+
+const FloatAnimationCurve* AnimationCurve::ToFloatAnimationCurve() const {
+ DCHECK(Type() == AnimationCurve::Float);
+ return static_cast<const FloatAnimationCurve*>(this);
+}
+
+AnimationCurve::CurveType FloatAnimationCurve::Type() const {
+ return Float;
+}
+
+const TransformAnimationCurve* AnimationCurve::ToTransformAnimationCurve()
+ const {
+ DCHECK(Type() == AnimationCurve::Transform);
+ return static_cast<const TransformAnimationCurve*>(this);
+}
+
+AnimationCurve::CurveType TransformAnimationCurve::Type() const {
+ return Transform;
+}
+
+const FilterAnimationCurve* AnimationCurve::ToFilterAnimationCurve() const {
+ DCHECK(Type() == AnimationCurve::Filter);
+ return static_cast<const FilterAnimationCurve*>(this);
+}
+
+AnimationCurve::CurveType FilterAnimationCurve::Type() const {
+ return Filter;
+}
+
+} // namespace cc
diff --git a/chromium/cc/animation/animation_curve.h b/chromium/cc/animation/animation_curve.h
new file mode 100644
index 00000000000..3860234ab13
--- /dev/null
+++ b/chromium/cc/animation/animation_curve.h
@@ -0,0 +1,68 @@
+// 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.
+
+#ifndef CC_ANIMATION_ANIMATION_CURVE_H_
+#define CC_ANIMATION_ANIMATION_CURVE_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/output/filter_operations.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+
+class FilterAnimationCurve;
+class FloatAnimationCurve;
+class TransformAnimationCurve;
+class TransformOperations;
+
+// An animation curve is a function that returns a value given a time.
+class CC_EXPORT AnimationCurve {
+ public:
+ enum CurveType { Float, Transform, Filter };
+
+ virtual ~AnimationCurve() {}
+
+ virtual double Duration() const = 0;
+ virtual CurveType Type() const = 0;
+ virtual scoped_ptr<AnimationCurve> Clone() const = 0;
+
+ const FloatAnimationCurve* ToFloatAnimationCurve() const;
+ const TransformAnimationCurve* ToTransformAnimationCurve() const;
+ const FilterAnimationCurve* ToFilterAnimationCurve() const;
+};
+
+class CC_EXPORT FloatAnimationCurve : public AnimationCurve {
+ public:
+ virtual ~FloatAnimationCurve() {}
+
+ virtual float GetValue(double t) const = 0;
+
+ // Partial Animation implementation.
+ virtual CurveType Type() const OVERRIDE;
+};
+
+class CC_EXPORT TransformAnimationCurve : public AnimationCurve {
+ public:
+ virtual ~TransformAnimationCurve() {}
+
+ virtual gfx::Transform GetValue(double t) const = 0;
+
+ // Partial Animation implementation.
+ virtual CurveType Type() const OVERRIDE;
+};
+
+class CC_EXPORT FilterAnimationCurve : public AnimationCurve {
+ public:
+ virtual ~FilterAnimationCurve() {}
+
+ virtual FilterOperations GetValue(double t) const = 0;
+
+ // Partial Animation implementation.
+ virtual CurveType Type() const OVERRIDE;
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_ANIMATION_CURVE_H_
diff --git a/chromium/cc/animation/animation_delegate.h b/chromium/cc/animation/animation_delegate.h
new file mode 100644
index 00000000000..c9367b8546a
--- /dev/null
+++ b/chromium/cc/animation/animation_delegate.h
@@ -0,0 +1,21 @@
+// Copyright 2013 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.
+
+#ifndef CC_ANIMATION_ANIMATION_DELEGATE_H_
+#define CC_ANIMATION_ANIMATION_DELEGATE_H_
+
+namespace cc {
+
+class AnimationDelegate {
+ public:
+ virtual void NotifyAnimationStarted(double time) = 0;
+ virtual void NotifyAnimationFinished(double time) = 0;
+
+ protected:
+ virtual ~AnimationDelegate() {}
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_ANIMATION_DELEGATE_H_
diff --git a/chromium/cc/animation/animation_events.cc b/chromium/cc/animation/animation_events.cc
new file mode 100644
index 00000000000..95b0e071a21
--- /dev/null
+++ b/chromium/cc/animation/animation_events.cc
@@ -0,0 +1,23 @@
+// Copyright 2013 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 "cc/animation/animation_events.h"
+
+namespace cc {
+
+AnimationEvent::AnimationEvent(AnimationEvent::Type type,
+ int layer_id,
+ int group_id,
+ Animation::TargetProperty target_property,
+ double monotonic_time)
+ : type(type),
+ layer_id(layer_id),
+ group_id(group_id),
+ target_property(target_property),
+ monotonic_time(monotonic_time),
+ is_impl_only(false),
+ opacity(0.f) {
+}
+
+} // namespace cc
diff --git a/chromium/cc/animation/animation_events.h b/chromium/cc/animation/animation_events.h
new file mode 100644
index 00000000000..1fd742c9a8b
--- /dev/null
+++ b/chromium/cc/animation/animation_events.h
@@ -0,0 +1,39 @@
+// 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.
+
+#ifndef CC_ANIMATION_ANIMATION_EVENTS_H_
+#define CC_ANIMATION_ANIMATION_EVENTS_H_
+
+#include <vector>
+
+#include "cc/animation/animation.h"
+#include "cc/base/cc_export.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+
+struct CC_EXPORT AnimationEvent {
+ enum Type { Started, Finished, PropertyUpdate };
+
+ AnimationEvent(Type type,
+ int layer_id,
+ int group_id,
+ Animation::TargetProperty target_property,
+ double monotonic_time);
+
+ Type type;
+ int layer_id;
+ int group_id;
+ Animation::TargetProperty target_property;
+ double monotonic_time;
+ bool is_impl_only;
+ float opacity;
+ gfx::Transform transform;
+};
+
+typedef std::vector<AnimationEvent> AnimationEventsVector;
+
+} // namespace cc
+
+#endif // CC_ANIMATION_ANIMATION_EVENTS_H_
diff --git a/chromium/cc/animation/animation_id_provider.cc b/chromium/cc/animation/animation_id_provider.cc
new file mode 100644
index 00000000000..5c7afb6d98c
--- /dev/null
+++ b/chromium/cc/animation/animation_id_provider.cc
@@ -0,0 +1,19 @@
+// Copyright (c) 2013 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 "cc/animation/animation_id_provider.h"
+
+namespace cc {
+
+int AnimationIdProvider::NextAnimationId() {
+ static int next_animation_id = 1;
+ return next_animation_id++;
+}
+
+int AnimationIdProvider::NextGroupId() {
+ static int next_group_id = 1;
+ return next_group_id++;
+}
+
+} // namespace cc
diff --git a/chromium/cc/animation/animation_id_provider.h b/chromium/cc/animation/animation_id_provider.h
new file mode 100644
index 00000000000..b403841315b
--- /dev/null
+++ b/chromium/cc/animation/animation_id_provider.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2013 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.
+
+#ifndef CC_ANIMATION_ANIMATION_ID_PROVIDER_H_
+#define CC_ANIMATION_ANIMATION_ID_PROVIDER_H_
+
+#include "base/basictypes.h"
+#include "cc/base/cc_export.h"
+
+namespace cc {
+
+class CC_EXPORT AnimationIdProvider {
+ public:
+ // These functions each return monotonically increasing values.
+ static int NextAnimationId();
+ static int NextGroupId();
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(AnimationIdProvider);
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_ANIMATION_ID_PROVIDER_H_
diff --git a/chromium/cc/animation/animation_registrar.cc b/chromium/cc/animation/animation_registrar.cc
new file mode 100644
index 00000000000..05f522cbdce
--- /dev/null
+++ b/chromium/cc/animation/animation_registrar.cc
@@ -0,0 +1,57 @@
+// 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 "cc/animation/animation_registrar.h"
+
+#include "cc/animation/layer_animation_controller.h"
+
+namespace cc {
+
+AnimationRegistrar::AnimationRegistrar() {}
+
+AnimationRegistrar::~AnimationRegistrar() {
+ AnimationControllerMap copy = all_animation_controllers_;
+ for (AnimationControllerMap::iterator iter = copy.begin();
+ iter != copy.end();
+ ++iter)
+ (*iter).second->SetAnimationRegistrar(NULL);
+}
+
+scoped_refptr<LayerAnimationController>
+AnimationRegistrar::GetAnimationControllerForId(int id) {
+ scoped_refptr<LayerAnimationController> to_return;
+ if (!ContainsKey(all_animation_controllers_, id)) {
+ to_return = LayerAnimationController::Create(id);
+ to_return->SetAnimationRegistrar(this);
+ all_animation_controllers_[id] = to_return.get();
+ } else {
+ to_return = all_animation_controllers_[id];
+ }
+ return to_return;
+}
+
+void AnimationRegistrar::DidActivateAnimationController(
+ LayerAnimationController* controller) {
+ active_animation_controllers_[controller->id()] = controller;
+}
+
+void AnimationRegistrar::DidDeactivateAnimationController(
+ LayerAnimationController* controller) {
+ if (ContainsKey(active_animation_controllers_, controller->id()))
+ active_animation_controllers_.erase(controller->id());
+}
+
+void AnimationRegistrar::RegisterAnimationController(
+ LayerAnimationController* controller) {
+ all_animation_controllers_[controller->id()] = controller;
+}
+
+void AnimationRegistrar::UnregisterAnimationController(
+ LayerAnimationController* controller) {
+ if (ContainsKey(all_animation_controllers_, controller->id()))
+ all_animation_controllers_.erase(controller->id());
+ DidDeactivateAnimationController(controller);
+}
+
+} // namespace cc
diff --git a/chromium/cc/animation/animation_registrar.h b/chromium/cc/animation/animation_registrar.h
new file mode 100644
index 00000000000..579b11f8202
--- /dev/null
+++ b/chromium/cc/animation/animation_registrar.h
@@ -0,0 +1,65 @@
+// 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.
+
+#ifndef CC_ANIMATION_ANIMATION_REGISTRAR_H_
+#define CC_ANIMATION_ANIMATION_REGISTRAR_H_
+
+#include "base/containers/hash_tables.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+
+namespace cc {
+
+class LayerAnimationController;
+
+class CC_EXPORT AnimationRegistrar {
+ public:
+ typedef base::hash_map<int, LayerAnimationController*> AnimationControllerMap;
+
+ static scoped_ptr<AnimationRegistrar> Create() {
+ return make_scoped_ptr(new AnimationRegistrar());
+ }
+
+ virtual ~AnimationRegistrar();
+
+ // If an animation has been registered for the given id, return it. Otherwise
+ // creates a new one and returns a scoped_refptr to it.
+ scoped_refptr<LayerAnimationController> GetAnimationControllerForId(int id);
+
+ // Registers the given animation controller as active. An active animation
+ // controller is one that has a running animation that needs to be ticked.
+ void DidActivateAnimationController(LayerAnimationController* controller);
+
+ // Unregisters the given animation controller. When this happens, the
+ // animation controller will no longer be ticked (since it's not active). It
+ // is not an error to call this function with a deactivated controller.
+ void DidDeactivateAnimationController(LayerAnimationController* controller);
+
+ // Registers the given controller as alive.
+ void RegisterAnimationController(LayerAnimationController* controller);
+
+ // Unregisters the given controller as alive.
+ void UnregisterAnimationController(LayerAnimationController* controller);
+
+ const AnimationControllerMap& active_animation_controllers() const {
+ return active_animation_controllers_;
+ }
+
+ const AnimationControllerMap& all_animation_controllers() const {
+ return all_animation_controllers_;
+ }
+
+ private:
+ AnimationRegistrar();
+
+ AnimationControllerMap active_animation_controllers_;
+ AnimationControllerMap all_animation_controllers_;
+
+ DISALLOW_COPY_AND_ASSIGN(AnimationRegistrar);
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_ANIMATION_REGISTRAR_H_
diff --git a/chromium/cc/animation/animation_unittest.cc b/chromium/cc/animation/animation_unittest.cc
new file mode 100644
index 00000000000..337d3eedb35
--- /dev/null
+++ b/chromium/cc/animation/animation_unittest.cc
@@ -0,0 +1,205 @@
+// 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 "cc/animation/animation.h"
+
+#include "cc/test/animation_test_common.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+scoped_ptr<Animation> CreateAnimation(int iterations, double duration) {
+ scoped_ptr<Animation> to_return(Animation::Create(
+ make_scoped_ptr(
+ new FakeFloatAnimationCurve(duration)).PassAs<AnimationCurve>(),
+ 0,
+ 1,
+ Animation::Opacity));
+ to_return->set_iterations(iterations);
+ return to_return.Pass();
+}
+
+scoped_ptr<Animation> CreateAnimation(int iterations) {
+ return CreateAnimation(iterations, 1);
+}
+
+TEST(AnimationTest, TrimTimeZeroIterations) {
+ scoped_ptr<Animation> anim(CreateAnimation(0));
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(-1.0));
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(0.0));
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(1.0));
+}
+
+TEST(AnimationTest, TrimTimeOneIteration) {
+ scoped_ptr<Animation> anim(CreateAnimation(1));
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(-1.0));
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(0.0));
+ EXPECT_EQ(1, anim->TrimTimeToCurrentIteration(1.0));
+ EXPECT_EQ(1, anim->TrimTimeToCurrentIteration(2.0));
+}
+
+TEST(AnimationTest, TrimTimeInfiniteIterations) {
+ scoped_ptr<Animation> anim(CreateAnimation(-1));
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(0.0));
+ EXPECT_EQ(0.5, anim->TrimTimeToCurrentIteration(0.5));
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(1.0));
+ EXPECT_EQ(0.5, anim->TrimTimeToCurrentIteration(1.5));
+}
+
+TEST(AnimationTest, TrimTimeAlternating) {
+ scoped_ptr<Animation> anim(CreateAnimation(-1));
+ anim->set_alternates_direction(true);
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(0.0));
+ EXPECT_EQ(0.5, anim->TrimTimeToCurrentIteration(0.5));
+ EXPECT_EQ(1, anim->TrimTimeToCurrentIteration(1.0));
+ EXPECT_EQ(0.75, anim->TrimTimeToCurrentIteration(1.25));
+}
+
+TEST(AnimationTest, TrimTimeStartTime) {
+ scoped_ptr<Animation> anim(CreateAnimation(1));
+ anim->set_start_time(4);
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(0.0));
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(4.0));
+ EXPECT_EQ(0.5, anim->TrimTimeToCurrentIteration(4.5));
+ EXPECT_EQ(1, anim->TrimTimeToCurrentIteration(5.0));
+ EXPECT_EQ(1, anim->TrimTimeToCurrentIteration(6.0));
+}
+
+TEST(AnimationTest, TrimTimeTimeOffset) {
+ scoped_ptr<Animation> anim(CreateAnimation(1));
+ anim->set_time_offset(4);
+ anim->set_start_time(4);
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(0.0));
+ EXPECT_EQ(0.5, anim->TrimTimeToCurrentIteration(0.5));
+ EXPECT_EQ(1, anim->TrimTimeToCurrentIteration(1.0));
+ EXPECT_EQ(1, anim->TrimTimeToCurrentIteration(1.0));
+}
+
+TEST(AnimationTest, TrimTimePauseResume) {
+ scoped_ptr<Animation> anim(CreateAnimation(1));
+ anim->SetRunState(Animation::Running, 0.0);
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(0.0));
+ EXPECT_EQ(0.5, anim->TrimTimeToCurrentIteration(0.5));
+ anim->SetRunState(Animation::Paused, 0.5);
+ EXPECT_EQ(0.5, anim->TrimTimeToCurrentIteration(1024.0));
+ anim->SetRunState(Animation::Running, 1024.0);
+ EXPECT_EQ(0.5, anim->TrimTimeToCurrentIteration(1024.0));
+ EXPECT_EQ(1, anim->TrimTimeToCurrentIteration(1024.5));
+}
+
+TEST(AnimationTest, TrimTimeSuspendResume) {
+ scoped_ptr<Animation> anim(CreateAnimation(1));
+ anim->SetRunState(Animation::Running, 0.0);
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(0.0));
+ EXPECT_EQ(0.5, anim->TrimTimeToCurrentIteration(0.5));
+ anim->Suspend(0.5);
+ EXPECT_EQ(0.5, anim->TrimTimeToCurrentIteration(1024.0));
+ anim->Resume(1024);
+ EXPECT_EQ(0.5, anim->TrimTimeToCurrentIteration(1024.0));
+ EXPECT_EQ(1, anim->TrimTimeToCurrentIteration(1024.5));
+}
+
+TEST(AnimationTest, TrimTimeZeroDuration) {
+ scoped_ptr<Animation> anim(CreateAnimation(0, 0));
+ anim->SetRunState(Animation::Running, 0.0);
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(-1.0));
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(0.0));
+ EXPECT_EQ(0, anim->TrimTimeToCurrentIteration(1.0));
+}
+
+TEST(AnimationTest, IsFinishedAtZeroIterations) {
+ scoped_ptr<Animation> anim(CreateAnimation(0));
+ anim->SetRunState(Animation::Running, 0.0);
+ EXPECT_FALSE(anim->IsFinishedAt(-1.0));
+ EXPECT_TRUE(anim->IsFinishedAt(0.0));
+ EXPECT_TRUE(anim->IsFinishedAt(1.0));
+}
+
+TEST(AnimationTest, IsFinishedAtOneIteration) {
+ scoped_ptr<Animation> anim(CreateAnimation(1));
+ anim->SetRunState(Animation::Running, 0.0);
+ EXPECT_FALSE(anim->IsFinishedAt(-1.0));
+ EXPECT_FALSE(anim->IsFinishedAt(0.0));
+ EXPECT_TRUE(anim->IsFinishedAt(1.0));
+ EXPECT_TRUE(anim->IsFinishedAt(2.0));
+}
+
+TEST(AnimationTest, IsFinishedAtInfiniteIterations) {
+ scoped_ptr<Animation> anim(CreateAnimation(-1));
+ anim->SetRunState(Animation::Running, 0.0);
+ EXPECT_FALSE(anim->IsFinishedAt(0.0));
+ EXPECT_FALSE(anim->IsFinishedAt(0.5));
+ EXPECT_FALSE(anim->IsFinishedAt(1.0));
+ EXPECT_FALSE(anim->IsFinishedAt(1.5));
+}
+
+TEST(AnimationTest, IsFinishedAtNotRunning) {
+ scoped_ptr<Animation> anim(CreateAnimation(0));
+ anim->SetRunState(Animation::Running, 0.0);
+ EXPECT_TRUE(anim->IsFinishedAt(0.0));
+ anim->SetRunState(Animation::Paused, 0.0);
+ EXPECT_FALSE(anim->IsFinishedAt(0.0));
+ anim->SetRunState(Animation::WaitingForNextTick, 0.0);
+ EXPECT_FALSE(anim->IsFinishedAt(0.0));
+ anim->SetRunState(Animation::WaitingForTargetAvailability, 0.0);
+ EXPECT_FALSE(anim->IsFinishedAt(0.0));
+ anim->SetRunState(Animation::WaitingForStartTime, 0.0);
+ EXPECT_FALSE(anim->IsFinishedAt(0.0));
+ anim->SetRunState(Animation::Finished, 0.0);
+ EXPECT_TRUE(anim->IsFinishedAt(0.0));
+ anim->SetRunState(Animation::Aborted, 0.0);
+ EXPECT_TRUE(anim->IsFinishedAt(0.0));
+}
+
+TEST(AnimationTest, IsFinished) {
+ scoped_ptr<Animation> anim(CreateAnimation(1));
+ anim->SetRunState(Animation::Running, 0.0);
+ EXPECT_FALSE(anim->is_finished());
+ anim->SetRunState(Animation::Paused, 0.0);
+ EXPECT_FALSE(anim->is_finished());
+ anim->SetRunState(Animation::WaitingForNextTick, 0.0);
+ EXPECT_FALSE(anim->is_finished());
+ anim->SetRunState(Animation::WaitingForTargetAvailability, 0.0);
+ EXPECT_FALSE(anim->is_finished());
+ anim->SetRunState(Animation::WaitingForStartTime, 0.0);
+ EXPECT_FALSE(anim->is_finished());
+ anim->SetRunState(Animation::Finished, 0.0);
+ EXPECT_TRUE(anim->is_finished());
+ anim->SetRunState(Animation::Aborted, 0.0);
+ EXPECT_TRUE(anim->is_finished());
+}
+
+TEST(AnimationTest, IsFinishedNeedsSynchronizedStartTime) {
+ scoped_ptr<Animation> anim(CreateAnimation(1));
+ anim->SetRunState(Animation::Running, 2.0);
+ EXPECT_FALSE(anim->is_finished());
+ anim->SetRunState(Animation::Paused, 2.0);
+ EXPECT_FALSE(anim->is_finished());
+ anim->SetRunState(Animation::WaitingForNextTick, 2.0);
+ EXPECT_FALSE(anim->is_finished());
+ anim->SetRunState(Animation::WaitingForTargetAvailability, 2.0);
+ EXPECT_FALSE(anim->is_finished());
+ anim->SetRunState(Animation::WaitingForStartTime, 2.0);
+ EXPECT_FALSE(anim->is_finished());
+ anim->SetRunState(Animation::Finished, 0.0);
+ EXPECT_TRUE(anim->is_finished());
+ anim->SetRunState(Animation::Aborted, 0.0);
+ EXPECT_TRUE(anim->is_finished());
+}
+
+TEST(AnimationTest, RunStateChangesIgnoredWhileSuspended) {
+ scoped_ptr<Animation> anim(CreateAnimation(1));
+ anim->Suspend(0);
+ EXPECT_EQ(Animation::Paused, anim->run_state());
+ anim->SetRunState(Animation::Running, 0.0);
+ EXPECT_EQ(Animation::Paused, anim->run_state());
+ anim->Resume(0);
+ anim->SetRunState(Animation::Running, 0.0);
+ EXPECT_EQ(Animation::Running, anim->run_state());
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/animation/keyframed_animation_curve.cc b/chromium/cc/animation/keyframed_animation_curve.cc
new file mode 100644
index 00000000000..c016c91335b
--- /dev/null
+++ b/chromium/cc/animation/keyframed_animation_curve.cc
@@ -0,0 +1,270 @@
+// 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 "cc/animation/keyframed_animation_curve.h"
+
+namespace cc {
+
+namespace {
+
+template <class Keyframe>
+void InsertKeyframe(scoped_ptr<Keyframe> keyframe,
+ ScopedPtrVector<Keyframe>& keyframes) {
+ // Usually, the keyframes will be added in order, so this loop would be
+ // unnecessary and we should skip it if possible.
+ if (!keyframes.empty() && keyframe->Time() < keyframes.back()->Time()) {
+ for (size_t i = 0; i < keyframes.size(); ++i) {
+ if (keyframe->Time() < keyframes[i]->Time()) {
+ keyframes.insert(keyframes.begin() + i, keyframe.Pass());
+ return;
+ }
+ }
+ }
+
+ keyframes.push_back(keyframe.Pass());
+}
+
+scoped_ptr<TimingFunction> CloneTimingFunction(
+ const TimingFunction* timing_function) {
+ DCHECK(timing_function);
+ scoped_ptr<AnimationCurve> curve(timing_function->Clone());
+ return scoped_ptr<TimingFunction>(
+ static_cast<TimingFunction*>(curve.release()));
+}
+
+} // namespace
+
+Keyframe::Keyframe(double time, scoped_ptr<TimingFunction> timing_function)
+ : time_(time),
+ timing_function_(timing_function.Pass()) {}
+
+Keyframe::~Keyframe() {}
+
+double Keyframe::Time() const {
+ return time_;
+}
+
+scoped_ptr<FloatKeyframe> FloatKeyframe::Create(
+ double time,
+ float value,
+ scoped_ptr<TimingFunction> timing_function) {
+ return make_scoped_ptr(
+ new FloatKeyframe(time, value, timing_function.Pass()));
+}
+
+FloatKeyframe::FloatKeyframe(double time,
+ float value,
+ scoped_ptr<TimingFunction> timing_function)
+ : Keyframe(time, timing_function.Pass()),
+ value_(value) {}
+
+FloatKeyframe::~FloatKeyframe() {}
+
+float FloatKeyframe::Value() const {
+ return value_;
+}
+
+scoped_ptr<FloatKeyframe> FloatKeyframe::Clone() const {
+ scoped_ptr<TimingFunction> func;
+ if (timing_function())
+ func = CloneTimingFunction(timing_function());
+ return FloatKeyframe::Create(Time(), Value(), func.Pass());
+}
+
+scoped_ptr<TransformKeyframe> TransformKeyframe::Create(
+ double time,
+ const TransformOperations& value,
+ scoped_ptr<TimingFunction> timing_function) {
+ return make_scoped_ptr(
+ new TransformKeyframe(time, value, timing_function.Pass()));
+}
+
+TransformKeyframe::TransformKeyframe(double time,
+ const TransformOperations& value,
+ scoped_ptr<TimingFunction> timing_function)
+ : Keyframe(time, timing_function.Pass()),
+ value_(value) {}
+
+TransformKeyframe::~TransformKeyframe() {}
+
+const TransformOperations& TransformKeyframe::Value() const {
+ return value_;
+}
+
+scoped_ptr<TransformKeyframe> TransformKeyframe::Clone() const {
+ scoped_ptr<TimingFunction> func;
+ if (timing_function())
+ func = CloneTimingFunction(timing_function());
+ return TransformKeyframe::Create(Time(), Value(), func.Pass());
+}
+
+scoped_ptr<FilterKeyframe> FilterKeyframe::Create(
+ double time,
+ const FilterOperations& value,
+ scoped_ptr<TimingFunction> timing_function) {
+ return make_scoped_ptr(
+ new FilterKeyframe(time, value, timing_function.Pass()));
+}
+
+FilterKeyframe::FilterKeyframe(double time,
+ const FilterOperations& value,
+ scoped_ptr<TimingFunction> timing_function)
+ : Keyframe(time, timing_function.Pass()),
+ value_(value) {}
+
+FilterKeyframe::~FilterKeyframe() {}
+
+const FilterOperations& FilterKeyframe::Value() const {
+ return value_;
+}
+
+scoped_ptr<FilterKeyframe> FilterKeyframe::Clone() const {
+ scoped_ptr<TimingFunction> func;
+ if (timing_function())
+ func = CloneTimingFunction(timing_function());
+ return FilterKeyframe::Create(Time(), Value(), func.Pass());
+}
+
+scoped_ptr<KeyframedFloatAnimationCurve> KeyframedFloatAnimationCurve::
+ Create() {
+ return make_scoped_ptr(new KeyframedFloatAnimationCurve);
+}
+
+KeyframedFloatAnimationCurve::KeyframedFloatAnimationCurve() {}
+
+KeyframedFloatAnimationCurve::~KeyframedFloatAnimationCurve() {}
+
+void KeyframedFloatAnimationCurve::AddKeyframe(
+ scoped_ptr<FloatKeyframe> keyframe) {
+ InsertKeyframe(keyframe.Pass(), keyframes_);
+}
+
+double KeyframedFloatAnimationCurve::Duration() const {
+ return keyframes_.back()->Time() - keyframes_.front()->Time();
+}
+
+scoped_ptr<AnimationCurve> KeyframedFloatAnimationCurve::Clone() const {
+ scoped_ptr<KeyframedFloatAnimationCurve> to_return(
+ KeyframedFloatAnimationCurve::Create());
+ for (size_t i = 0; i < keyframes_.size(); ++i)
+ to_return->AddKeyframe(keyframes_[i]->Clone());
+ return to_return.PassAs<AnimationCurve>();
+}
+
+float KeyframedFloatAnimationCurve::GetValue(double t) const {
+ if (t <= keyframes_.front()->Time())
+ return keyframes_.front()->Value();
+
+ if (t >= keyframes_.back()->Time())
+ return keyframes_.back()->Value();
+
+ size_t i = 0;
+ for (; i < keyframes_.size() - 1; ++i) {
+ if (t < keyframes_[i+1]->Time())
+ break;
+ }
+
+ float progress =
+ static_cast<float>((t - keyframes_[i]->Time()) /
+ (keyframes_[i+1]->Time() - keyframes_[i]->Time()));
+
+ if (keyframes_[i]->timing_function())
+ progress = keyframes_[i]->timing_function()->GetValue(progress);
+
+ return keyframes_[i]->Value() +
+ (keyframes_[i+1]->Value() - keyframes_[i]->Value()) * progress;
+}
+
+scoped_ptr<KeyframedTransformAnimationCurve> KeyframedTransformAnimationCurve::
+ Create() {
+ return make_scoped_ptr(new KeyframedTransformAnimationCurve);
+}
+
+KeyframedTransformAnimationCurve::KeyframedTransformAnimationCurve() {}
+
+KeyframedTransformAnimationCurve::~KeyframedTransformAnimationCurve() {}
+
+void KeyframedTransformAnimationCurve::AddKeyframe(
+ scoped_ptr<TransformKeyframe> keyframe) {
+ InsertKeyframe(keyframe.Pass(), keyframes_);
+}
+
+double KeyframedTransformAnimationCurve::Duration() const {
+ return keyframes_.back()->Time() - keyframes_.front()->Time();
+}
+
+scoped_ptr<AnimationCurve> KeyframedTransformAnimationCurve::Clone() const {
+ scoped_ptr<KeyframedTransformAnimationCurve> to_return(
+ KeyframedTransformAnimationCurve::Create());
+ for (size_t i = 0; i < keyframes_.size(); ++i)
+ to_return->AddKeyframe(keyframes_[i]->Clone());
+ return to_return.PassAs<AnimationCurve>();
+}
+
+// Assumes that (*keyframes).front()->Time() < t < (*keyframes).back()-Time().
+template<typename ValueType, typename KeyframeType>
+static ValueType GetCurveValue(const ScopedPtrVector<KeyframeType>* keyframes,
+ double t) {
+ size_t i = 0;
+ for (; i < keyframes->size() - 1; ++i) {
+ if (t < (*keyframes)[i+1]->Time())
+ break;
+ }
+
+ double progress = (t - (*keyframes)[i]->Time()) /
+ ((*keyframes)[i+1]->Time() - (*keyframes)[i]->Time());
+
+ if ((*keyframes)[i]->timing_function())
+ progress = (*keyframes)[i]->timing_function()->GetValue(progress);
+
+ return (*keyframes)[i+1]->Value().Blend((*keyframes)[i]->Value(), progress);
+}
+
+gfx::Transform KeyframedTransformAnimationCurve::GetValue(double t) const {
+ if (t <= keyframes_.front()->Time())
+ return keyframes_.front()->Value().Apply();
+
+ if (t >= keyframes_.back()->Time())
+ return keyframes_.back()->Value().Apply();
+
+ return GetCurveValue<gfx::Transform, TransformKeyframe>(&keyframes_, t);
+}
+
+scoped_ptr<KeyframedFilterAnimationCurve> KeyframedFilterAnimationCurve::
+ Create() {
+ return make_scoped_ptr(new KeyframedFilterAnimationCurve);
+}
+
+KeyframedFilterAnimationCurve::KeyframedFilterAnimationCurve() {}
+
+KeyframedFilterAnimationCurve::~KeyframedFilterAnimationCurve() {}
+
+void KeyframedFilterAnimationCurve::AddKeyframe(
+ scoped_ptr<FilterKeyframe> keyframe) {
+ InsertKeyframe(keyframe.Pass(), keyframes_);
+}
+
+double KeyframedFilterAnimationCurve::Duration() const {
+ return keyframes_.back()->Time() - keyframes_.front()->Time();
+}
+
+scoped_ptr<AnimationCurve> KeyframedFilterAnimationCurve::Clone() const {
+ scoped_ptr<KeyframedFilterAnimationCurve> to_return(
+ KeyframedFilterAnimationCurve::Create());
+ for (size_t i = 0; i < keyframes_.size(); ++i)
+ to_return->AddKeyframe(keyframes_[i]->Clone());
+ return to_return.PassAs<AnimationCurve>();
+}
+
+FilterOperations KeyframedFilterAnimationCurve::GetValue(double t) const {
+ if (t <= keyframes_.front()->Time())
+ return keyframes_.front()->Value();
+
+ if (t >= keyframes_.back()->Time())
+ return keyframes_.back()->Value();
+
+ return GetCurveValue<FilterOperations, FilterKeyframe>(&keyframes_, t);
+}
+
+} // namespace cc
diff --git a/chromium/cc/animation/keyframed_animation_curve.h b/chromium/cc/animation/keyframed_animation_curve.h
new file mode 100644
index 00000000000..9dda773caec
--- /dev/null
+++ b/chromium/cc/animation/keyframed_animation_curve.h
@@ -0,0 +1,178 @@
+// 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.
+
+#ifndef CC_ANIMATION_KEYFRAMED_ANIMATION_CURVE_H_
+#define CC_ANIMATION_KEYFRAMED_ANIMATION_CURVE_H_
+
+#include "cc/animation/animation_curve.h"
+#include "cc/animation/timing_function.h"
+#include "cc/animation/transform_operations.h"
+#include "cc/base/cc_export.h"
+#include "cc/base/scoped_ptr_vector.h"
+
+namespace cc {
+
+class CC_EXPORT Keyframe {
+ public:
+ double Time() const;
+ const TimingFunction* timing_function() const {
+ return timing_function_.get();
+ }
+
+ protected:
+ Keyframe(double time, scoped_ptr<TimingFunction> timing_function);
+ virtual ~Keyframe();
+
+ private:
+ double time_;
+ scoped_ptr<TimingFunction> timing_function_;
+
+ DISALLOW_COPY_AND_ASSIGN(Keyframe);
+};
+
+class CC_EXPORT FloatKeyframe : public Keyframe {
+ public:
+ static scoped_ptr<FloatKeyframe> Create(
+ double time,
+ float value,
+ scoped_ptr<TimingFunction> timing_function);
+ virtual ~FloatKeyframe();
+
+ float Value() const;
+
+ scoped_ptr<FloatKeyframe> Clone() const;
+
+ private:
+ FloatKeyframe(double time,
+ float value,
+ scoped_ptr<TimingFunction> timing_function);
+
+ float value_;
+};
+
+class CC_EXPORT TransformKeyframe : public Keyframe {
+ public:
+ static scoped_ptr<TransformKeyframe> Create(
+ double time,
+ const TransformOperations& value,
+ scoped_ptr<TimingFunction> timing_function);
+ virtual ~TransformKeyframe();
+
+ const TransformOperations& Value() const;
+
+ scoped_ptr<TransformKeyframe> Clone() const;
+
+ private:
+ TransformKeyframe(
+ double time,
+ const TransformOperations& value,
+ scoped_ptr<TimingFunction> timing_function);
+
+ TransformOperations value_;
+};
+
+class CC_EXPORT FilterKeyframe : public Keyframe {
+ public:
+ static scoped_ptr<FilterKeyframe> Create(
+ double time,
+ const FilterOperations& value,
+ scoped_ptr<TimingFunction> timing_function);
+ virtual ~FilterKeyframe();
+
+ const FilterOperations& Value() const;
+
+ scoped_ptr<FilterKeyframe> Clone() const;
+
+ private:
+ FilterKeyframe(
+ double time,
+ const FilterOperations& value,
+ scoped_ptr<TimingFunction> timing_function);
+
+ FilterOperations value_;
+};
+
+class CC_EXPORT KeyframedFloatAnimationCurve : public FloatAnimationCurve {
+ public:
+ // It is required that the keyframes be sorted by time.
+ static scoped_ptr<KeyframedFloatAnimationCurve> Create();
+
+ virtual ~KeyframedFloatAnimationCurve();
+
+ void AddKeyframe(scoped_ptr<FloatKeyframe> keyframe);
+
+ // AnimationCurve implementation
+ virtual double Duration() const OVERRIDE;
+ virtual scoped_ptr<AnimationCurve> Clone() const OVERRIDE;
+
+ // FloatAnimationCurve implementation
+ virtual float GetValue(double t) const OVERRIDE;
+
+ private:
+ KeyframedFloatAnimationCurve();
+
+ // Always sorted in order of increasing time. No two keyframes have the
+ // same time.
+ ScopedPtrVector<FloatKeyframe> keyframes_;
+
+ DISALLOW_COPY_AND_ASSIGN(KeyframedFloatAnimationCurve);
+};
+
+class CC_EXPORT KeyframedTransformAnimationCurve
+ : public TransformAnimationCurve {
+ public:
+ // It is required that the keyframes be sorted by time.
+ static scoped_ptr<KeyframedTransformAnimationCurve> Create();
+
+ virtual ~KeyframedTransformAnimationCurve();
+
+ void AddKeyframe(scoped_ptr<TransformKeyframe> keyframe);
+
+ // AnimationCurve implementation
+ virtual double Duration() const OVERRIDE;
+ virtual scoped_ptr<AnimationCurve> Clone() const OVERRIDE;
+
+ // TransformAnimationCurve implementation
+ virtual gfx::Transform GetValue(double t) const OVERRIDE;
+
+ private:
+ KeyframedTransformAnimationCurve();
+
+ // Always sorted in order of increasing time. No two keyframes have the
+ // same time.
+ ScopedPtrVector<TransformKeyframe> keyframes_;
+
+ DISALLOW_COPY_AND_ASSIGN(KeyframedTransformAnimationCurve);
+};
+
+class CC_EXPORT KeyframedFilterAnimationCurve
+ : public FilterAnimationCurve {
+ public:
+ // It is required that the keyframes be sorted by time.
+ static scoped_ptr<KeyframedFilterAnimationCurve> Create();
+
+ virtual ~KeyframedFilterAnimationCurve();
+
+ void AddKeyframe(scoped_ptr<FilterKeyframe> keyframe);
+
+ // AnimationCurve implementation
+ virtual double Duration() const OVERRIDE;
+ virtual scoped_ptr<AnimationCurve> Clone() const OVERRIDE;
+
+ // FilterAnimationCurve implementation
+ virtual FilterOperations GetValue(double t) const OVERRIDE;
+
+ private:
+ KeyframedFilterAnimationCurve();
+
+ // Always sorted in order of increasing time. No two keyframes have the
+ // same time.
+ ScopedPtrVector<FilterKeyframe> keyframes_;
+
+ DISALLOW_COPY_AND_ASSIGN(KeyframedFilterAnimationCurve);
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_KEYFRAMED_ANIMATION_CURVE_H_
diff --git a/chromium/cc/animation/keyframed_animation_curve_unittest.cc b/chromium/cc/animation/keyframed_animation_curve_unittest.cc
new file mode 100644
index 00000000000..32efc147636
--- /dev/null
+++ b/chromium/cc/animation/keyframed_animation_curve_unittest.cc
@@ -0,0 +1,336 @@
+// 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 "cc/animation/keyframed_animation_curve.h"
+
+#include "cc/animation/transform_operations.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+void ExpectTranslateX(double translate_x, const gfx::Transform& transform) {
+ EXPECT_FLOAT_EQ(translate_x, transform.matrix().getDouble(0, 3));
+}
+
+void ExpectBrightness(double brightness, const FilterOperations& filter) {
+ EXPECT_EQ(1u, filter.size());
+ EXPECT_EQ(FilterOperation::BRIGHTNESS, filter.at(0).type());
+ EXPECT_FLOAT_EQ(brightness, filter.at(0).amount());
+}
+
+// Tests that a float animation with one keyframe works as expected.
+TEST(KeyframedAnimationCurveTest, OneFloatKeyframe) {
+ scoped_ptr<KeyframedFloatAnimationCurve> curve(
+ KeyframedFloatAnimationCurve::Create());
+ curve->AddKeyframe(
+ FloatKeyframe::Create(0.0, 2.f, scoped_ptr<TimingFunction>()));
+ EXPECT_FLOAT_EQ(2.f, curve->GetValue(-1.f));
+ EXPECT_FLOAT_EQ(2.f, curve->GetValue(0.f));
+ EXPECT_FLOAT_EQ(2.f, curve->GetValue(0.5f));
+ EXPECT_FLOAT_EQ(2.f, curve->GetValue(1.f));
+ EXPECT_FLOAT_EQ(2.f, curve->GetValue(2.f));
+}
+
+// Tests that a float animation with two keyframes works as expected.
+TEST(KeyframedAnimationCurveTest, TwoFloatKeyframe) {
+ scoped_ptr<KeyframedFloatAnimationCurve> curve(
+ KeyframedFloatAnimationCurve::Create());
+ curve->AddKeyframe(
+ FloatKeyframe::Create(0.0, 2.f, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(
+ FloatKeyframe::Create(1.0, 4.f, scoped_ptr<TimingFunction>()));
+ EXPECT_FLOAT_EQ(2.f, curve->GetValue(-1.f));
+ EXPECT_FLOAT_EQ(2.f, curve->GetValue(0.f));
+ EXPECT_FLOAT_EQ(3.f, curve->GetValue(0.5f));
+ EXPECT_FLOAT_EQ(4.f, curve->GetValue(1.f));
+ EXPECT_FLOAT_EQ(4.f, curve->GetValue(2.f));
+}
+
+// Tests that a float animation with three keyframes works as expected.
+TEST(KeyframedAnimationCurveTest, ThreeFloatKeyframe) {
+ scoped_ptr<KeyframedFloatAnimationCurve> curve(
+ KeyframedFloatAnimationCurve::Create());
+ curve->AddKeyframe(
+ FloatKeyframe::Create(0.0, 2.f, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(
+ FloatKeyframe::Create(1.0, 4.f, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(
+ FloatKeyframe::Create(2.0, 8.f, scoped_ptr<TimingFunction>()));
+ EXPECT_FLOAT_EQ(2.f, curve->GetValue(-1.f));
+ EXPECT_FLOAT_EQ(2.f, curve->GetValue(0.f));
+ EXPECT_FLOAT_EQ(3.f, curve->GetValue(0.5f));
+ EXPECT_FLOAT_EQ(4.f, curve->GetValue(1.f));
+ EXPECT_FLOAT_EQ(6.f, curve->GetValue(1.5f));
+ EXPECT_FLOAT_EQ(8.f, curve->GetValue(2.f));
+ EXPECT_FLOAT_EQ(8.f, curve->GetValue(3.f));
+}
+
+// Tests that a float animation with multiple keys at a given time works sanely.
+TEST(KeyframedAnimationCurveTest, RepeatedFloatKeyTimes) {
+ scoped_ptr<KeyframedFloatAnimationCurve> curve(
+ KeyframedFloatAnimationCurve::Create());
+ curve->AddKeyframe(
+ FloatKeyframe::Create(0.0, 4.f, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(
+ FloatKeyframe::Create(1.0, 4.f, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(
+ FloatKeyframe::Create(1.0, 6.f, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(
+ FloatKeyframe::Create(2.0, 6.f, scoped_ptr<TimingFunction>()));
+
+ EXPECT_FLOAT_EQ(4.f, curve->GetValue(-1.f));
+ EXPECT_FLOAT_EQ(4.f, curve->GetValue(0.f));
+ EXPECT_FLOAT_EQ(4.f, curve->GetValue(0.5f));
+
+ // There is a discontinuity at 1. Any value between 4 and 6 is valid.
+ float value = curve->GetValue(1.f);
+ EXPECT_TRUE(value >= 4 && value <= 6);
+
+ EXPECT_FLOAT_EQ(6.f, curve->GetValue(1.5f));
+ EXPECT_FLOAT_EQ(6.f, curve->GetValue(2.f));
+ EXPECT_FLOAT_EQ(6.f, curve->GetValue(3.f));
+}
+
+// Tests that a transform animation with one keyframe works as expected.
+TEST(KeyframedAnimationCurveTest, OneTransformKeyframe) {
+ scoped_ptr<KeyframedTransformAnimationCurve> curve(
+ KeyframedTransformAnimationCurve::Create());
+ TransformOperations operations;
+ operations.AppendTranslate(2.f, 0.f, 0.f);
+ curve->AddKeyframe(
+ TransformKeyframe::Create(0.f, operations, scoped_ptr<TimingFunction>()));
+
+ ExpectTranslateX(2.f, curve->GetValue(-1.f));
+ ExpectTranslateX(2.f, curve->GetValue(0.f));
+ ExpectTranslateX(2.f, curve->GetValue(0.5f));
+ ExpectTranslateX(2.f, curve->GetValue(1.f));
+ ExpectTranslateX(2.f, curve->GetValue(2.f));
+}
+
+// Tests that a transform animation with two keyframes works as expected.
+TEST(KeyframedAnimationCurveTest, TwoTransformKeyframe) {
+ scoped_ptr<KeyframedTransformAnimationCurve> curve(
+ KeyframedTransformAnimationCurve::Create());
+ TransformOperations operations1;
+ operations1.AppendTranslate(2.f, 0.f, 0.f);
+ TransformOperations operations2;
+ operations2.AppendTranslate(4.f, 0.f, 0.f);
+
+ curve->AddKeyframe(TransformKeyframe::Create(
+ 0.f, operations1, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(TransformKeyframe::Create(
+ 1.f, operations2, scoped_ptr<TimingFunction>()));
+ ExpectTranslateX(2.f, curve->GetValue(-1.f));
+ ExpectTranslateX(2.f, curve->GetValue(0.f));
+ ExpectTranslateX(3.f, curve->GetValue(0.5f));
+ ExpectTranslateX(4.f, curve->GetValue(1.f));
+ ExpectTranslateX(4.f, curve->GetValue(2.f));
+}
+
+// Tests that a transform animation with three keyframes works as expected.
+TEST(KeyframedAnimationCurveTest, ThreeTransformKeyframe) {
+ scoped_ptr<KeyframedTransformAnimationCurve> curve(
+ KeyframedTransformAnimationCurve::Create());
+ TransformOperations operations1;
+ operations1.AppendTranslate(2.f, 0.f, 0.f);
+ TransformOperations operations2;
+ operations2.AppendTranslate(4.f, 0.f, 0.f);
+ TransformOperations operations3;
+ operations3.AppendTranslate(8.f, 0.f, 0.f);
+ curve->AddKeyframe(TransformKeyframe::Create(
+ 0.f, operations1, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(TransformKeyframe::Create(
+ 1.f, operations2, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(TransformKeyframe::Create(
+ 2.f, operations3, scoped_ptr<TimingFunction>()));
+ ExpectTranslateX(2.f, curve->GetValue(-1.f));
+ ExpectTranslateX(2.f, curve->GetValue(0.f));
+ ExpectTranslateX(3.f, curve->GetValue(0.5f));
+ ExpectTranslateX(4.f, curve->GetValue(1.f));
+ ExpectTranslateX(6.f, curve->GetValue(1.5f));
+ ExpectTranslateX(8.f, curve->GetValue(2.f));
+ ExpectTranslateX(8.f, curve->GetValue(3.f));
+}
+
+// Tests that a transform animation with multiple keys at a given time works
+// sanely.
+TEST(KeyframedAnimationCurveTest, RepeatedTransformKeyTimes) {
+ scoped_ptr<KeyframedTransformAnimationCurve> curve(
+ KeyframedTransformAnimationCurve::Create());
+ // A step function.
+ TransformOperations operations1;
+ operations1.AppendTranslate(4.f, 0.f, 0.f);
+ TransformOperations operations2;
+ operations2.AppendTranslate(4.f, 0.f, 0.f);
+ TransformOperations operations3;
+ operations3.AppendTranslate(6.f, 0.f, 0.f);
+ TransformOperations operations4;
+ operations4.AppendTranslate(6.f, 0.f, 0.f);
+ curve->AddKeyframe(TransformKeyframe::Create(
+ 0.f, operations1, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(TransformKeyframe::Create(
+ 1.f, operations2, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(TransformKeyframe::Create(
+ 1.f, operations3, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(TransformKeyframe::Create(
+ 2.f, operations4, scoped_ptr<TimingFunction>()));
+
+ ExpectTranslateX(4.f, curve->GetValue(-1.f));
+ ExpectTranslateX(4.f, curve->GetValue(0.f));
+ ExpectTranslateX(4.f, curve->GetValue(0.5f));
+
+ // There is a discontinuity at 1. Any value between 4 and 6 is valid.
+ gfx::Transform value = curve->GetValue(1.f);
+ EXPECT_GE(value.matrix().getDouble(0.f, 3.f), 4);
+ EXPECT_LE(value.matrix().getDouble(0.f, 3.f), 6);
+
+ ExpectTranslateX(6.f, curve->GetValue(1.5f));
+ ExpectTranslateX(6.f, curve->GetValue(2.f));
+ ExpectTranslateX(6.f, curve->GetValue(3.f));
+}
+
+// Tests that a filter animation with one keyframe works as expected.
+TEST(KeyframedAnimationCurveTest, OneFilterKeyframe) {
+ scoped_ptr<KeyframedFilterAnimationCurve> curve(
+ KeyframedFilterAnimationCurve::Create());
+ FilterOperations operations;
+ operations.Append(FilterOperation::CreateBrightnessFilter(2.f));
+ curve->AddKeyframe(
+ FilterKeyframe::Create(0.f, operations, scoped_ptr<TimingFunction>()));
+
+ ExpectBrightness(2.f, curve->GetValue(-1.f));
+ ExpectBrightness(2.f, curve->GetValue(0.f));
+ ExpectBrightness(2.f, curve->GetValue(0.5f));
+ ExpectBrightness(2.f, curve->GetValue(1.f));
+ ExpectBrightness(2.f, curve->GetValue(2.f));
+}
+
+// Tests that a filter animation with two keyframes works as expected.
+TEST(KeyframedAnimationCurveTest, TwoFilterKeyframe) {
+ scoped_ptr<KeyframedFilterAnimationCurve> curve(
+ KeyframedFilterAnimationCurve::Create());
+ FilterOperations operations1;
+ operations1.Append(FilterOperation::CreateBrightnessFilter(2.f));
+ FilterOperations operations2;
+ operations2.Append(FilterOperation::CreateBrightnessFilter(4.f));
+
+ curve->AddKeyframe(FilterKeyframe::Create(
+ 0.f, operations1, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(FilterKeyframe::Create(
+ 1.f, operations2, scoped_ptr<TimingFunction>()));
+ ExpectBrightness(2.f, curve->GetValue(-1.f));
+ ExpectBrightness(2.f, curve->GetValue(0.f));
+ ExpectBrightness(3.f, curve->GetValue(0.5f));
+ ExpectBrightness(4.f, curve->GetValue(1.f));
+ ExpectBrightness(4.f, curve->GetValue(2.f));
+}
+
+// Tests that a filter animation with three keyframes works as expected.
+TEST(KeyframedAnimationCurveTest, ThreeFilterKeyframe) {
+ scoped_ptr<KeyframedFilterAnimationCurve> curve(
+ KeyframedFilterAnimationCurve::Create());
+ FilterOperations operations1;
+ operations1.Append(FilterOperation::CreateBrightnessFilter(2.f));
+ FilterOperations operations2;
+ operations2.Append(FilterOperation::CreateBrightnessFilter(4.f));
+ FilterOperations operations3;
+ operations3.Append(FilterOperation::CreateBrightnessFilter(8.f));
+ curve->AddKeyframe(FilterKeyframe::Create(
+ 0.f, operations1, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(FilterKeyframe::Create(
+ 1.f, operations2, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(FilterKeyframe::Create(
+ 2.f, operations3, scoped_ptr<TimingFunction>()));
+ ExpectBrightness(2.f, curve->GetValue(-1.f));
+ ExpectBrightness(2.f, curve->GetValue(0.f));
+ ExpectBrightness(3.f, curve->GetValue(0.5f));
+ ExpectBrightness(4.f, curve->GetValue(1.f));
+ ExpectBrightness(6.f, curve->GetValue(1.5f));
+ ExpectBrightness(8.f, curve->GetValue(2.f));
+ ExpectBrightness(8.f, curve->GetValue(3.f));
+}
+
+// Tests that a filter animation with multiple keys at a given time works
+// sanely.
+TEST(KeyframedAnimationCurveTest, RepeatedFilterKeyTimes) {
+ scoped_ptr<KeyframedFilterAnimationCurve> curve(
+ KeyframedFilterAnimationCurve::Create());
+ // A step function.
+ FilterOperations operations1;
+ operations1.Append(FilterOperation::CreateBrightnessFilter(4.f));
+ FilterOperations operations2;
+ operations2.Append(FilterOperation::CreateBrightnessFilter(4.f));
+ FilterOperations operations3;
+ operations3.Append(FilterOperation::CreateBrightnessFilter(6.f));
+ FilterOperations operations4;
+ operations4.Append(FilterOperation::CreateBrightnessFilter(6.f));
+ curve->AddKeyframe(FilterKeyframe::Create(
+ 0.f, operations1, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(FilterKeyframe::Create(
+ 1.f, operations2, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(FilterKeyframe::Create(
+ 1.f, operations3, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(FilterKeyframe::Create(
+ 2.f, operations4, scoped_ptr<TimingFunction>()));
+
+ ExpectBrightness(4.f, curve->GetValue(-1.f));
+ ExpectBrightness(4.f, curve->GetValue(0.f));
+ ExpectBrightness(4.f, curve->GetValue(0.5f));
+
+ // There is a discontinuity at 1. Any value between 4 and 6 is valid.
+ FilterOperations value = curve->GetValue(1.f);
+ EXPECT_EQ(1u, value.size());
+ EXPECT_EQ(FilterOperation::BRIGHTNESS, value.at(0).type());
+ EXPECT_GE(value.at(0).amount(), 4);
+ EXPECT_LE(value.at(0).amount(), 6);
+
+ ExpectBrightness(6.f, curve->GetValue(1.5f));
+ ExpectBrightness(6.f, curve->GetValue(2.f));
+ ExpectBrightness(6.f, curve->GetValue(3.f));
+}
+
+// Tests that the keyframes may be added out of order.
+TEST(KeyframedAnimationCurveTest, UnsortedKeyframes) {
+ scoped_ptr<KeyframedFloatAnimationCurve> curve(
+ KeyframedFloatAnimationCurve::Create());
+ curve->AddKeyframe(
+ FloatKeyframe::Create(2.0, 8.f, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(
+ FloatKeyframe::Create(0.0, 2.f, scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(
+ FloatKeyframe::Create(1.0, 4.f, scoped_ptr<TimingFunction>()));
+ EXPECT_FLOAT_EQ(2.f, curve->GetValue(-1.f));
+ EXPECT_FLOAT_EQ(2.f, curve->GetValue(0.f));
+ EXPECT_FLOAT_EQ(3.f, curve->GetValue(0.5f));
+ EXPECT_FLOAT_EQ(4.f, curve->GetValue(1.f));
+ EXPECT_FLOAT_EQ(6.f, curve->GetValue(1.5f));
+ EXPECT_FLOAT_EQ(8.f, curve->GetValue(2.f));
+ EXPECT_FLOAT_EQ(8.f, curve->GetValue(3.f));
+}
+
+// Tests that a cubic bezier timing function works as expected.
+TEST(KeyframedAnimationCurveTest, CubicBezierTimingFunction) {
+ scoped_ptr<KeyframedFloatAnimationCurve> curve(
+ KeyframedFloatAnimationCurve::Create());
+ curve->AddKeyframe(FloatKeyframe::Create(
+ 0.0,
+ 0.f,
+ CubicBezierTimingFunction::Create(0.25f, 0.f, 0.75f, 1.f)
+ .PassAs<TimingFunction>()));
+ curve->AddKeyframe(
+ FloatKeyframe::Create(1.0, 1.f, scoped_ptr<TimingFunction>()));
+
+ EXPECT_FLOAT_EQ(0.f, curve->GetValue(0.f));
+ EXPECT_LT(0.f, curve->GetValue(0.25f));
+ EXPECT_GT(0.25f, curve->GetValue(0.25f));
+ EXPECT_NEAR(curve->GetValue(0.5f), 0.5f, 0.00015f);
+ EXPECT_LT(0.75f, curve->GetValue(0.75f));
+ EXPECT_GT(1.f, curve->GetValue(0.75f));
+ EXPECT_FLOAT_EQ(1.f, curve->GetValue(1.f));
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/animation/layer_animation_controller.cc b/chromium/cc/animation/layer_animation_controller.cc
new file mode 100644
index 00000000000..5823b7919d2
--- /dev/null
+++ b/chromium/cc/animation/layer_animation_controller.cc
@@ -0,0 +1,729 @@
+// 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 "cc/animation/layer_animation_controller.h"
+
+#include <algorithm>
+
+#include "cc/animation/animation.h"
+#include "cc/animation/animation_delegate.h"
+#include "cc/animation/animation_registrar.h"
+#include "cc/animation/keyframed_animation_curve.h"
+#include "cc/animation/layer_animation_value_observer.h"
+#include "cc/base/scoped_ptr_algorithm.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+
+LayerAnimationController::LayerAnimationController(int id)
+ : force_sync_(false),
+ registrar_(0),
+ id_(id),
+ is_active_(false),
+ last_tick_time_(0),
+ layer_animation_delegate_(NULL) {}
+
+LayerAnimationController::~LayerAnimationController() {
+ if (registrar_)
+ registrar_->UnregisterAnimationController(this);
+}
+
+scoped_refptr<LayerAnimationController> LayerAnimationController::Create(
+ int id) {
+ return make_scoped_refptr(new LayerAnimationController(id));
+}
+
+void LayerAnimationController::PauseAnimation(int animation_id,
+ double time_offset) {
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (active_animations_[i]->id() == animation_id) {
+ active_animations_[i]->SetRunState(
+ Animation::Paused, time_offset + active_animations_[i]->start_time());
+ }
+ }
+}
+
+struct HasAnimationId {
+ explicit HasAnimationId(int id) : id_(id) {}
+ bool operator()(Animation* animation) const {
+ return animation->id() == id_;
+ }
+
+ private:
+ int id_;
+};
+
+void LayerAnimationController::RemoveAnimation(int animation_id) {
+ ScopedPtrVector<Animation>& animations = active_animations_;
+ animations.erase(cc::remove_if(&animations,
+ animations.begin(),
+ animations.end(),
+ HasAnimationId(animation_id)),
+ animations.end());
+ UpdateActivation(NormalActivation);
+}
+
+struct HasAnimationIdAndProperty {
+ HasAnimationIdAndProperty(int id, Animation::TargetProperty target_property)
+ : id_(id), target_property_(target_property) {}
+ bool operator()(Animation* animation) const {
+ return animation->id() == id_ &&
+ animation->target_property() == target_property_;
+ }
+
+ private:
+ int id_;
+ Animation::TargetProperty target_property_;
+};
+
+void LayerAnimationController::RemoveAnimation(
+ int animation_id,
+ Animation::TargetProperty target_property) {
+ ScopedPtrVector<Animation>& animations = active_animations_;
+ animations.erase(cc::remove_if(&animations,
+ animations.begin(),
+ animations.end(),
+ HasAnimationIdAndProperty(animation_id,
+ target_property)),
+ animations.end());
+ UpdateActivation(NormalActivation);
+}
+
+// According to render layer backing, these are for testing only.
+void LayerAnimationController::SuspendAnimations(double monotonic_time) {
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (!active_animations_[i]->is_finished())
+ active_animations_[i]->SetRunState(Animation::Paused, monotonic_time);
+ }
+}
+
+// Looking at GraphicsLayerCA, this appears to be the analog to
+// SuspendAnimations, which is for testing.
+void LayerAnimationController::ResumeAnimations(double monotonic_time) {
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (active_animations_[i]->run_state() == Animation::Paused)
+ active_animations_[i]->SetRunState(Animation::Running, monotonic_time);
+ }
+}
+
+// Ensures that the list of active animations on the main thread and the impl
+// thread are kept in sync.
+void LayerAnimationController::PushAnimationUpdatesTo(
+ LayerAnimationController* controller_impl) {
+ DCHECK(this != controller_impl);
+ if (force_sync_) {
+ ReplaceImplThreadAnimations(controller_impl);
+ force_sync_ = false;
+ } else {
+ PurgeAnimationsMarkedForDeletion();
+ PushNewAnimationsToImplThread(controller_impl);
+
+ // Remove finished impl side animations only after pushing,
+ // and only after the animations are deleted on the main thread
+ // this insures we will never push an animation twice.
+ RemoveAnimationsCompletedOnMainThread(controller_impl);
+
+ PushPropertiesToImplThread(controller_impl);
+ }
+ controller_impl->UpdateActivation(NormalActivation);
+ UpdateActivation(NormalActivation);
+}
+
+void LayerAnimationController::Animate(double monotonic_time) {
+ if (!HasValueObserver())
+ return;
+
+ StartAnimationsWaitingForNextTick(monotonic_time);
+ StartAnimationsWaitingForStartTime(monotonic_time);
+ StartAnimationsWaitingForTargetAvailability(monotonic_time);
+ ResolveConflicts(monotonic_time);
+ TickAnimations(monotonic_time);
+ last_tick_time_ = monotonic_time;
+}
+
+void LayerAnimationController::AccumulatePropertyUpdates(
+ double monotonic_time,
+ AnimationEventsVector* events) {
+ if (!events)
+ return;
+
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ Animation* animation = active_animations_[i];
+ if (!animation->is_impl_only())
+ continue;
+
+ if (animation->target_property() == Animation::Opacity) {
+ AnimationEvent event(AnimationEvent::PropertyUpdate,
+ id_,
+ animation->group(),
+ Animation::Opacity,
+ monotonic_time);
+ event.opacity = animation->curve()->ToFloatAnimationCurve()->GetValue(
+ monotonic_time);
+ event.is_impl_only = true;
+ events->push_back(event);
+ } else if (animation->target_property() == Animation::Transform) {
+ AnimationEvent event(AnimationEvent::PropertyUpdate,
+ id_,
+ animation->group(),
+ Animation::Transform,
+ monotonic_time);
+ event.transform =
+ animation->curve()->ToTransformAnimationCurve()->GetValue(
+ monotonic_time);
+ event.is_impl_only = true;
+ events->push_back(event);
+ }
+ }
+}
+
+void LayerAnimationController::UpdateState(bool start_ready_animations,
+ AnimationEventsVector* events) {
+ if (!HasActiveValueObserver())
+ return;
+
+ if (start_ready_animations)
+ PromoteStartedAnimations(last_tick_time_, events);
+
+ MarkFinishedAnimations(last_tick_time_);
+ MarkAnimationsForDeletion(last_tick_time_, events);
+
+ if (start_ready_animations) {
+ StartAnimationsWaitingForTargetAvailability(last_tick_time_);
+ PromoteStartedAnimations(last_tick_time_, events);
+ }
+
+ AccumulatePropertyUpdates(last_tick_time_, events);
+
+ UpdateActivation(NormalActivation);
+}
+
+void LayerAnimationController::AddAnimation(scoped_ptr<Animation> animation) {
+ active_animations_.push_back(animation.Pass());
+ UpdateActivation(NormalActivation);
+}
+
+Animation* LayerAnimationController::GetAnimation(
+ int group_id,
+ Animation::TargetProperty target_property) const {
+ for (size_t i = 0; i < active_animations_.size(); ++i)
+ if (active_animations_[i]->group() == group_id &&
+ active_animations_[i]->target_property() == target_property)
+ return active_animations_[i];
+ return 0;
+}
+
+Animation* LayerAnimationController::GetAnimation(
+ Animation::TargetProperty target_property) const {
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ size_t index = active_animations_.size() - i - 1;
+ if (active_animations_[index]->target_property() == target_property)
+ return active_animations_[index];
+ }
+ return 0;
+}
+
+bool LayerAnimationController::HasActiveAnimation() const {
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (!active_animations_[i]->is_finished())
+ return true;
+ }
+ return false;
+}
+
+bool LayerAnimationController::IsAnimatingProperty(
+ Animation::TargetProperty target_property) const {
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (!active_animations_[i]->is_finished() &&
+ active_animations_[i]->target_property() == target_property)
+ return true;
+ }
+ return false;
+}
+
+void LayerAnimationController::SetAnimationRegistrar(
+ AnimationRegistrar* registrar) {
+ if (registrar_ == registrar)
+ return;
+
+ if (registrar_)
+ registrar_->UnregisterAnimationController(this);
+
+ registrar_ = registrar;
+ if (registrar_)
+ registrar_->RegisterAnimationController(this);
+
+ UpdateActivation(ForceActivation);
+}
+
+void LayerAnimationController::NotifyAnimationStarted(
+ const AnimationEvent& event,
+ double wall_clock_time) {
+ if (event.is_impl_only) {
+ FOR_EACH_OBSERVER(LayerAnimationEventObserver, event_observers_,
+ OnAnimationStarted(event));
+ if (layer_animation_delegate_)
+ layer_animation_delegate_->NotifyAnimationStarted(wall_clock_time);
+
+ return;
+ }
+
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (active_animations_[i]->group() == event.group_id &&
+ active_animations_[i]->target_property() == event.target_property &&
+ active_animations_[i]->needs_synchronized_start_time()) {
+ active_animations_[i]->set_needs_synchronized_start_time(false);
+ active_animations_[i]->set_start_time(event.monotonic_time);
+
+ FOR_EACH_OBSERVER(LayerAnimationEventObserver, event_observers_,
+ OnAnimationStarted(event));
+ if (layer_animation_delegate_)
+ layer_animation_delegate_->NotifyAnimationStarted(wall_clock_time);
+
+ return;
+ }
+ }
+}
+
+void LayerAnimationController::NotifyAnimationFinished(
+ const AnimationEvent& event,
+ double wall_clock_time) {
+ if (event.is_impl_only) {
+ if (layer_animation_delegate_)
+ layer_animation_delegate_->NotifyAnimationFinished(wall_clock_time);
+ return;
+ }
+
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (active_animations_[i]->group() == event.group_id &&
+ active_animations_[i]->target_property() == event.target_property) {
+ active_animations_[i]->set_received_finished_event(true);
+ if (layer_animation_delegate_)
+ layer_animation_delegate_->NotifyAnimationFinished(wall_clock_time);
+
+ return;
+ }
+ }
+}
+
+void LayerAnimationController::NotifyAnimationPropertyUpdate(
+ const AnimationEvent& event) {
+ switch (event.target_property) {
+ case Animation::Opacity:
+ NotifyObserversOpacityAnimated(event.opacity);
+ break;
+ case Animation::Transform:
+ NotifyObserversTransformAnimated(event.transform);
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+void LayerAnimationController::AddValueObserver(
+ LayerAnimationValueObserver* observer) {
+ if (!value_observers_.HasObserver(observer))
+ value_observers_.AddObserver(observer);
+}
+
+void LayerAnimationController::RemoveValueObserver(
+ LayerAnimationValueObserver* observer) {
+ value_observers_.RemoveObserver(observer);
+}
+
+void LayerAnimationController::AddEventObserver(
+ LayerAnimationEventObserver* observer) {
+ if (!event_observers_.HasObserver(observer))
+ event_observers_.AddObserver(observer);
+}
+
+void LayerAnimationController::RemoveEventObserver(
+ LayerAnimationEventObserver* observer) {
+ event_observers_.RemoveObserver(observer);
+}
+
+void LayerAnimationController::PushNewAnimationsToImplThread(
+ LayerAnimationController* controller_impl) const {
+ // Any new animations owned by the main thread's controller are cloned and
+ // add to the impl thread's controller.
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ // If the animation is already running on the impl thread, there is no
+ // need to copy it over.
+ if (controller_impl->GetAnimation(active_animations_[i]->group(),
+ active_animations_[i]->target_property()))
+ continue;
+
+ // If the animation is not running on the impl thread, it does not
+ // necessarily mean that it needs to be copied over and started; it may
+ // have already finished. In this case, the impl thread animation will
+ // have already notified that it has started and the main thread animation
+ // will no longer need
+ // a synchronized start time.
+ if (!active_animations_[i]->needs_synchronized_start_time())
+ continue;
+
+ // The new animation should be set to run as soon as possible.
+ Animation::RunState initial_run_state =
+ Animation::WaitingForTargetAvailability;
+ double start_time = 0;
+ scoped_ptr<Animation> to_add(active_animations_[i]->CloneAndInitialize(
+ Animation::ControllingInstance, initial_run_state, start_time));
+ DCHECK(!to_add->needs_synchronized_start_time());
+ controller_impl->AddAnimation(to_add.Pass());
+ }
+}
+
+struct IsCompleted {
+ explicit IsCompleted(const LayerAnimationController& main_thread_controller)
+ : main_thread_controller_(main_thread_controller) {}
+ bool operator()(Animation* animation) const {
+ if (animation->is_impl_only()) {
+ return (animation->run_state() == Animation::WaitingForDeletion);
+ } else {
+ return !main_thread_controller_.GetAnimation(
+ animation->group(),
+ animation->target_property());
+ }
+ }
+
+ private:
+ const LayerAnimationController& main_thread_controller_;
+};
+
+void LayerAnimationController::RemoveAnimationsCompletedOnMainThread(
+ LayerAnimationController* controller_impl) const {
+ // Delete all impl thread animations for which there is no corresponding
+ // main thread animation. Each iteration,
+ // controller->active_animations_.size() is decremented or i is incremented
+ // guaranteeing progress towards loop termination.
+ ScopedPtrVector<Animation>& animations =
+ controller_impl->active_animations_;
+ animations.erase(cc::remove_if(&animations,
+ animations.begin(),
+ animations.end(),
+ IsCompleted(*this)),
+ animations.end());
+}
+
+void LayerAnimationController::PushPropertiesToImplThread(
+ LayerAnimationController* controller_impl) const {
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ Animation* current_impl =
+ controller_impl->GetAnimation(
+ active_animations_[i]->group(),
+ active_animations_[i]->target_property());
+ if (current_impl)
+ active_animations_[i]->PushPropertiesTo(current_impl);
+ }
+}
+
+void LayerAnimationController::StartAnimationsWaitingForNextTick(
+ double monotonic_time) {
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (active_animations_[i]->run_state() == Animation::WaitingForNextTick)
+ active_animations_[i]->SetRunState(Animation::Starting, monotonic_time);
+ }
+}
+
+void LayerAnimationController::StartAnimationsWaitingForStartTime(
+ double monotonic_time) {
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (active_animations_[i]->run_state() == Animation::WaitingForStartTime &&
+ active_animations_[i]->start_time() <= monotonic_time)
+ active_animations_[i]->SetRunState(Animation::Starting, monotonic_time);
+ }
+}
+
+void LayerAnimationController::StartAnimationsWaitingForTargetAvailability(
+ double monotonic_time) {
+ // First collect running properties.
+ TargetProperties blocked_properties;
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (active_animations_[i]->run_state() == Animation::Starting ||
+ active_animations_[i]->run_state() == Animation::Running)
+ blocked_properties.insert(active_animations_[i]->target_property());
+ }
+
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (active_animations_[i]->run_state() ==
+ Animation::WaitingForTargetAvailability) {
+ // Collect all properties for animations with the same group id (they
+ // should all also be in the list of animations).
+ TargetProperties enqueued_properties;
+ enqueued_properties.insert(active_animations_[i]->target_property());
+ for (size_t j = i + 1; j < active_animations_.size(); ++j) {
+ if (active_animations_[i]->group() == active_animations_[j]->group())
+ enqueued_properties.insert(active_animations_[j]->target_property());
+ }
+
+ // Check to see if intersection of the list of properties affected by
+ // the group and the list of currently blocked properties is null. In
+ // any case, the group's target properties need to be added to the list
+ // of blocked properties.
+ bool null_intersection = true;
+ for (TargetProperties::iterator p_iter = enqueued_properties.begin();
+ p_iter != enqueued_properties.end();
+ ++p_iter) {
+ if (!blocked_properties.insert(*p_iter).second)
+ null_intersection = false;
+ }
+
+ // If the intersection is null, then we are free to start the animations
+ // in the group.
+ if (null_intersection) {
+ active_animations_[i]->SetRunState(
+ Animation::Starting, monotonic_time);
+ for (size_t j = i + 1; j < active_animations_.size(); ++j) {
+ if (active_animations_[i]->group() ==
+ active_animations_[j]->group()) {
+ active_animations_[j]->SetRunState(
+ Animation::Starting, monotonic_time);
+ }
+ }
+ }
+ }
+ }
+}
+
+void LayerAnimationController::PromoteStartedAnimations(
+ double monotonic_time,
+ AnimationEventsVector* events) {
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (active_animations_[i]->run_state() == Animation::Starting) {
+ active_animations_[i]->SetRunState(Animation::Running, monotonic_time);
+ if (!active_animations_[i]->has_set_start_time())
+ active_animations_[i]->set_start_time(monotonic_time);
+ if (events) {
+ AnimationEvent started_event(
+ AnimationEvent::Started,
+ id_,
+ active_animations_[i]->group(),
+ active_animations_[i]->target_property(),
+ monotonic_time);
+ started_event.is_impl_only = active_animations_[i]->is_impl_only();
+ events->push_back(started_event);
+ }
+ }
+ }
+}
+
+void LayerAnimationController::MarkFinishedAnimations(double monotonic_time) {
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (active_animations_[i]->IsFinishedAt(monotonic_time) &&
+ active_animations_[i]->run_state() != Animation::WaitingForDeletion)
+ active_animations_[i]->SetRunState(Animation::Finished, monotonic_time);
+ }
+}
+
+void LayerAnimationController::ResolveConflicts(double monotonic_time) {
+ // Find any animations that are animating the same property and resolve the
+ // confict. We could eventually blend, but for now we'll just abort the
+ // previous animation (where 'previous' means: (1) has a prior start time or
+ // (2) has an equal start time, but was added to the queue earlier, i.e.,
+ // has a lower index in active_animations_).
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (active_animations_[i]->run_state() == Animation::Starting ||
+ active_animations_[i]->run_state() == Animation::Running) {
+ for (size_t j = i + 1; j < active_animations_.size(); ++j) {
+ if ((active_animations_[j]->run_state() == Animation::Starting ||
+ active_animations_[j]->run_state() == Animation::Running) &&
+ active_animations_[i]->target_property() ==
+ active_animations_[j]->target_property()) {
+ if (active_animations_[i]->start_time() >
+ active_animations_[j]->start_time()) {
+ active_animations_[j]->SetRunState(Animation::Aborted,
+ monotonic_time);
+ } else {
+ active_animations_[i]->SetRunState(Animation::Aborted,
+ monotonic_time);
+ }
+ }
+ }
+ }
+ }
+}
+
+void LayerAnimationController::MarkAnimationsForDeletion(
+ double monotonic_time, AnimationEventsVector* events) {
+ // Non-aborted animations are marked for deletion after a corresponding
+ // AnimationEvent::Finished event is sent or received. This means that if
+ // we don't have an events vector, we must ensure that non-aborted animations
+ // have received a finished event before marking them for deletion.
+ for (size_t i = 0; i < active_animations_.size(); i++) {
+ int group_id = active_animations_[i]->group();
+ bool all_anims_with_same_id_are_finished = false;
+
+ // Since deleting an animation on the main thread leads to its deletion
+ // on the impl thread, we only mark a Finished main thread animation for
+ // deletion once it has received a Finished event from the impl thread.
+ bool animation_i_will_send_or_has_received_finish_event =
+ events || active_animations_[i]->received_finished_event();
+ // If an animation is finished, and not already marked for deletion,
+ // find out if all other animations in the same group are also finished.
+ if (active_animations_[i]->run_state() == Animation::Aborted ||
+ (active_animations_[i]->run_state() == Animation::Finished &&
+ animation_i_will_send_or_has_received_finish_event)) {
+ all_anims_with_same_id_are_finished = true;
+ for (size_t j = 0; j < active_animations_.size(); ++j) {
+ bool animation_j_will_send_or_has_received_finish_event =
+ events || active_animations_[j]->received_finished_event();
+ if (group_id == active_animations_[j]->group() &&
+ (!active_animations_[j]->is_finished() ||
+ (active_animations_[j]->run_state() == Animation::Finished &&
+ !animation_j_will_send_or_has_received_finish_event))) {
+ all_anims_with_same_id_are_finished = false;
+ break;
+ }
+ }
+ }
+ if (all_anims_with_same_id_are_finished) {
+ // We now need to remove all animations with the same group id as
+ // group_id (and send along animation finished notifications, if
+ // necessary).
+ for (size_t j = i; j < active_animations_.size(); j++) {
+ if (group_id == active_animations_[j]->group()) {
+ if (events) {
+ AnimationEvent finished_event(
+ AnimationEvent::Finished,
+ id_,
+ active_animations_[j]->group(),
+ active_animations_[j]->target_property(),
+ monotonic_time);
+ finished_event.is_impl_only = active_animations_[j]->is_impl_only();
+ events->push_back(finished_event);
+ }
+ active_animations_[j]->SetRunState(Animation::WaitingForDeletion,
+ monotonic_time);
+ }
+ }
+ }
+ }
+}
+
+static bool IsWaitingForDeletion(Animation* animation) {
+ return animation->run_state() == Animation::WaitingForDeletion;
+}
+
+void LayerAnimationController::PurgeAnimationsMarkedForDeletion() {
+ ScopedPtrVector<Animation>& animations = active_animations_;
+ animations.erase(cc::remove_if(&animations,
+ animations.begin(),
+ animations.end(),
+ IsWaitingForDeletion),
+ animations.end());
+}
+
+void LayerAnimationController::ReplaceImplThreadAnimations(
+ LayerAnimationController* controller_impl) const {
+ controller_impl->active_animations_.clear();
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ scoped_ptr<Animation> to_add;
+ if (active_animations_[i]->needs_synchronized_start_time()) {
+ // We haven't received an animation started notification yet, so it
+ // is important that we add it in a 'waiting' and not 'running' state.
+ Animation::RunState initial_run_state =
+ Animation::WaitingForTargetAvailability;
+ double start_time = 0;
+ to_add = active_animations_[i]->CloneAndInitialize(
+ Animation::ControllingInstance,
+ initial_run_state, start_time).Pass();
+ } else {
+ to_add = active_animations_[i]->Clone(
+ Animation::ControllingInstance).Pass();
+ }
+
+ controller_impl->AddAnimation(to_add.Pass());
+ }
+}
+
+void LayerAnimationController::TickAnimations(double monotonic_time) {
+ for (size_t i = 0; i < active_animations_.size(); ++i) {
+ if (active_animations_[i]->run_state() == Animation::Starting ||
+ active_animations_[i]->run_state() == Animation::Running ||
+ active_animations_[i]->run_state() == Animation::Paused) {
+ double trimmed =
+ active_animations_[i]->TrimTimeToCurrentIteration(monotonic_time);
+
+ // Animation assumes its initial value until it gets the synchronized
+ // start time from the impl thread and can start ticking.
+ if (active_animations_[i]->needs_synchronized_start_time())
+ trimmed = 0;
+
+ // A just-started animation assumes its initial value.
+ if (active_animations_[i]->run_state() == Animation::Starting &&
+ !active_animations_[i]->has_set_start_time())
+ trimmed = 0;
+
+ switch (active_animations_[i]->target_property()) {
+ case Animation::Transform: {
+ const TransformAnimationCurve* transform_animation_curve =
+ active_animations_[i]->curve()->ToTransformAnimationCurve();
+ const gfx::Transform transform =
+ transform_animation_curve->GetValue(trimmed);
+ NotifyObserversTransformAnimated(transform);
+ break;
+ }
+
+ case Animation::Opacity: {
+ const FloatAnimationCurve* float_animation_curve =
+ active_animations_[i]->curve()->ToFloatAnimationCurve();
+ const float opacity = float_animation_curve->GetValue(trimmed);
+ NotifyObserversOpacityAnimated(opacity);
+ break;
+ }
+
+ // Do nothing for sentinel value.
+ case Animation::TargetPropertyEnumSize:
+ NOTREACHED();
+ }
+ }
+ }
+}
+
+void LayerAnimationController::UpdateActivation(UpdateActivationType type) {
+ bool force = type == ForceActivation;
+ if (registrar_) {
+ if (!active_animations_.empty() && (!is_active_ || force))
+ registrar_->DidActivateAnimationController(this);
+ else if (active_animations_.empty() && (is_active_ || force))
+ registrar_->DidDeactivateAnimationController(this);
+ is_active_ = !active_animations_.empty();
+ }
+}
+
+void LayerAnimationController::NotifyObserversOpacityAnimated(float opacity) {
+ FOR_EACH_OBSERVER(LayerAnimationValueObserver,
+ value_observers_,
+ OnOpacityAnimated(opacity));
+}
+
+void LayerAnimationController::NotifyObserversTransformAnimated(
+ const gfx::Transform& transform) {
+ FOR_EACH_OBSERVER(LayerAnimationValueObserver,
+ value_observers_,
+ OnTransformAnimated(transform));
+}
+
+bool LayerAnimationController::HasValueObserver() {
+ if (value_observers_.might_have_observers()) {
+ ObserverListBase<LayerAnimationValueObserver>::Iterator it(
+ value_observers_);
+ return it.GetNext() != NULL;
+ }
+ return false;
+}
+
+bool LayerAnimationController::HasActiveValueObserver() {
+ if (value_observers_.might_have_observers()) {
+ ObserverListBase<LayerAnimationValueObserver>::Iterator it(
+ value_observers_);
+ LayerAnimationValueObserver* obs;
+ while ((obs = it.GetNext()) != NULL)
+ if (obs->IsActive())
+ return true;
+ }
+ return false;
+}
+
+} // namespace cc
diff --git a/chromium/cc/animation/layer_animation_controller.h b/chromium/cc/animation/layer_animation_controller.h
new file mode 100644
index 00000000000..e450f1534d5
--- /dev/null
+++ b/chromium/cc/animation/layer_animation_controller.h
@@ -0,0 +1,168 @@
+// 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.
+
+#ifndef CC_ANIMATION_LAYER_ANIMATION_CONTROLLER_H_
+#define CC_ANIMATION_LAYER_ANIMATION_CONTROLLER_H_
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "base/time/time.h"
+#include "cc/animation/animation_events.h"
+#include "cc/animation/layer_animation_event_observer.h"
+#include "cc/base/cc_export.h"
+#include "cc/base/scoped_ptr_vector.h"
+#include "ui/gfx/transform.h"
+
+namespace gfx { class Transform; }
+
+namespace cc {
+
+class Animation;
+class AnimationDelegate;
+class AnimationRegistrar;
+class KeyframeValueList;
+class LayerAnimationValueObserver;
+
+class CC_EXPORT LayerAnimationController
+ : public base::RefCounted<LayerAnimationController> {
+ public:
+ static scoped_refptr<LayerAnimationController> Create(int id);
+
+ int id() const { return id_; }
+
+ // These methods are virtual for testing.
+ virtual void AddAnimation(scoped_ptr<Animation> animation);
+ virtual void PauseAnimation(int animation_id, double time_offset);
+ virtual void RemoveAnimation(int animation_id);
+ virtual void RemoveAnimation(int animation_id,
+ Animation::TargetProperty target_property);
+ virtual void SuspendAnimations(double monotonic_time);
+ virtual void ResumeAnimations(double monotonic_time);
+
+ // Ensures that the list of active animations on the main thread and the impl
+ // thread are kept in sync. This function does not take ownership of the impl
+ // thread controller.
+ virtual void PushAnimationUpdatesTo(
+ LayerAnimationController* controller_impl);
+
+ void Animate(double monotonic_time);
+ void AccumulatePropertyUpdates(double monotonic_time,
+ AnimationEventsVector* events);
+
+ void UpdateState(bool start_ready_animations,
+ AnimationEventsVector* events);
+
+ // Returns the active animation in the given group, animating the given
+ // property, if such an animation exists.
+ Animation* GetAnimation(int group_id,
+ Animation::TargetProperty target_property) const;
+
+ // Returns the active animation animating the given property that is either
+ // running, or is next to run, if such an animation exists.
+ Animation* GetAnimation(Animation::TargetProperty target_property) const;
+
+ // Returns true if there are any animations that have neither finished nor
+ // aborted.
+ bool HasActiveAnimation() const;
+
+ // Returns true if there are any animations at all to process.
+ bool has_any_animation() const { return !active_animations_.empty(); }
+
+ // Returns true if there is an animation currently animating the given
+ // property, or if there is an animation scheduled to animate this property in
+ // the future.
+ bool IsAnimatingProperty(Animation::TargetProperty target_property) const;
+
+ // If a sync is forced, then the next time animation updates are pushed to the
+ // impl thread, all animations will be transferred.
+ void set_force_sync() { force_sync_ = true; }
+
+ void SetAnimationRegistrar(AnimationRegistrar* registrar);
+ AnimationRegistrar* animation_registrar() { return registrar_; }
+
+ void NotifyAnimationStarted(const AnimationEvent& event,
+ double wall_clock_time);
+ void NotifyAnimationFinished(const AnimationEvent& event,
+ double wall_clock_time);
+ void NotifyAnimationPropertyUpdate(const AnimationEvent& event);
+
+ void AddValueObserver(LayerAnimationValueObserver* observer);
+ void RemoveValueObserver(LayerAnimationValueObserver* observer);
+
+ void AddEventObserver(LayerAnimationEventObserver* observer);
+ void RemoveEventObserver(LayerAnimationEventObserver* observer);
+
+ void set_layer_animation_delegate(AnimationDelegate* delegate) {
+ layer_animation_delegate_ = delegate;
+ }
+
+ protected:
+ friend class base::RefCounted<LayerAnimationController>;
+
+ explicit LayerAnimationController(int id);
+ virtual ~LayerAnimationController();
+
+ private:
+ typedef base::hash_set<int> TargetProperties;
+
+ void PushNewAnimationsToImplThread(
+ LayerAnimationController* controller_impl) const;
+ void RemoveAnimationsCompletedOnMainThread(
+ LayerAnimationController* controller_impl) const;
+ void PushPropertiesToImplThread(
+ LayerAnimationController* controller_impl) const;
+ void ReplaceImplThreadAnimations(
+ LayerAnimationController* controller_impl) const;
+
+ void StartAnimationsWaitingForNextTick(double monotonic_time);
+ void StartAnimationsWaitingForStartTime(double monotonic_time);
+ void StartAnimationsWaitingForTargetAvailability(double monotonic_time);
+ void ResolveConflicts(double monotonic_time);
+ void PromoteStartedAnimations(double monotonic_time,
+ AnimationEventsVector* events);
+ void MarkFinishedAnimations(double monotonic_time);
+ void MarkAnimationsForDeletion(double monotonic_time,
+ AnimationEventsVector* events);
+ void PurgeAnimationsMarkedForDeletion();
+
+ void TickAnimations(double monotonic_time);
+
+ enum UpdateActivationType {
+ NormalActivation,
+ ForceActivation
+ };
+ void UpdateActivation(UpdateActivationType type);
+
+ void NotifyObserversOpacityAnimated(float opacity);
+ void NotifyObserversTransformAnimated(const gfx::Transform& transform);
+
+ bool HasValueObserver();
+ bool HasActiveValueObserver();
+
+ // If this is true, we force a sync to the impl thread.
+ bool force_sync_;
+
+ AnimationRegistrar* registrar_;
+ int id_;
+ ScopedPtrVector<Animation> active_animations_;
+
+ // This is used to ensure that we don't spam the registrar.
+ bool is_active_;
+
+ double last_tick_time_;
+
+ ObserverList<LayerAnimationValueObserver> value_observers_;
+ ObserverList<LayerAnimationEventObserver> event_observers_;
+
+ AnimationDelegate* layer_animation_delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(LayerAnimationController);
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_LAYER_ANIMATION_CONTROLLER_H_
diff --git a/chromium/cc/animation/layer_animation_controller_unittest.cc b/chromium/cc/animation/layer_animation_controller_unittest.cc
new file mode 100644
index 00000000000..2e06f5cc829
--- /dev/null
+++ b/chromium/cc/animation/layer_animation_controller_unittest.cc
@@ -0,0 +1,1121 @@
+// 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 "cc/animation/layer_animation_controller.h"
+
+#include "cc/animation/animation.h"
+#include "cc/animation/animation_curve.h"
+#include "cc/animation/animation_delegate.h"
+#include "cc/animation/keyframed_animation_curve.h"
+#include "cc/animation/transform_operations.h"
+#include "cc/test/animation_test_common.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+namespace {
+
+void ExpectTranslateX(double translate_x, const gfx::Transform& matrix) {
+ EXPECT_FLOAT_EQ(translate_x, matrix.matrix().getDouble(0, 3)); }
+
+scoped_ptr<Animation> CreateAnimation(scoped_ptr<AnimationCurve> curve,
+ int id,
+ Animation::TargetProperty property) {
+ return Animation::Create(curve.Pass(), 0, id, property);
+}
+
+TEST(LayerAnimationControllerTest, SyncNewAnimation) {
+ FakeLayerAnimationValueObserver dummy_impl;
+ scoped_refptr<LayerAnimationController> controller_impl(
+ LayerAnimationController::Create(0));
+ controller_impl->AddValueObserver(&dummy_impl);
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddValueObserver(&dummy);
+
+ EXPECT_FALSE(controller_impl->GetAnimation(Animation::Opacity));
+
+ AddOpacityTransitionToController(controller.get(), 1, 0, 1, false);
+ int group_id = controller->GetAnimation(Animation::Opacity)->group();
+
+ controller->PushAnimationUpdatesTo(controller_impl.get());
+
+ EXPECT_TRUE(controller_impl->GetAnimation(group_id, Animation::Opacity));
+ EXPECT_EQ(Animation::WaitingForTargetAvailability,
+ controller_impl->GetAnimation(group_id,
+ Animation::Opacity)->run_state());
+}
+
+// If an animation is started on the impl thread before it is ticked on the main
+// thread, we must be sure to respect the synchronized start time.
+TEST(LayerAnimationControllerTest, DoNotClobberStartTimes) {
+ FakeLayerAnimationValueObserver dummy_impl;
+ scoped_refptr<LayerAnimationController> controller_impl(
+ LayerAnimationController::Create(0));
+ controller_impl->AddValueObserver(&dummy_impl);
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddValueObserver(&dummy);
+
+ EXPECT_FALSE(controller_impl->GetAnimation(Animation::Opacity));
+
+ AddOpacityTransitionToController(controller.get(), 1, 0, 1, false);
+ int group_id = controller->GetAnimation(Animation::Opacity)->group();
+
+ controller->PushAnimationUpdatesTo(controller_impl.get());
+
+ EXPECT_TRUE(controller_impl->GetAnimation(group_id, Animation::Opacity));
+ EXPECT_EQ(Animation::WaitingForTargetAvailability,
+ controller_impl->GetAnimation(group_id,
+ Animation::Opacity)->run_state());
+
+ AnimationEventsVector events;
+ controller_impl->Animate(1.0);
+ controller_impl->UpdateState(true, &events);
+
+ // Synchronize the start times.
+ EXPECT_EQ(1u, events.size());
+ controller->NotifyAnimationStarted(events[0], 0.0);
+ EXPECT_EQ(controller->GetAnimation(group_id,
+ Animation::Opacity)->start_time(),
+ controller_impl->GetAnimation(group_id,
+ Animation::Opacity)->start_time());
+
+ // Start the animation on the main thread. Should not affect the start time.
+ controller->Animate(1.5);
+ controller->UpdateState(true, NULL);
+ EXPECT_EQ(controller->GetAnimation(group_id,
+ Animation::Opacity)->start_time(),
+ controller_impl->GetAnimation(group_id,
+ Animation::Opacity)->start_time());
+}
+
+TEST(LayerAnimationControllerTest, SyncPauseAndResume) {
+ FakeLayerAnimationValueObserver dummy_impl;
+ scoped_refptr<LayerAnimationController> controller_impl(
+ LayerAnimationController::Create(0));
+ controller_impl->AddValueObserver(&dummy_impl);
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddValueObserver(&dummy);
+
+ EXPECT_FALSE(controller_impl->GetAnimation(Animation::Opacity));
+
+ AddOpacityTransitionToController(controller.get(), 1, 0, 1, false);
+ int group_id = controller->GetAnimation(Animation::Opacity)->group();
+
+ controller->PushAnimationUpdatesTo(controller_impl.get());
+
+ EXPECT_TRUE(controller_impl->GetAnimation(group_id, Animation::Opacity));
+ EXPECT_EQ(Animation::WaitingForTargetAvailability,
+ controller_impl->GetAnimation(group_id,
+ Animation::Opacity)->run_state());
+
+ // Start the animations on each controller.
+ AnimationEventsVector events;
+ controller_impl->Animate(0.0);
+ controller_impl->UpdateState(true, &events);
+ controller->Animate(0.0);
+ controller->UpdateState(true, NULL);
+ EXPECT_EQ(Animation::Running,
+ controller_impl->GetAnimation(group_id,
+ Animation::Opacity)->run_state());
+ EXPECT_EQ(Animation::Running,
+ controller->GetAnimation(group_id,
+ Animation::Opacity)->run_state());
+
+ // Pause the main-thread animation.
+ controller->SuspendAnimations(1.0);
+ EXPECT_EQ(Animation::Paused,
+ controller->GetAnimation(group_id,
+ Animation::Opacity)->run_state());
+
+ // The pause run state change should make it to the impl thread controller.
+ controller->PushAnimationUpdatesTo(controller_impl.get());
+ EXPECT_EQ(Animation::Paused,
+ controller_impl->GetAnimation(group_id,
+ Animation::Opacity)->run_state());
+
+ // Resume the main-thread animation.
+ controller->ResumeAnimations(2.0);
+ EXPECT_EQ(Animation::Running,
+ controller->GetAnimation(group_id,
+ Animation::Opacity)->run_state());
+
+ // The pause run state change should make it to the impl thread controller.
+ controller->PushAnimationUpdatesTo(controller_impl.get());
+ EXPECT_EQ(Animation::Running,
+ controller_impl->GetAnimation(group_id,
+ Animation::Opacity)->run_state());
+}
+
+TEST(LayerAnimationControllerTest, DoNotSyncFinishedAnimation) {
+ FakeLayerAnimationValueObserver dummy_impl;
+ scoped_refptr<LayerAnimationController> controller_impl(
+ LayerAnimationController::Create(0));
+ controller_impl->AddValueObserver(&dummy_impl);
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddValueObserver(&dummy);
+
+ EXPECT_FALSE(controller_impl->GetAnimation(Animation::Opacity));
+
+ int animation_id =
+ AddOpacityTransitionToController(controller.get(), 1, 0, 1, false);
+ int group_id = controller->GetAnimation(Animation::Opacity)->group();
+
+ controller->PushAnimationUpdatesTo(controller_impl.get());
+
+ EXPECT_TRUE(controller_impl->GetAnimation(group_id, Animation::Opacity));
+ EXPECT_EQ(Animation::WaitingForTargetAvailability,
+ controller_impl->GetAnimation(group_id,
+ Animation::Opacity)->run_state());
+
+ // Notify main thread controller that the animation has started.
+ AnimationEvent animation_started_event(
+ AnimationEvent::Started, 0, group_id, Animation::Opacity, 0);
+ controller->NotifyAnimationStarted(animation_started_event, 0.0);
+
+ // Force animation to complete on impl thread.
+ controller_impl->RemoveAnimation(animation_id);
+
+ EXPECT_FALSE(controller_impl->GetAnimation(group_id, Animation::Opacity));
+
+ controller->PushAnimationUpdatesTo(controller_impl.get());
+
+ // Even though the main thread has a 'new' animation, it should not be pushed
+ // because the animation has already completed on the impl thread.
+ EXPECT_FALSE(controller_impl->GetAnimation(group_id, Animation::Opacity));
+}
+
+// Ensure that a finished animation is eventually deleted by both the
+// main-thread and the impl-thread controllers.
+TEST(LayerAnimationControllerTest, AnimationsAreDeleted) {
+ FakeLayerAnimationValueObserver dummy;
+ FakeLayerAnimationValueObserver dummy_impl;
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ scoped_refptr<LayerAnimationController> controller_impl(
+ LayerAnimationController::Create(0));
+ controller->AddValueObserver(&dummy);
+ controller_impl->AddValueObserver(&dummy_impl);
+
+ AddOpacityTransitionToController(controller.get(), 1.0, 0.0f, 1.0f, false);
+ controller->Animate(0.0);
+ controller->UpdateState(true, NULL);
+ controller->PushAnimationUpdatesTo(controller_impl.get());
+
+ controller_impl->Animate(0.5);
+ controller_impl->UpdateState(true, events.get());
+
+ // There should be a Started event for the animation.
+ EXPECT_EQ(1u, events->size());
+ EXPECT_EQ(AnimationEvent::Started, (*events)[0].type);
+ controller->NotifyAnimationStarted((*events)[0], 0.0);
+
+ controller->Animate(1.0);
+ controller->UpdateState(true, NULL);
+
+ events.reset(new AnimationEventsVector);
+ controller_impl->Animate(2.0);
+ controller_impl->UpdateState(true, events.get());
+
+ // There should be a Finished event for the animation.
+ EXPECT_EQ(1u, events->size());
+ EXPECT_EQ(AnimationEvent::Finished, (*events)[0].type);
+
+ // Neither controller should have deleted the animation yet.
+ EXPECT_TRUE(controller->GetAnimation(Animation::Opacity));
+ EXPECT_TRUE(controller_impl->GetAnimation(Animation::Opacity));
+
+ controller->NotifyAnimationFinished((*events)[0], 0.0);
+
+ controller->Animate(3.0);
+ controller->UpdateState(true, NULL);
+
+ controller->PushAnimationUpdatesTo(controller_impl.get());
+
+ // Both controllers should now have deleted the animation.
+ EXPECT_FALSE(controller->has_any_animation());
+ EXPECT_FALSE(controller_impl->has_any_animation());
+}
+
+// Tests that transitioning opacity from 0 to 1 works as expected.
+
+static const AnimationEvent* GetMostRecentPropertyUpdateEvent(
+ const AnimationEventsVector* events) {
+ const AnimationEvent* event = 0;
+ for (size_t i = 0; i < events->size(); ++i)
+ if ((*events)[i].type == AnimationEvent::PropertyUpdate)
+ event = &(*events)[i];
+
+ return event;
+}
+
+TEST(LayerAnimationControllerTest, TrivialTransition) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddValueObserver(&dummy);
+
+ scoped_ptr<Animation> to_add(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)).Pass(),
+ 1,
+ Animation::Opacity));
+
+ controller->AddAnimation(to_add.Pass());
+ controller->Animate(0.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ // A non-impl-only animation should not generate property updates.
+ const AnimationEvent* event = GetMostRecentPropertyUpdateEvent(events.get());
+ EXPECT_FALSE(event);
+ controller->Animate(1.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_EQ(1.f, dummy.opacity());
+ EXPECT_FALSE(controller->HasActiveAnimation());
+ event = GetMostRecentPropertyUpdateEvent(events.get());
+ EXPECT_FALSE(event);
+}
+
+TEST(LayerAnimationControllerTest, TrivialTransitionOnImpl) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy_impl;
+ scoped_refptr<LayerAnimationController> controller_impl(
+ LayerAnimationController::Create(0));
+ controller_impl->AddValueObserver(&dummy_impl);
+
+ scoped_ptr<Animation> to_add(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)).Pass(),
+ 1,
+ Animation::Opacity));
+ to_add->set_is_impl_only(true);
+
+ controller_impl->AddAnimation(to_add.Pass());
+ controller_impl->Animate(0.0);
+ controller_impl->UpdateState(true, events.get());
+ EXPECT_TRUE(controller_impl->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy_impl.opacity());
+ EXPECT_EQ(2u, events->size());
+ const AnimationEvent* start_opacity_event =
+ GetMostRecentPropertyUpdateEvent(events.get());
+ EXPECT_EQ(0.f, start_opacity_event->opacity);
+
+ controller_impl->Animate(1.0);
+ controller_impl->UpdateState(true, events.get());
+ EXPECT_EQ(1.f, dummy_impl.opacity());
+ EXPECT_FALSE(controller_impl->HasActiveAnimation());
+ EXPECT_EQ(4u, events->size());
+ const AnimationEvent* end_opacity_event =
+ GetMostRecentPropertyUpdateEvent(events.get());
+ EXPECT_EQ(1.f, end_opacity_event->opacity);
+}
+
+TEST(LayerAnimationControllerTest, TrivialTransformOnImpl) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy_impl;
+ scoped_refptr<LayerAnimationController> controller_impl(
+ LayerAnimationController::Create(0));
+ controller_impl->AddValueObserver(&dummy_impl);
+
+ // Choose different values for x and y to avoid coincidental values in the
+ // observed transforms.
+ const float delta_x = 3;
+ const float delta_y = 4;
+
+ scoped_ptr<KeyframedTransformAnimationCurve> curve(
+ KeyframedTransformAnimationCurve::Create());
+
+ // Create simple Transform animation.
+ TransformOperations operations;
+ curve->AddKeyframe(TransformKeyframe::Create(
+ 0, operations, scoped_ptr<cc::TimingFunction>()));
+ operations.AppendTranslate(delta_x, delta_y, 0);
+ curve->AddKeyframe(TransformKeyframe::Create(
+ 1, operations, scoped_ptr<cc::TimingFunction>()));
+
+ scoped_ptr<Animation> animation(Animation::Create(
+ curve.PassAs<AnimationCurve>(), 1, 0, Animation::Transform));
+ animation->set_is_impl_only(true);
+ controller_impl->AddAnimation(animation.Pass());
+
+ // Run animation.
+ controller_impl->Animate(0.0);
+ controller_impl->UpdateState(true, events.get());
+ EXPECT_TRUE(controller_impl->HasActiveAnimation());
+ EXPECT_EQ(gfx::Transform(), dummy_impl.transform());
+ EXPECT_EQ(2u, events->size());
+ const AnimationEvent* start_transform_event =
+ GetMostRecentPropertyUpdateEvent(events.get());
+ ASSERT_TRUE(start_transform_event);
+ EXPECT_EQ(gfx::Transform(), start_transform_event->transform);
+ EXPECT_TRUE(start_transform_event->is_impl_only);
+
+ gfx::Transform expected_transform;
+ expected_transform.Translate(delta_x, delta_y);
+
+ controller_impl->Animate(1.0);
+ controller_impl->UpdateState(true, events.get());
+ EXPECT_EQ(expected_transform, dummy_impl.transform());
+ EXPECT_FALSE(controller_impl->HasActiveAnimation());
+ EXPECT_EQ(4u, events->size());
+ const AnimationEvent* end_transform_event =
+ GetMostRecentPropertyUpdateEvent(events.get());
+ EXPECT_EQ(expected_transform, end_transform_event->transform);
+ EXPECT_TRUE(end_transform_event->is_impl_only);
+}
+
+class FakeAnimationDelegate : public AnimationDelegate {
+ public:
+ FakeAnimationDelegate()
+ : started_(false),
+ finished_(false) {}
+
+ virtual void NotifyAnimationStarted(double time) OVERRIDE {
+ started_ = true;
+ }
+
+ virtual void NotifyAnimationFinished(double time) OVERRIDE {
+ finished_ = true;
+ }
+
+ bool started() { return started_; }
+
+ bool finished() { return finished_; }
+
+ private:
+ bool started_;
+ bool finished_;
+};
+
+// Tests that impl-only animations lead to start and finished notifications
+// being sent to the main thread controller's animation delegate.
+TEST(LayerAnimationControllerTest,
+ NotificationsForImplOnlyAnimationsAreSentToMainThreadDelegate) {
+ FakeLayerAnimationValueObserver dummy_impl;
+ scoped_refptr<LayerAnimationController> controller_impl(
+ LayerAnimationController::Create(0));
+ controller_impl->AddValueObserver(&dummy_impl);
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddValueObserver(&dummy);
+ FakeAnimationDelegate delegate;
+ controller->set_layer_animation_delegate(&delegate);
+
+ scoped_ptr<Animation> to_add(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)).Pass(),
+ 1,
+ Animation::Opacity));
+ to_add->set_is_impl_only(true);
+ controller_impl->AddAnimation(to_add.Pass());
+
+ controller_impl->Animate(0.0);
+ controller_impl->UpdateState(true, events.get());
+
+ // We should receive 2 events (a started notification and a property update).
+ EXPECT_EQ(2u, events->size());
+ EXPECT_EQ(AnimationEvent::Started, (*events)[0].type);
+ EXPECT_TRUE((*events)[0].is_impl_only);
+ EXPECT_EQ(AnimationEvent::PropertyUpdate, (*events)[1].type);
+ EXPECT_TRUE((*events)[1].is_impl_only);
+
+ // Passing on the start event to the main thread controller should cause the
+ // delegate to get notified.
+ EXPECT_FALSE(delegate.started());
+ controller->NotifyAnimationStarted((*events)[0], 0.0);
+ EXPECT_TRUE(delegate.started());
+
+ events.reset(new AnimationEventsVector);
+ controller_impl->Animate(1.0);
+ controller_impl->UpdateState(true, events.get());
+
+ // We should receive 2 events (a finished notification and a property update).
+ EXPECT_EQ(2u, events->size());
+ EXPECT_EQ(AnimationEvent::Finished, (*events)[0].type);
+ EXPECT_TRUE((*events)[0].is_impl_only);
+ EXPECT_EQ(AnimationEvent::PropertyUpdate, (*events)[1].type);
+ EXPECT_TRUE((*events)[1].is_impl_only);
+
+ // Passing on the finished event to the main thread controller should cause
+ // the delegate to get notified.
+ EXPECT_FALSE(delegate.finished());
+ controller->NotifyAnimationFinished((*events)[0], 0.0);
+ EXPECT_TRUE(delegate.finished());
+}
+
+// Tests animations that are waiting for a synchronized start time do not
+// finish.
+TEST(LayerAnimationControllerTest,
+ AnimationsWaitingForStartTimeDoNotFinishIfTheyOutwaitTheirFinish) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddValueObserver(&dummy);
+
+ scoped_ptr<Animation> to_add(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)).Pass(),
+ 1,
+ Animation::Opacity));
+ to_add->set_needs_synchronized_start_time(true);
+
+ // We should pause at the first keyframe indefinitely waiting for that
+ // animation to start.
+ controller->AddAnimation(to_add.Pass());
+ controller->Animate(0.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ controller->Animate(1.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ controller->Animate(2.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+
+ // Send the synchronized start time.
+ controller->NotifyAnimationStarted(
+ AnimationEvent(AnimationEvent::Started, 0, 1, Animation::Opacity, 2),
+ 0.0);
+ controller->Animate(5.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_EQ(1.f, dummy.opacity());
+ EXPECT_FALSE(controller->HasActiveAnimation());
+}
+
+// Tests that two queued animations affecting the same property run in sequence.
+TEST(LayerAnimationControllerTest, TrivialQueuing) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddValueObserver(&dummy);
+
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)).Pass(),
+ 1,
+ Animation::Opacity));
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(
+ new FakeFloatTransition(1.0, 1.f, 0.5f)).Pass(),
+ 2,
+ Animation::Opacity));
+
+ controller->Animate(0.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ controller->Animate(1.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(1.f, dummy.opacity());
+ controller->Animate(2.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_EQ(0.5f, dummy.opacity());
+ EXPECT_FALSE(controller->HasActiveAnimation());
+}
+
+// Tests interrupting a transition with another transition.
+TEST(LayerAnimationControllerTest, Interrupt) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddValueObserver(&dummy);
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)).Pass(),
+ 1,
+ Animation::Opacity));
+ controller->Animate(0.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+
+ scoped_ptr<Animation> to_add(CreateAnimation(
+ scoped_ptr<AnimationCurve>(
+ new FakeFloatTransition(1.0, 1.f, 0.5f)).Pass(),
+ 2,
+ Animation::Opacity));
+ to_add->SetRunState(Animation::WaitingForNextTick, 0);
+ controller->AddAnimation(to_add.Pass());
+
+ // Since the animation was in the WaitingForNextTick state, it should start
+ // right in this call to animate.
+ controller->Animate(0.5);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(1.f, dummy.opacity());
+ controller->Animate(1.5);
+ controller->UpdateState(true, events.get());
+ EXPECT_EQ(0.5f, dummy.opacity());
+ EXPECT_FALSE(controller->HasActiveAnimation());
+}
+
+// Tests scheduling two animations to run together when only one property is
+// free.
+TEST(LayerAnimationControllerTest, ScheduleTogetherWhenAPropertyIsBlocked) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddValueObserver(&dummy);
+
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeTransformTransition(1)).Pass(),
+ 1,
+ Animation::Transform));
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeTransformTransition(1)).Pass(),
+ 2,
+ Animation::Transform));
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)).Pass(),
+ 2,
+ Animation::Opacity));
+
+ controller->Animate(0.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_EQ(0.f, dummy.opacity());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ controller->Animate(1.0);
+ controller->UpdateState(true, events.get());
+ // Should not have started the float transition yet.
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ // The float animation should have started at time 1 and should be done.
+ controller->Animate(2.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_EQ(1.f, dummy.opacity());
+ EXPECT_FALSE(controller->HasActiveAnimation());
+}
+
+// Tests scheduling two animations to run together with different lengths and
+// another animation queued to start when the shorter animation finishes (should
+// wait for both to finish).
+TEST(LayerAnimationControllerTest, ScheduleTogetherWithAnAnimWaiting) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddValueObserver(&dummy);
+
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeTransformTransition(2)).Pass(),
+ 1,
+ Animation::Transform));
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)).Pass(),
+ 1,
+ Animation::Opacity));
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(
+ new FakeFloatTransition(1.0, 1.f, 0.5f)).Pass(),
+ 2,
+ Animation::Opacity));
+
+ // Animations with id 1 should both start now.
+ controller->Animate(0.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ // The opacity animation should have finished at time 1, but the group
+ // of animations with id 1 don't finish until time 2 because of the length
+ // of the transform animation.
+ controller->Animate(2.0);
+ controller->UpdateState(true, events.get());
+ // Should not have started the float transition yet.
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(1.f, dummy.opacity());
+
+ // The second opacity animation should start at time 2 and should be done by
+ // time 3.
+ controller->Animate(3.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_EQ(0.5f, dummy.opacity());
+ EXPECT_FALSE(controller->HasActiveAnimation());
+}
+
+// Tests scheduling an animation to start in the future.
+TEST(LayerAnimationControllerTest, ScheduleAnimation) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddValueObserver(&dummy);
+
+ scoped_ptr<Animation> to_add(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)).Pass(),
+ 1,
+ Animation::Opacity));
+ to_add->SetRunState(Animation::WaitingForStartTime, 0);
+ to_add->set_start_time(1.f);
+ controller->AddAnimation(to_add.Pass());
+
+ controller->Animate(0.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ controller->Animate(1.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ controller->Animate(2.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_EQ(1.f, dummy.opacity());
+ EXPECT_FALSE(controller->HasActiveAnimation());
+}
+
+// Tests scheduling an animation to start in the future that's interrupting a
+// running animation.
+TEST(LayerAnimationControllerTest,
+ ScheduledAnimationInterruptsRunningAnimation) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddValueObserver(&dummy);
+
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(2.0, 0.f, 1.f)).Pass(),
+ 1,
+ Animation::Opacity));
+
+ scoped_ptr<Animation> to_add(CreateAnimation(
+ scoped_ptr<AnimationCurve>(
+ new FakeFloatTransition(1.0, 0.5f, 0.f)).Pass(),
+ 2,
+ Animation::Opacity));
+ to_add->SetRunState(Animation::WaitingForStartTime, 0);
+ to_add->set_start_time(1.f);
+ controller->AddAnimation(to_add.Pass());
+
+ // First 2s opacity transition should start immediately.
+ controller->Animate(0.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ controller->Animate(0.5);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.25f, dummy.opacity());
+ controller->Animate(1.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.5f, dummy.opacity());
+ controller->Animate(2.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_EQ(0.f, dummy.opacity());
+ EXPECT_FALSE(controller->HasActiveAnimation());
+}
+
+// Tests scheduling an animation to start in the future that interrupts a
+// running animation and there is yet another animation queued to start later.
+TEST(LayerAnimationControllerTest,
+ ScheduledAnimationInterruptsRunningAnimationWithAnimInQueue) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddValueObserver(&dummy);
+
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(2.0, 0.f, 1.f)).Pass(),
+ 1,
+ Animation::Opacity));
+
+ scoped_ptr<Animation> to_add(CreateAnimation(
+ scoped_ptr<AnimationCurve>(
+ new FakeFloatTransition(2.0, 0.5f, 0.f)).Pass(),
+ 2,
+ Animation::Opacity));
+ to_add->SetRunState(Animation::WaitingForStartTime, 0);
+ to_add->set_start_time(1.f);
+ controller->AddAnimation(to_add.Pass());
+
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(
+ new FakeFloatTransition(1.0, 0.f, 0.75f)).Pass(),
+ 3,
+ Animation::Opacity));
+
+ // First 2s opacity transition should start immediately.
+ controller->Animate(0.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ controller->Animate(0.5);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.25f, dummy.opacity());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ controller->Animate(1.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.5f, dummy.opacity());
+ controller->Animate(3.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ controller->Animate(4.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_EQ(0.75f, dummy.opacity());
+ EXPECT_FALSE(controller->HasActiveAnimation());
+}
+
+// Test that a looping animation loops and for the correct number of iterations.
+TEST(LayerAnimationControllerTest, TrivialLooping) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddValueObserver(&dummy);
+
+ scoped_ptr<Animation> to_add(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)).Pass(),
+ 1,
+ Animation::Opacity));
+ to_add->set_iterations(3);
+ controller->AddAnimation(to_add.Pass());
+
+ controller->Animate(0.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ controller->Animate(1.25);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.25f, dummy.opacity());
+ controller->Animate(1.75);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.75f, dummy.opacity());
+ controller->Animate(2.25);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.25f, dummy.opacity());
+ controller->Animate(2.75);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.75f, dummy.opacity());
+ controller->Animate(3.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_FALSE(controller->HasActiveAnimation());
+ EXPECT_EQ(1.f, dummy.opacity());
+
+ // Just be extra sure.
+ controller->Animate(4.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_EQ(1.f, dummy.opacity());
+}
+
+// Test that an infinitely looping animation does indeed go until aborted.
+TEST(LayerAnimationControllerTest, InfiniteLooping) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddValueObserver(&dummy);
+
+ const int id = 1;
+ scoped_ptr<Animation> to_add(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)).Pass(),
+ id,
+ Animation::Opacity));
+ to_add->set_iterations(-1);
+ controller->AddAnimation(to_add.Pass());
+
+ controller->Animate(0.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ controller->Animate(1.25);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.25f, dummy.opacity());
+ controller->Animate(1.75);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.75f, dummy.opacity());
+
+ controller->Animate(1073741824.25);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.25f, dummy.opacity());
+ controller->Animate(1073741824.75);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.75f, dummy.opacity());
+
+ EXPECT_TRUE(controller->GetAnimation(id, Animation::Opacity));
+ controller->GetAnimation(id, Animation::Opacity)->SetRunState(
+ Animation::Aborted, 0.75);
+ EXPECT_FALSE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.75f, dummy.opacity());
+}
+
+// Test that pausing and resuming work as expected.
+TEST(LayerAnimationControllerTest, PauseResume) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddValueObserver(&dummy);
+
+ const int id = 1;
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)).Pass(),
+ id,
+ Animation::Opacity));
+
+ controller->Animate(0.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ controller->Animate(0.5);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.5f, dummy.opacity());
+
+ EXPECT_TRUE(controller->GetAnimation(id, Animation::Opacity));
+ controller->GetAnimation(id, Animation::Opacity)->SetRunState(
+ Animation::Paused, 0.5);
+
+ controller->Animate(1024);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.5f, dummy.opacity());
+
+ EXPECT_TRUE(controller->GetAnimation(id, Animation::Opacity));
+ controller->GetAnimation(id, Animation::Opacity)->SetRunState(
+ Animation::Running, 1024);
+
+ controller->Animate(1024.25);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.75f, dummy.opacity());
+ controller->Animate(1024.5);
+ controller->UpdateState(true, events.get());
+ EXPECT_FALSE(controller->HasActiveAnimation());
+ EXPECT_EQ(1.f, dummy.opacity());
+}
+
+TEST(LayerAnimationControllerTest, AbortAGroupedAnimation) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddValueObserver(&dummy);
+
+ const int id = 1;
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeTransformTransition(1)).Pass(),
+ id,
+ Animation::Transform));
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(2.0, 0.f, 1.f)).Pass(),
+ id,
+ Animation::Opacity));
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(
+ new FakeFloatTransition(1.0, 1.f, 0.75f)).Pass(),
+ 2,
+ Animation::Opacity));
+
+ controller->Animate(0.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+ controller->Animate(1.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.5f, dummy.opacity());
+
+ EXPECT_TRUE(controller->GetAnimation(id, Animation::Opacity));
+ controller->GetAnimation(id, Animation::Opacity)->SetRunState(
+ Animation::Aborted, 1);
+ controller->Animate(1.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(1.f, dummy.opacity());
+ controller->Animate(2.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(!controller->HasActiveAnimation());
+ EXPECT_EQ(0.75f, dummy.opacity());
+}
+
+TEST(LayerAnimationControllerTest, ForceSyncWhenSynchronizedStartTimeNeeded) {
+ FakeLayerAnimationValueObserver dummy_impl;
+ scoped_refptr<LayerAnimationController> controller_impl(
+ LayerAnimationController::Create(0));
+ controller_impl->AddValueObserver(&dummy_impl);
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddValueObserver(&dummy);
+
+ scoped_ptr<Animation> to_add(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(2.0, 0.f, 1.f)).Pass(),
+ 0,
+ Animation::Opacity));
+ to_add->set_needs_synchronized_start_time(true);
+ controller->AddAnimation(to_add.Pass());
+
+ controller->Animate(0.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ Animation* active_animation = controller->GetAnimation(0, Animation::Opacity);
+ EXPECT_TRUE(active_animation);
+ EXPECT_TRUE(active_animation->needs_synchronized_start_time());
+
+ controller->set_force_sync();
+
+ controller->PushAnimationUpdatesTo(controller_impl.get());
+
+ active_animation = controller_impl->GetAnimation(0, Animation::Opacity);
+ EXPECT_TRUE(active_animation);
+ EXPECT_EQ(Animation::WaitingForTargetAvailability,
+ active_animation->run_state());
+}
+
+// Tests that skipping a call to UpdateState works as expected.
+TEST(LayerAnimationControllerTest, SkipUpdateState) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+ controller->AddValueObserver(&dummy);
+
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeTransformTransition(1)).Pass(),
+ 1,
+ Animation::Transform));
+
+ controller->Animate(0.0);
+ controller->UpdateState(true, events.get());
+
+ controller->AddAnimation(CreateAnimation(
+ scoped_ptr<AnimationCurve>(new FakeFloatTransition(1.0, 0.f, 1.f)).Pass(),
+ 2,
+ Animation::Opacity));
+
+ // Animate but don't UpdateState.
+ controller->Animate(1.0);
+
+ controller->Animate(2.0);
+ events.reset(new AnimationEventsVector);
+ controller->UpdateState(true, events.get());
+
+ // Should have one Started event and one Finished event.
+ EXPECT_EQ(2u, events->size());
+ EXPECT_NE((*events)[0].type, (*events)[1].type);
+
+ // The float transition should still be at its starting point.
+ EXPECT_TRUE(controller->HasActiveAnimation());
+ EXPECT_EQ(0.f, dummy.opacity());
+
+ controller->Animate(3.0);
+ controller->UpdateState(true, events.get());
+
+ // The float tranisition should now be done.
+ EXPECT_EQ(1.f, dummy.opacity());
+ EXPECT_FALSE(controller->HasActiveAnimation());
+}
+
+// Tests that an animation controller with only an inactive observer gets ticked
+// but doesn't progress animations past the Starting state.
+TEST(LayerAnimationControllerTest, InactiveObserverGetsTicked) {
+ scoped_ptr<AnimationEventsVector> events(
+ make_scoped_ptr(new AnimationEventsVector));
+ FakeLayerAnimationValueObserver dummy;
+ FakeInactiveLayerAnimationValueObserver inactive_dummy;
+ scoped_refptr<LayerAnimationController> controller(
+ LayerAnimationController::Create(0));
+
+ const int id = 1;
+ controller->AddAnimation(CreateAnimation(scoped_ptr<AnimationCurve>(
+ new FakeFloatTransition(1.0, 0.5f, 1.f)).Pass(),
+ id,
+ Animation::Opacity));
+
+ // Without an observer, the animation shouldn't progress to the Starting
+ // state.
+ controller->Animate(0.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_EQ(0u, events->size());
+ EXPECT_EQ(Animation::WaitingForTargetAvailability,
+ controller->GetAnimation(id, Animation::Opacity)->run_state());
+
+ controller->AddValueObserver(&inactive_dummy);
+
+ // With only an inactive observer, the animation should progress to the
+ // Starting state and get ticked at its starting point, but should not
+ // progress to Running.
+ controller->Animate(1.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_EQ(0u, events->size());
+ EXPECT_EQ(Animation::Starting,
+ controller->GetAnimation(id, Animation::Opacity)->run_state());
+ EXPECT_EQ(0.5f, inactive_dummy.opacity());
+
+ // Even when already in the Starting state, the animation should stay
+ // there, and shouldn't be ticked past its starting point.
+ controller->Animate(2.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_EQ(0u, events->size());
+ EXPECT_EQ(Animation::Starting,
+ controller->GetAnimation(id, Animation::Opacity)->run_state());
+ EXPECT_EQ(0.5f, inactive_dummy.opacity());
+
+ controller->AddValueObserver(&dummy);
+
+ // Now that an active observer has been added, the animation should still
+ // initially tick at its starting point, but should now progress to Running.
+ controller->Animate(3.0);
+ controller->UpdateState(true, events.get());
+ EXPECT_EQ(1u, events->size());
+ EXPECT_EQ(Animation::Running,
+ controller->GetAnimation(id, Animation::Opacity)->run_state());
+ EXPECT_EQ(0.5f, inactive_dummy.opacity());
+ EXPECT_EQ(0.5f, dummy.opacity());
+
+ // The animation should now tick past its starting point.
+ controller->Animate(3.5);
+ EXPECT_NE(0.5f, inactive_dummy.opacity());
+ EXPECT_NE(0.5f, dummy.opacity());
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/animation/layer_animation_event_observer.h b/chromium/cc/animation/layer_animation_event_observer.h
new file mode 100644
index 00000000000..9f7f04bbfea
--- /dev/null
+++ b/chromium/cc/animation/layer_animation_event_observer.h
@@ -0,0 +1,23 @@
+// 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.
+
+#ifndef CC_ANIMATION_LAYER_ANIMATION_EVENT_OBSERVER_H_
+#define CC_ANIMATION_LAYER_ANIMATION_EVENT_OBSERVER_H_
+
+#include "cc/base/cc_export.h"
+
+namespace cc {
+
+class CC_EXPORT LayerAnimationEventObserver {
+ public:
+ virtual void OnAnimationStarted(const AnimationEvent& event) = 0;
+
+ protected:
+ virtual ~LayerAnimationEventObserver() {}
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_LAYER_ANIMATION_EVENT_OBSERVER_H_
+
diff --git a/chromium/cc/animation/layer_animation_value_observer.h b/chromium/cc/animation/layer_animation_value_observer.h
new file mode 100644
index 00000000000..ca3cafaf129
--- /dev/null
+++ b/chromium/cc/animation/layer_animation_value_observer.h
@@ -0,0 +1,24 @@
+// 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.
+
+#ifndef CC_ANIMATION_LAYER_ANIMATION_VALUE_OBSERVER_H_
+#define CC_ANIMATION_LAYER_ANIMATION_VALUE_OBSERVER_H_
+
+#include "cc/base/cc_export.h"
+
+namespace cc {
+
+class CC_EXPORT LayerAnimationValueObserver {
+ public:
+ virtual ~LayerAnimationValueObserver() {}
+
+ virtual void OnOpacityAnimated(float opacity) = 0;
+ virtual void OnTransformAnimated(const gfx::Transform& transform) = 0;
+ virtual bool IsActive() const = 0;
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_LAYER_ANIMATION_VALUE_OBSERVER_H_
+
diff --git a/chromium/cc/animation/scrollbar_animation_controller.h b/chromium/cc/animation/scrollbar_animation_controller.h
new file mode 100644
index 00000000000..55d29457689
--- /dev/null
+++ b/chromium/cc/animation/scrollbar_animation_controller.h
@@ -0,0 +1,33 @@
+// 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.
+
+#ifndef CC_ANIMATION_SCROLLBAR_ANIMATION_CONTROLLER_H_
+#define CC_ANIMATION_SCROLLBAR_ANIMATION_CONTROLLER_H_
+
+#include "base/time/time.h"
+#include "cc/base/cc_export.h"
+#include "ui/gfx/vector2d_f.h"
+
+namespace cc {
+
+// This abstract class represents the compositor-side analogy of
+// ScrollbarAnimator. Individual platforms should subclass it to provide
+// specialized implementation.
+class CC_EXPORT ScrollbarAnimationController {
+ public:
+ virtual ~ScrollbarAnimationController() {}
+
+ virtual bool IsScrollGestureInProgress() const = 0;
+ virtual bool IsAnimating() const = 0;
+ virtual base::TimeDelta DelayBeforeStart(base::TimeTicks now) const = 0;
+
+ virtual bool Animate(base::TimeTicks now) = 0;
+ virtual void DidScrollGestureBegin() = 0;
+ virtual void DidScrollGestureEnd(base::TimeTicks now) = 0;
+ virtual void DidProgrammaticallyUpdateScroll(base::TimeTicks now) = 0;
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_SCROLLBAR_ANIMATION_CONTROLLER_H_
diff --git a/chromium/cc/animation/scrollbar_animation_controller_linear_fade.cc b/chromium/cc/animation/scrollbar_animation_controller_linear_fade.cc
new file mode 100644
index 00000000000..53827120a5c
--- /dev/null
+++ b/chromium/cc/animation/scrollbar_animation_controller_linear_fade.cc
@@ -0,0 +1,95 @@
+// 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 "cc/animation/scrollbar_animation_controller_linear_fade.h"
+
+#include "base/time/time.h"
+#include "cc/layers/layer_impl.h"
+
+namespace cc {
+
+scoped_ptr<ScrollbarAnimationControllerLinearFade>
+ScrollbarAnimationControllerLinearFade::Create(LayerImpl* scroll_layer,
+ base::TimeDelta fadeout_delay,
+ base::TimeDelta fadeout_length) {
+ return make_scoped_ptr(new ScrollbarAnimationControllerLinearFade(
+ scroll_layer, fadeout_delay, fadeout_length));
+}
+
+ScrollbarAnimationControllerLinearFade::ScrollbarAnimationControllerLinearFade(
+ LayerImpl* scroll_layer,
+ base::TimeDelta fadeout_delay,
+ base::TimeDelta fadeout_length)
+ : ScrollbarAnimationController(),
+ scroll_layer_(scroll_layer),
+ scroll_gesture_in_progress_(false),
+ fadeout_delay_(fadeout_delay),
+ fadeout_length_(fadeout_length) {}
+
+ScrollbarAnimationControllerLinearFade::
+ ~ScrollbarAnimationControllerLinearFade() {}
+
+bool ScrollbarAnimationControllerLinearFade::IsScrollGestureInProgress() const {
+ return scroll_gesture_in_progress_;
+}
+
+bool ScrollbarAnimationControllerLinearFade::IsAnimating() const {
+ return !last_awaken_time_.is_null();
+}
+
+base::TimeDelta ScrollbarAnimationControllerLinearFade::DelayBeforeStart(
+ base::TimeTicks now) const {
+ if (now > last_awaken_time_ + fadeout_delay_)
+ return base::TimeDelta();
+ return fadeout_delay_ - (now - last_awaken_time_);
+}
+
+bool ScrollbarAnimationControllerLinearFade::Animate(base::TimeTicks now) {
+ float opacity = OpacityAtTime(now);
+ scroll_layer_->SetScrollbarOpacity(opacity);
+ if (!opacity)
+ last_awaken_time_ = base::TimeTicks();
+ return IsAnimating() && DelayBeforeStart(now) == base::TimeDelta();
+}
+
+void ScrollbarAnimationControllerLinearFade::DidScrollGestureBegin() {
+ scroll_layer_->SetScrollbarOpacity(1.0f);
+ scroll_gesture_in_progress_ = true;
+ last_awaken_time_ = base::TimeTicks();
+}
+
+void ScrollbarAnimationControllerLinearFade::DidScrollGestureEnd(
+ base::TimeTicks now) {
+ scroll_gesture_in_progress_ = false;
+ last_awaken_time_ = now;
+}
+
+void ScrollbarAnimationControllerLinearFade::DidProgrammaticallyUpdateScroll(
+ base::TimeTicks now) {
+ // Don't set scroll_gesture_in_progress_ as this scroll is not from a gesture
+ // and we won't receive ScrollEnd.
+ scroll_layer_->SetScrollbarOpacity(1.0f);
+ last_awaken_time_ = now;
+}
+
+float ScrollbarAnimationControllerLinearFade::OpacityAtTime(
+ base::TimeTicks now) {
+ if (scroll_gesture_in_progress_)
+ return 1.0f;
+
+ if (last_awaken_time_.is_null())
+ return 0.0f;
+
+ base::TimeDelta delta = now - last_awaken_time_;
+
+ if (delta <= fadeout_delay_)
+ return 1.0f;
+ if (delta < fadeout_delay_ + fadeout_length_) {
+ return (fadeout_delay_ + fadeout_length_ - delta).InSecondsF() /
+ fadeout_length_.InSecondsF();
+ }
+ return 0.0f;
+}
+
+} // namespace cc
diff --git a/chromium/cc/animation/scrollbar_animation_controller_linear_fade.h b/chromium/cc/animation/scrollbar_animation_controller_linear_fade.h
new file mode 100644
index 00000000000..9ecb3c1617c
--- /dev/null
+++ b/chromium/cc/animation/scrollbar_animation_controller_linear_fade.h
@@ -0,0 +1,56 @@
+// 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.
+
+#ifndef CC_ANIMATION_SCROLLBAR_ANIMATION_CONTROLLER_LINEAR_FADE_H_
+#define CC_ANIMATION_SCROLLBAR_ANIMATION_CONTROLLER_LINEAR_FADE_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/animation/scrollbar_animation_controller.h"
+#include "cc/base/cc_export.h"
+
+namespace cc {
+class LayerImpl;
+
+class CC_EXPORT ScrollbarAnimationControllerLinearFade
+ : public ScrollbarAnimationController {
+ public:
+ static scoped_ptr<ScrollbarAnimationControllerLinearFade> Create(
+ LayerImpl* scroll_layer,
+ base::TimeDelta fadeout_delay,
+ base::TimeDelta fadeout_length);
+
+ virtual ~ScrollbarAnimationControllerLinearFade();
+
+ // ScrollbarAnimationController overrides.
+ virtual bool IsScrollGestureInProgress() const OVERRIDE;
+ virtual bool IsAnimating() const OVERRIDE;
+ virtual base::TimeDelta DelayBeforeStart(base::TimeTicks now) const OVERRIDE;
+
+ virtual bool Animate(base::TimeTicks now) OVERRIDE;
+ virtual void DidScrollGestureBegin() OVERRIDE;
+ virtual void DidScrollGestureEnd(base::TimeTicks now) OVERRIDE;
+ virtual void DidProgrammaticallyUpdateScroll(base::TimeTicks now) OVERRIDE;
+
+ protected:
+ ScrollbarAnimationControllerLinearFade(LayerImpl* scroll_layer,
+ base::TimeDelta fadeout_delay,
+ base::TimeDelta fadeout_length);
+
+ private:
+ float OpacityAtTime(base::TimeTicks now);
+
+ LayerImpl* scroll_layer_;
+
+ base::TimeTicks last_awaken_time_;
+ bool scroll_gesture_in_progress_;
+
+ base::TimeDelta fadeout_delay_;
+ base::TimeDelta fadeout_length_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScrollbarAnimationControllerLinearFade);
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_SCROLLBAR_ANIMATION_CONTROLLER_LINEAR_FADE_H_
diff --git a/chromium/cc/animation/scrollbar_animation_controller_linear_fade_unittest.cc b/chromium/cc/animation/scrollbar_animation_controller_linear_fade_unittest.cc
new file mode 100644
index 00000000000..a8365e8b4de
--- /dev/null
+++ b/chromium/cc/animation/scrollbar_animation_controller_linear_fade_unittest.cc
@@ -0,0 +1,161 @@
+// 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 "cc/animation/scrollbar_animation_controller_linear_fade.h"
+
+#include "cc/layers/scrollbar_layer_impl.h"
+#include "cc/test/fake_impl_proxy.h"
+#include "cc/test/fake_layer_tree_host_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+class ScrollbarAnimationControllerLinearFadeTest : public testing::Test {
+ public:
+ ScrollbarAnimationControllerLinearFadeTest() : host_impl_(&proxy_) {}
+
+ protected:
+ virtual void SetUp() {
+ scroll_layer_ = LayerImpl::Create(host_impl_.active_tree(), 1);
+ scrollbar_layer_ = ScrollbarLayerImpl::Create(
+ host_impl_.active_tree(), 2, HORIZONTAL);
+
+ scroll_layer_->SetMaxScrollOffset(gfx::Vector2d(50, 50));
+ scroll_layer_->SetBounds(gfx::Size(50, 50));
+ scroll_layer_->SetHorizontalScrollbarLayer(scrollbar_layer_.get());
+
+ scrollbar_controller_ = ScrollbarAnimationControllerLinearFade::Create(
+ scroll_layer_.get(),
+ base::TimeDelta::FromSeconds(2),
+ base::TimeDelta::FromSeconds(3));
+ }
+
+ FakeImplProxy proxy_;
+ FakeLayerTreeHostImpl host_impl_;
+ scoped_ptr<ScrollbarAnimationControllerLinearFade> scrollbar_controller_;
+ scoped_ptr<LayerImpl> scroll_layer_;
+ scoped_ptr<ScrollbarLayerImpl> scrollbar_layer_;
+};
+
+TEST_F(ScrollbarAnimationControllerLinearFadeTest, HiddenInBegin) {
+ scrollbar_controller_->Animate(base::TimeTicks());
+ EXPECT_FLOAT_EQ(0.0f, scrollbar_layer_->opacity());
+}
+
+TEST_F(ScrollbarAnimationControllerLinearFadeTest, AwakenByScrollGesture) {
+ base::TimeTicks time;
+ time += base::TimeDelta::FromSeconds(1);
+ scrollbar_controller_->DidScrollGestureBegin();
+ EXPECT_TRUE(scrollbar_controller_->IsScrollGestureInProgress());
+ EXPECT_FALSE(scrollbar_controller_->IsAnimating());
+ EXPECT_FLOAT_EQ(1.0f, scrollbar_layer_->opacity());
+
+ time += base::TimeDelta::FromSeconds(100);
+ scrollbar_controller_->Animate(time);
+ EXPECT_FLOAT_EQ(1.0f, scrollbar_layer_->opacity());
+ scrollbar_controller_->DidScrollGestureEnd(time);
+
+ EXPECT_FALSE(scrollbar_controller_->IsScrollGestureInProgress());
+ EXPECT_TRUE(scrollbar_controller_->IsAnimating());
+ EXPECT_EQ(2, scrollbar_controller_->DelayBeforeStart(time).InSeconds());
+
+ time += base::TimeDelta::FromSeconds(1);
+ scrollbar_controller_->Animate(time);
+ EXPECT_FLOAT_EQ(1.0f, scrollbar_layer_->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ scrollbar_controller_->Animate(time);
+ EXPECT_FLOAT_EQ(1.0f, scrollbar_layer_->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ scrollbar_controller_->Animate(time);
+ EXPECT_FLOAT_EQ(2.0f / 3.0f, scrollbar_layer_->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ scrollbar_controller_->Animate(time);
+ EXPECT_FLOAT_EQ(1.0f / 3.0f, scrollbar_layer_->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+
+ scrollbar_controller_->DidScrollGestureBegin();
+ scrollbar_controller_->DidScrollGestureEnd(time);
+
+ time += base::TimeDelta::FromSeconds(1);
+ scrollbar_controller_->Animate(time);
+ EXPECT_FLOAT_EQ(1.0f, scrollbar_layer_->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ scrollbar_controller_->Animate(time);
+ EXPECT_FLOAT_EQ(1.0f, scrollbar_layer_->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ scrollbar_controller_->Animate(time);
+ EXPECT_FLOAT_EQ(2.0f / 3.0f, scrollbar_layer_->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ scrollbar_controller_->Animate(time);
+ EXPECT_FLOAT_EQ(1.0f / 3.0f, scrollbar_layer_->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ scrollbar_controller_->Animate(time);
+ EXPECT_FLOAT_EQ(0.0f, scrollbar_layer_->opacity());
+}
+
+TEST_F(ScrollbarAnimationControllerLinearFadeTest, AwakenByProgrammaticScroll) {
+ base::TimeTicks time;
+ time += base::TimeDelta::FromSeconds(1);
+ scrollbar_controller_->DidProgrammaticallyUpdateScroll(time);
+ EXPECT_FALSE(scrollbar_controller_->IsScrollGestureInProgress());
+ EXPECT_TRUE(scrollbar_controller_->IsAnimating());
+ EXPECT_EQ(2, scrollbar_controller_->DelayBeforeStart(time).InSeconds());
+ scrollbar_controller_->Animate(time);
+ EXPECT_FLOAT_EQ(1.0f, scrollbar_layer_->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ scrollbar_controller_->Animate(time);
+ EXPECT_FLOAT_EQ(1.0f, scrollbar_layer_->opacity());
+ scrollbar_controller_->DidProgrammaticallyUpdateScroll(time);
+
+ time += base::TimeDelta::FromSeconds(1);
+ scrollbar_controller_->Animate(time);
+ EXPECT_FLOAT_EQ(1.0f, scrollbar_layer_->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ scrollbar_controller_->Animate(time);
+ EXPECT_FLOAT_EQ(1.0f, scrollbar_layer_->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ scrollbar_controller_->Animate(time);
+ EXPECT_FLOAT_EQ(2.0f / 3.0f, scrollbar_layer_->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ scrollbar_controller_->Animate(time);
+ EXPECT_FLOAT_EQ(1.0f / 3.0f, scrollbar_layer_->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ scrollbar_controller_->DidProgrammaticallyUpdateScroll(time);
+ time += base::TimeDelta::FromSeconds(1);
+ scrollbar_controller_->Animate(time);
+ EXPECT_FLOAT_EQ(1.0f, scrollbar_layer_->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ scrollbar_controller_->Animate(time);
+ EXPECT_FLOAT_EQ(1.0f, scrollbar_layer_->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ scrollbar_controller_->Animate(time);
+ EXPECT_FLOAT_EQ(2.0f / 3.0f, scrollbar_layer_->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ scrollbar_controller_->Animate(time);
+ EXPECT_FLOAT_EQ(1.0f / 3.0f, scrollbar_layer_->opacity());
+
+ time += base::TimeDelta::FromSeconds(1);
+ scrollbar_controller_->Animate(time);
+ EXPECT_FLOAT_EQ(0.0f, scrollbar_layer_->opacity());
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/animation/timing_function.cc b/chromium/cc/animation/timing_function.cc
new file mode 100644
index 00000000000..65d607fd573
--- /dev/null
+++ b/chromium/cc/animation/timing_function.cc
@@ -0,0 +1,118 @@
+// 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 <algorithm>
+#include <cmath>
+
+#include "base/logging.h"
+#include "cc/animation/timing_function.h"
+
+namespace cc {
+
+namespace {
+
+static const double BEZIER_EPSILON = 1e-7;
+static const int MAX_STEPS = 30;
+
+static double eval_bezier(double x1, double x2, double t) {
+ const double x1_times_3 = 3.0 * x1;
+ const double x2_times_3 = 3.0 * x2;
+ const double h3 = x1_times_3;
+ const double h1 = x1_times_3 - x2_times_3 + 1.0;
+ const double h2 = x2_times_3 - 6.0 * x1;
+ return t * (t * (t * h1 + h2) + h3);
+}
+
+static double bezier_interp(double x1,
+ double y1,
+ double x2,
+ double y2,
+ double x) {
+ DCHECK_GE(1.0, x1);
+ DCHECK_LE(0.0, x1);
+ DCHECK_GE(1.0, x2);
+ DCHECK_LE(0.0, x2);
+
+ x1 = std::min(std::max(x1, 0.0), 1.0);
+ x2 = std::min(std::max(x2, 0.0), 1.0);
+ x = std::min(std::max(x, 0.0), 1.0);
+
+ // Step 1. Find the t corresponding to the given x. I.e., we want t such that
+ // eval_bezier(x1, x2, t) = x. There is a unique solution if x1 and x2 lie
+ // within (0, 1).
+ //
+ // We're just going to do bisection for now (for simplicity), but we could
+ // easily do some newton steps if this turns out to be a bottleneck.
+ double t = 0.0;
+ double step = 1.0;
+ for (int i = 0; i < MAX_STEPS; ++i, step *= 0.5) {
+ const double error = eval_bezier(x1, x2, t) - x;
+ if (std::abs(error) < BEZIER_EPSILON)
+ break;
+ t += error > 0.0 ? -step : step;
+ }
+
+ // We should have terminated the above loop because we got close to x, not
+ // because we exceeded MAX_STEPS. Do a DCHECK here to confirm.
+ DCHECK_GT(BEZIER_EPSILON, std::abs(eval_bezier(x1, x2, t) - x));
+
+ // Step 2. Return the interpolated y values at the t we computed above.
+ return eval_bezier(y1, y2, t);
+}
+
+} // namespace
+
+TimingFunction::TimingFunction() {}
+
+TimingFunction::~TimingFunction() {}
+
+double TimingFunction::Duration() const {
+ return 1.0;
+}
+
+scoped_ptr<CubicBezierTimingFunction> CubicBezierTimingFunction::Create(
+ double x1, double y1, double x2, double y2) {
+ return make_scoped_ptr(new CubicBezierTimingFunction(x1, y1, x2, y2));
+}
+
+CubicBezierTimingFunction::CubicBezierTimingFunction(double x1,
+ double y1,
+ double x2,
+ double y2)
+ : x1_(x1), y1_(y1), x2_(x2), y2_(y2) {}
+
+CubicBezierTimingFunction::~CubicBezierTimingFunction() {}
+
+float CubicBezierTimingFunction::GetValue(double x) const {
+ return static_cast<float>(bezier_interp(x1_, y1_, x2_, y2_, x));
+}
+
+scoped_ptr<AnimationCurve> CubicBezierTimingFunction::Clone() const {
+ return make_scoped_ptr(
+ new CubicBezierTimingFunction(*this)).PassAs<AnimationCurve>();
+}
+
+// These numbers come from
+// http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag.
+scoped_ptr<TimingFunction> EaseTimingFunction::Create() {
+ return CubicBezierTimingFunction::Create(
+ 0.25, 0.1, 0.25, 1.0).PassAs<TimingFunction>();
+}
+
+scoped_ptr<TimingFunction> EaseInTimingFunction::Create() {
+ return CubicBezierTimingFunction::Create(
+ 0.42, 0.0, 1.0, 1.0).PassAs<TimingFunction>();
+}
+
+scoped_ptr<TimingFunction> EaseOutTimingFunction::Create() {
+ return CubicBezierTimingFunction::Create(
+ 0.0, 0.0, 0.58, 1.0).PassAs<TimingFunction>();
+}
+
+scoped_ptr<TimingFunction> EaseInOutTimingFunction::Create() {
+ return CubicBezierTimingFunction::Create(
+ 0.42, 0.0, 0.58, 1).PassAs<TimingFunction>();
+}
+
+} // namespace cc
diff --git a/chromium/cc/animation/timing_function.h b/chromium/cc/animation/timing_function.h
new file mode 100644
index 00000000000..3aa2f253d81
--- /dev/null
+++ b/chromium/cc/animation/timing_function.h
@@ -0,0 +1,85 @@
+// 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.
+
+#ifndef CC_ANIMATION_TIMING_FUNCTION_H_
+#define CC_ANIMATION_TIMING_FUNCTION_H_
+
+#include "cc/animation/animation_curve.h"
+#include "cc/base/cc_export.h"
+#include "third_party/skia/include/core/SkScalar.h"
+
+namespace cc {
+
+// See http://www.w3.org/TR/css3-transitions/.
+class CC_EXPORT TimingFunction : public FloatAnimationCurve {
+ public:
+ virtual ~TimingFunction();
+
+ // Partial implementation of FloatAnimationCurve.
+ virtual double Duration() const OVERRIDE;
+
+ protected:
+ TimingFunction();
+
+ private:
+ DISALLOW_ASSIGN(TimingFunction);
+};
+
+class CC_EXPORT CubicBezierTimingFunction : public TimingFunction {
+ public:
+ static scoped_ptr<CubicBezierTimingFunction> Create(double x1, double y1,
+ double x2, double y2);
+ virtual ~CubicBezierTimingFunction();
+
+ // Partial implementation of FloatAnimationCurve.
+ virtual float GetValue(double time) const OVERRIDE;
+ virtual scoped_ptr<AnimationCurve> Clone() const OVERRIDE;
+
+ protected:
+ CubicBezierTimingFunction(double x1, double y1, double x2, double y2);
+
+ double x1_;
+ double y1_;
+ double x2_;
+ double y2_;
+
+ private:
+ DISALLOW_ASSIGN(CubicBezierTimingFunction);
+};
+
+class CC_EXPORT EaseTimingFunction {
+ public:
+ static scoped_ptr<TimingFunction> Create();
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(EaseTimingFunction);
+};
+
+class CC_EXPORT EaseInTimingFunction {
+ public:
+ static scoped_ptr<TimingFunction> Create();
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(EaseInTimingFunction);
+};
+
+class CC_EXPORT EaseOutTimingFunction {
+ public:
+ static scoped_ptr<TimingFunction> Create();
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(EaseOutTimingFunction);
+};
+
+class CC_EXPORT EaseInOutTimingFunction {
+ public:
+ static scoped_ptr<TimingFunction> Create();
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(EaseInOutTimingFunction);
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_TIMING_FUNCTION_H_
diff --git a/chromium/cc/animation/timing_function_unittest.cc b/chromium/cc/animation/timing_function_unittest.cc
new file mode 100644
index 00000000000..2caa12d0772
--- /dev/null
+++ b/chromium/cc/animation/timing_function_unittest.cc
@@ -0,0 +1,71 @@
+// Copyright 2011 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 "cc/animation/timing_function.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+TEST(TimingFunctionTest, CubicBezierTimingFunction) {
+ scoped_ptr<CubicBezierTimingFunction> function =
+ CubicBezierTimingFunction::Create(0.25, 0.0, 0.75, 1.0);
+
+ double epsilon = 0.00015;
+
+ EXPECT_NEAR(function->GetValue(0), 0, epsilon);
+ EXPECT_NEAR(function->GetValue(0.05), 0.01136, epsilon);
+ EXPECT_NEAR(function->GetValue(0.1), 0.03978, epsilon);
+ EXPECT_NEAR(function->GetValue(0.15), 0.079780, epsilon);
+ EXPECT_NEAR(function->GetValue(0.2), 0.12803, epsilon);
+ EXPECT_NEAR(function->GetValue(0.25), 0.18235, epsilon);
+ EXPECT_NEAR(function->GetValue(0.3), 0.24115, epsilon);
+ EXPECT_NEAR(function->GetValue(0.35), 0.30323, epsilon);
+ EXPECT_NEAR(function->GetValue(0.4), 0.36761, epsilon);
+ EXPECT_NEAR(function->GetValue(0.45), 0.43345, epsilon);
+ EXPECT_NEAR(function->GetValue(0.5), 0.5, epsilon);
+ EXPECT_NEAR(function->GetValue(0.6), 0.63238, epsilon);
+ EXPECT_NEAR(function->GetValue(0.65), 0.69676, epsilon);
+ EXPECT_NEAR(function->GetValue(0.7), 0.75884, epsilon);
+ EXPECT_NEAR(function->GetValue(0.75), 0.81764, epsilon);
+ EXPECT_NEAR(function->GetValue(0.8), 0.87196, epsilon);
+ EXPECT_NEAR(function->GetValue(0.85), 0.92021, epsilon);
+ EXPECT_NEAR(function->GetValue(0.9), 0.96021, epsilon);
+ EXPECT_NEAR(function->GetValue(0.95), 0.98863, epsilon);
+ EXPECT_NEAR(function->GetValue(1), 1, epsilon);
+}
+
+// Tests that the bezier timing function works with knots with y not in (0, 1).
+TEST(TimingFunctionTest, CubicBezierTimingFunctionUnclampedYValues) {
+ scoped_ptr<CubicBezierTimingFunction> function =
+ CubicBezierTimingFunction::Create(0.5, -1.0, 0.5, 2.0);
+
+ double epsilon = 0.00015;
+
+ EXPECT_NEAR(function->GetValue(0.0), 0.0, epsilon);
+ EXPECT_NEAR(function->GetValue(0.05), -0.08954, epsilon);
+ EXPECT_NEAR(function->GetValue(0.1), -0.15613, epsilon);
+ EXPECT_NEAR(function->GetValue(0.15), -0.19641, epsilon);
+ EXPECT_NEAR(function->GetValue(0.2), -0.20651, epsilon);
+ EXPECT_NEAR(function->GetValue(0.25), -0.18232, epsilon);
+ EXPECT_NEAR(function->GetValue(0.3), -0.11992, epsilon);
+ EXPECT_NEAR(function->GetValue(0.35), -0.01672, epsilon);
+ EXPECT_NEAR(function->GetValue(0.4), 0.12660, epsilon);
+ EXPECT_NEAR(function->GetValue(0.45), 0.30349, epsilon);
+ EXPECT_NEAR(function->GetValue(0.5), 0.50000, epsilon);
+ EXPECT_NEAR(function->GetValue(0.55), 0.69651, epsilon);
+ EXPECT_NEAR(function->GetValue(0.6), 0.87340, epsilon);
+ EXPECT_NEAR(function->GetValue(0.65), 1.01672, epsilon);
+ EXPECT_NEAR(function->GetValue(0.7), 1.11992, epsilon);
+ EXPECT_NEAR(function->GetValue(0.75), 1.18232, epsilon);
+ EXPECT_NEAR(function->GetValue(0.8), 1.20651, epsilon);
+ EXPECT_NEAR(function->GetValue(0.85), 1.19641, epsilon);
+ EXPECT_NEAR(function->GetValue(0.9), 1.15613, epsilon);
+ EXPECT_NEAR(function->GetValue(0.95), 1.08954, epsilon);
+ EXPECT_NEAR(function->GetValue(1.0), 1.0, epsilon);
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/animation/transform_operation.cc b/chromium/cc/animation/transform_operation.cc
new file mode 100644
index 00000000000..dacea06d797
--- /dev/null
+++ b/chromium/cc/animation/transform_operation.cc
@@ -0,0 +1,180 @@
+// Copyright 2013 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 <cmath>
+#include <limits>
+
+#include "cc/animation/transform_operation.h"
+#include "ui/gfx/vector3d_f.h"
+
+namespace {
+const double kAngleEpsilon = 1e-4;
+}
+
+namespace cc {
+
+bool TransformOperation::IsIdentity() const {
+ return matrix.IsIdentity();
+}
+
+static bool IsOperationIdentity(const TransformOperation* operation) {
+ return !operation || operation->IsIdentity();
+}
+
+static bool ShareSameAxis(const TransformOperation* from,
+ const TransformOperation* to,
+ double* axis_x,
+ double* axis_y,
+ double* axis_z,
+ double* angle_from) {
+ if (IsOperationIdentity(from) && IsOperationIdentity(to))
+ return false;
+
+ if (IsOperationIdentity(from) && !IsOperationIdentity(to)) {
+ *axis_x = to->rotate.axis.x;
+ *axis_y = to->rotate.axis.y;
+ *axis_z = to->rotate.axis.z;
+ *angle_from = 0;
+ return true;
+ }
+
+ if (!IsOperationIdentity(from) && IsOperationIdentity(to)) {
+ *axis_x = from->rotate.axis.x;
+ *axis_y = from->rotate.axis.y;
+ *axis_z = from->rotate.axis.z;
+ *angle_from = from->rotate.angle;
+ return true;
+ }
+
+ double length_2 = from->rotate.axis.x * from->rotate.axis.x +
+ from->rotate.axis.y * from->rotate.axis.y +
+ from->rotate.axis.z * from->rotate.axis.z;
+ double other_length_2 = to->rotate.axis.x * to->rotate.axis.x +
+ to->rotate.axis.y * to->rotate.axis.y +
+ to->rotate.axis.z * to->rotate.axis.z;
+
+ if (length_2 <= kAngleEpsilon || other_length_2 <= kAngleEpsilon)
+ return false;
+
+ double dot = to->rotate.axis.x * from->rotate.axis.x +
+ to->rotate.axis.y * from->rotate.axis.y +
+ to->rotate.axis.z * from->rotate.axis.z;
+ double error = std::abs(1.0 - (dot * dot) / (length_2 * other_length_2));
+ bool result = error < kAngleEpsilon;
+ if (result) {
+ *axis_x = to->rotate.axis.x;
+ *axis_y = to->rotate.axis.y;
+ *axis_z = to->rotate.axis.z;
+ // If the axes are pointing in opposite directions, we need to reverse
+ // the angle.
+ *angle_from = dot > 0 ? from->rotate.angle : -from->rotate.angle;
+ }
+ return result;
+}
+
+static double BlendDoubles(double from, double to, double progress) {
+ return from * (1 - progress) + to * progress;
+}
+
+bool TransformOperation::BlendTransformOperations(
+ const TransformOperation* from,
+ const TransformOperation* to,
+ double progress,
+ gfx::Transform* result) {
+ if (IsOperationIdentity(from) && IsOperationIdentity(to))
+ return true;
+
+ TransformOperation::Type interpolation_type =
+ TransformOperation::TransformOperationIdentity;
+ if (IsOperationIdentity(to))
+ interpolation_type = from->type;
+ else
+ interpolation_type = to->type;
+
+ switch (interpolation_type) {
+ case TransformOperation::TransformOperationTranslate: {
+ double from_x = IsOperationIdentity(from) ? 0 : from->translate.x;
+ double from_y = IsOperationIdentity(from) ? 0 : from->translate.y;
+ double from_z = IsOperationIdentity(from) ? 0 : from->translate.z;
+ double to_x = IsOperationIdentity(to) ? 0 : to->translate.x;
+ double to_y = IsOperationIdentity(to) ? 0 : to->translate.y;
+ double to_z = IsOperationIdentity(to) ? 0 : to->translate.z;
+ result->Translate3d(BlendDoubles(from_x, to_x, progress),
+ BlendDoubles(from_y, to_y, progress),
+ BlendDoubles(from_z, to_z, progress));
+ break;
+ }
+ case TransformOperation::TransformOperationRotate: {
+ double axis_x = 0;
+ double axis_y = 0;
+ double axis_z = 1;
+ double from_angle = 0;
+ double to_angle = IsOperationIdentity(to) ? 0 : to->rotate.angle;
+ if (ShareSameAxis(from, to, &axis_x, &axis_y, &axis_z, &from_angle)) {
+ result->RotateAbout(gfx::Vector3dF(axis_x, axis_y, axis_z),
+ BlendDoubles(from_angle, to_angle, progress));
+ } else {
+ gfx::Transform to_matrix;
+ if (!IsOperationIdentity(to))
+ to_matrix = to->matrix;
+ gfx::Transform from_matrix;
+ if (!IsOperationIdentity(from))
+ from_matrix = from->matrix;
+ *result = to_matrix;
+ if (!result->Blend(from_matrix, progress))
+ return false;
+ }
+ break;
+ }
+ case TransformOperation::TransformOperationScale: {
+ double from_x = IsOperationIdentity(from) ? 1 : from->scale.x;
+ double from_y = IsOperationIdentity(from) ? 1 : from->scale.y;
+ double from_z = IsOperationIdentity(from) ? 1 : from->scale.z;
+ double to_x = IsOperationIdentity(to) ? 1 : to->scale.x;
+ double to_y = IsOperationIdentity(to) ? 1 : to->scale.y;
+ double to_z = IsOperationIdentity(to) ? 1 : to->scale.z;
+ result->Scale3d(BlendDoubles(from_x, to_x, progress),
+ BlendDoubles(from_y, to_y, progress),
+ BlendDoubles(from_z, to_z, progress));
+ break;
+ }
+ case TransformOperation::TransformOperationSkew: {
+ double from_x = IsOperationIdentity(from) ? 0 : from->skew.x;
+ double from_y = IsOperationIdentity(from) ? 0 : from->skew.y;
+ double to_x = IsOperationIdentity(to) ? 0 : to->skew.x;
+ double to_y = IsOperationIdentity(to) ? 0 : to->skew.y;
+ result->SkewX(BlendDoubles(from_x, to_x, progress));
+ result->SkewY(BlendDoubles(from_y, to_y, progress));
+ break;
+ }
+ case TransformOperation::TransformOperationPerspective: {
+ double from_perspective_depth = IsOperationIdentity(from) ?
+ std::numeric_limits<double>::max() : from->perspective_depth;
+ double to_perspective_depth = IsOperationIdentity(to) ?
+ std::numeric_limits<double>::max() : to->perspective_depth;
+ result->ApplyPerspectiveDepth(
+ BlendDoubles(from_perspective_depth, to_perspective_depth, progress));
+ break;
+ }
+ case TransformOperation::TransformOperationMatrix: {
+ gfx::Transform to_matrix;
+ if (!IsOperationIdentity(to))
+ to_matrix = to->matrix;
+ gfx::Transform from_matrix;
+ if (!IsOperationIdentity(from))
+ from_matrix = from->matrix;
+ *result = to_matrix;
+ if (!result->Blend(from_matrix, progress))
+ return false;
+ break;
+ }
+ case TransformOperation::TransformOperationIdentity:
+ // Do nothing.
+ break;
+ }
+
+ return true;
+}
+
+} // namespace cc
diff --git a/chromium/cc/animation/transform_operation.h b/chromium/cc/animation/transform_operation.h
new file mode 100644
index 00000000000..74673ab48cb
--- /dev/null
+++ b/chromium/cc/animation/transform_operation.h
@@ -0,0 +1,63 @@
+// Copyright 2013 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.
+
+#ifndef CC_ANIMATION_TRANSFORM_OPERATION_H_
+#define CC_ANIMATION_TRANSFORM_OPERATION_H_
+
+#include "ui/gfx/transform.h"
+
+namespace cc {
+
+struct TransformOperation {
+ enum Type {
+ TransformOperationTranslate,
+ TransformOperationRotate,
+ TransformOperationScale,
+ TransformOperationSkew,
+ TransformOperationPerspective,
+ TransformOperationMatrix,
+ TransformOperationIdentity
+ };
+
+ TransformOperation()
+ : type(TransformOperationIdentity) {
+ }
+
+ Type type;
+ gfx::Transform matrix;
+
+ union {
+ double perspective_depth;
+
+ struct {
+ double x, y;
+ } skew;
+
+ struct {
+ double x, y, z;
+ } scale;
+
+ struct {
+ double x, y, z;
+ } translate;
+
+ struct {
+ struct {
+ double x, y, z;
+ } axis;
+
+ double angle;
+ } rotate;
+ };
+
+ bool IsIdentity() const;
+ static bool BlendTransformOperations(const TransformOperation* from,
+ const TransformOperation* to,
+ double progress,
+ gfx::Transform* result);
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_TRANSFORM_OPERATION_H_
diff --git a/chromium/cc/animation/transform_operations.cc b/chromium/cc/animation/transform_operations.cc
new file mode 100644
index 00000000000..d74f1a5343a
--- /dev/null
+++ b/chromium/cc/animation/transform_operations.cc
@@ -0,0 +1,193 @@
+// Copyright 2013 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 "cc/animation/transform_operations.h"
+
+#include <algorithm>
+
+#include "ui/gfx/transform_util.h"
+#include "ui/gfx/vector3d_f.h"
+
+namespace cc {
+
+TransformOperations::TransformOperations()
+ : decomposed_transform_dirty_(true) {
+}
+
+TransformOperations::TransformOperations(const TransformOperations& other) {
+ operations_ = other.operations_;
+ decomposed_transform_dirty_ = other.decomposed_transform_dirty_;
+ if (!decomposed_transform_dirty_) {
+ decomposed_transform_.reset(
+ new gfx::DecomposedTransform(*other.decomposed_transform_.get()));
+ }
+}
+
+TransformOperations::~TransformOperations() {
+}
+
+gfx::Transform TransformOperations::Apply() const {
+ gfx::Transform to_return;
+ for (size_t i = 0; i < operations_.size(); ++i)
+ to_return.PreconcatTransform(operations_[i].matrix);
+ return to_return;
+}
+
+gfx::Transform TransformOperations::Blend(
+ const TransformOperations& from, double progress) const {
+ gfx::Transform to_return;
+ BlendInternal(from, progress, &to_return);
+ return to_return;
+}
+
+bool TransformOperations::MatchesTypes(const TransformOperations& other) const {
+ if (IsIdentity() || other.IsIdentity())
+ return true;
+
+ if (operations_.size() != other.operations_.size())
+ return false;
+
+ for (size_t i = 0; i < operations_.size(); ++i) {
+ if (operations_[i].type != other.operations_[i].type
+ && !operations_[i].IsIdentity()
+ && !other.operations_[i].IsIdentity())
+ return false;
+ }
+
+ return true;
+}
+
+bool TransformOperations::CanBlendWith(
+ const TransformOperations& other) const {
+ gfx::Transform dummy;
+ return BlendInternal(other, 0.5, &dummy);
+}
+
+void TransformOperations::AppendTranslate(double x, double y, double z) {
+ TransformOperation to_add;
+ to_add.matrix.Translate3d(x, y, z);
+ to_add.type = TransformOperation::TransformOperationTranslate;
+ to_add.translate.x = x;
+ to_add.translate.y = y;
+ to_add.translate.z = z;
+ operations_.push_back(to_add);
+ decomposed_transform_dirty_ = true;
+}
+
+void TransformOperations::AppendRotate(double x, double y, double z,
+ double degrees) {
+ TransformOperation to_add;
+ to_add.matrix.RotateAbout(gfx::Vector3dF(x, y, z), degrees);
+ to_add.type = TransformOperation::TransformOperationRotate;
+ to_add.rotate.axis.x = x;
+ to_add.rotate.axis.y = y;
+ to_add.rotate.axis.z = z;
+ to_add.rotate.angle = degrees;
+ operations_.push_back(to_add);
+ decomposed_transform_dirty_ = true;
+}
+
+void TransformOperations::AppendScale(double x, double y, double z) {
+ TransformOperation to_add;
+ to_add.matrix.Scale3d(x, y, z);
+ to_add.type = TransformOperation::TransformOperationScale;
+ to_add.scale.x = x;
+ to_add.scale.y = y;
+ to_add.scale.z = z;
+ operations_.push_back(to_add);
+ decomposed_transform_dirty_ = true;
+}
+
+void TransformOperations::AppendSkew(double x, double y) {
+ TransformOperation to_add;
+ to_add.matrix.SkewX(x);
+ to_add.matrix.SkewY(y);
+ to_add.type = TransformOperation::TransformOperationSkew;
+ to_add.skew.x = x;
+ to_add.skew.y = y;
+ operations_.push_back(to_add);
+ decomposed_transform_dirty_ = true;
+}
+
+void TransformOperations::AppendPerspective(double depth) {
+ TransformOperation to_add;
+ to_add.matrix.ApplyPerspectiveDepth(depth);
+ to_add.type = TransformOperation::TransformOperationPerspective;
+ to_add.perspective_depth = depth;
+ operations_.push_back(to_add);
+ decomposed_transform_dirty_ = true;
+}
+
+void TransformOperations::AppendMatrix(const gfx::Transform& matrix) {
+ TransformOperation to_add;
+ to_add.matrix = matrix;
+ to_add.type = TransformOperation::TransformOperationMatrix;
+ operations_.push_back(to_add);
+ decomposed_transform_dirty_ = true;
+}
+
+void TransformOperations::AppendIdentity() {
+ operations_.push_back(TransformOperation());
+}
+
+bool TransformOperations::IsIdentity() const {
+ for (size_t i = 0; i < operations_.size(); ++i) {
+ if (!operations_[i].IsIdentity())
+ return false;
+ }
+ return true;
+}
+
+bool TransformOperations::BlendInternal(const TransformOperations& from,
+ double progress,
+ gfx::Transform* result) const {
+ bool from_identity = from.IsIdentity();
+ bool to_identity = IsIdentity();
+ if (from_identity && to_identity)
+ return true;
+
+ if (MatchesTypes(from)) {
+ size_t num_operations =
+ std::max(from_identity ? 0 : from.operations_.size(),
+ to_identity ? 0 : operations_.size());
+ for (size_t i = 0; i < num_operations; ++i) {
+ gfx::Transform blended;
+ if (!TransformOperation::BlendTransformOperations(
+ from_identity ? 0 : &from.operations_[i],
+ to_identity ? 0 : &operations_[i],
+ progress,
+ &blended))
+ return false;
+ result->PreconcatTransform(blended);
+ }
+ return true;
+ }
+
+ if (!ComputeDecomposedTransform() || !from.ComputeDecomposedTransform())
+ return false;
+
+ gfx::DecomposedTransform to_return;
+ if (!gfx::BlendDecomposedTransforms(&to_return,
+ *decomposed_transform_.get(),
+ *from.decomposed_transform_.get(),
+ progress))
+ return false;
+
+ *result = ComposeTransform(to_return);
+ return true;
+}
+
+bool TransformOperations::ComputeDecomposedTransform() const {
+ if (decomposed_transform_dirty_) {
+ if (!decomposed_transform_)
+ decomposed_transform_.reset(new gfx::DecomposedTransform());
+ gfx::Transform transform = Apply();
+ if (!gfx::DecomposeTransform(decomposed_transform_.get(), transform))
+ return false;
+ decomposed_transform_dirty_ = false;
+ }
+ return true;
+}
+
+} // namespace cc
diff --git a/chromium/cc/animation/transform_operations.h b/chromium/cc/animation/transform_operations.h
new file mode 100644
index 00000000000..b5f960fa688
--- /dev/null
+++ b/chromium/cc/animation/transform_operations.h
@@ -0,0 +1,83 @@
+// Copyright 2013 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.
+
+#ifndef CC_ANIMATION_TRANSFORM_OPERATIONS_H_
+#define CC_ANIMATION_TRANSFORM_OPERATIONS_H_
+
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/animation/transform_operation.h"
+#include "cc/base/cc_export.h"
+#include "ui/gfx/transform.h"
+
+namespace gfx {
+struct DecomposedTransform;
+}
+
+namespace cc {
+
+// Transform operations are a decomposed transformation matrix. It can be
+// applied to obtain a gfx::Transform at any time, and can be blended
+// intelligently with other transform operations, so long as they represent the
+// same decomposition. For example, if we have a transform that is made up of
+// a rotation followed by skew, it can be blended intelligently with another
+// transform made up of a rotation followed by a skew. Blending is possible if
+// we have two dissimilar sets of transform operations, but the effect may not
+// be what was intended. For more information, see the comments for the blend
+// function below.
+class CC_EXPORT TransformOperations {
+ public:
+ TransformOperations();
+ TransformOperations(const TransformOperations& other);
+ ~TransformOperations();
+
+ // Returns a transformation matrix representing these transform operations.
+ gfx::Transform Apply() const;
+
+ // Given another set of transform operations and a progress in the range
+ // [0, 1], returns a transformation matrix representing the intermediate
+ // value. If this->MatchesTypes(from), then each of the operations are
+ // blended separately and then combined. Otherwise, the two sets of
+ // transforms are baked to matrices (using apply), and the matrices are
+ // then decomposed and interpolated. For more information, see
+ // http://www.w3.org/TR/2011/WD-css3-2d-transforms-20111215/#matrix-decomposition.
+ gfx::Transform Blend(const TransformOperations& from, double progress) const;
+
+ // Returns true if this operation and its descendants have the same types
+ // as other and its descendants.
+ bool MatchesTypes(const TransformOperations& other) const;
+
+ // Returns true if these operations can be blended. It will only return
+ // false if we must resort to matrix interpolation, and matrix interpolation
+ // fails (this can happen if either matrix cannot be decomposed).
+ bool CanBlendWith(const TransformOperations& other) const;
+
+ void AppendTranslate(double x, double y, double z);
+ void AppendRotate(double x, double y, double z, double degrees);
+ void AppendScale(double x, double y, double z);
+ void AppendSkew(double x, double y);
+ void AppendPerspective(double depth);
+ void AppendMatrix(const gfx::Transform& matrix);
+ void AppendIdentity();
+ bool IsIdentity() const;
+
+ private:
+ bool BlendInternal(const TransformOperations& from, double progress,
+ gfx::Transform* result) const;
+
+ std::vector<TransformOperation> operations_;
+
+ bool ComputeDecomposedTransform() const;
+
+ // For efficiency, we cache the decomposed transform.
+ mutable scoped_ptr<gfx::DecomposedTransform> decomposed_transform_;
+ mutable bool decomposed_transform_dirty_;
+
+ DISALLOW_ASSIGN(TransformOperations);
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_TRANSFORM_OPERATIONS_H_
diff --git a/chromium/cc/animation/transform_operations_unittest.cc b/chromium/cc/animation/transform_operations_unittest.cc
new file mode 100644
index 00000000000..8fc9b949d74
--- /dev/null
+++ b/chromium/cc/animation/transform_operations_unittest.cc
@@ -0,0 +1,711 @@
+// Copyright 2013 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 <limits>
+
+#include "base/memory/scoped_vector.h"
+#include "cc/animation/transform_operations.h"
+#include "cc/test/geometry_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/vector3d_f.h"
+
+namespace cc {
+namespace {
+
+TEST(TransformOperationTest, TransformTypesAreUnique) {
+ ScopedVector<TransformOperations> transforms;
+
+ TransformOperations* to_add = new TransformOperations();
+ to_add->AppendTranslate(1, 0, 0);
+ transforms.push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendRotate(0, 0, 1, 2);
+ transforms.push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendScale(2, 2, 2);
+ transforms.push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendSkew(1, 0);
+ transforms.push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendPerspective(800);
+ transforms.push_back(to_add);
+
+ for (size_t i = 0; i < transforms.size(); ++i) {
+ for (size_t j = 0; j < transforms.size(); ++j) {
+ bool matches_type = transforms[i]->MatchesTypes(*transforms[j]);
+ EXPECT_TRUE((i == j && matches_type) || !matches_type);
+ }
+ }
+}
+
+TEST(TransformOperationTest, MatchTypesSameLength) {
+ TransformOperations translates;
+ translates.AppendTranslate(1, 0, 0);
+ translates.AppendTranslate(1, 0, 0);
+ translates.AppendTranslate(1, 0, 0);
+
+ TransformOperations skews;
+ skews.AppendSkew(0, 2);
+ skews.AppendSkew(0, 2);
+ skews.AppendSkew(0, 2);
+
+ TransformOperations translates2;
+ translates2.AppendTranslate(0, 2, 0);
+ translates2.AppendTranslate(0, 2, 0);
+ translates2.AppendTranslate(0, 2, 0);
+
+ TransformOperations translates3 = translates2;
+
+ EXPECT_FALSE(translates.MatchesTypes(skews));
+ EXPECT_TRUE(translates.MatchesTypes(translates2));
+ EXPECT_TRUE(translates.MatchesTypes(translates3));
+}
+
+TEST(TransformOperationTest, MatchTypesDifferentLength) {
+ TransformOperations translates;
+ translates.AppendTranslate(1, 0, 0);
+ translates.AppendTranslate(1, 0, 0);
+ translates.AppendTranslate(1, 0, 0);
+
+ TransformOperations skews;
+ skews.AppendSkew(2, 0);
+ skews.AppendSkew(2, 0);
+
+ TransformOperations translates2;
+ translates2.AppendTranslate(0, 2, 0);
+ translates2.AppendTranslate(0, 2, 0);
+
+ EXPECT_FALSE(translates.MatchesTypes(skews));
+ EXPECT_FALSE(translates.MatchesTypes(translates2));
+}
+
+void GetIdentityOperations(ScopedVector<TransformOperations>* operations) {
+ TransformOperations* to_add = new TransformOperations();
+ operations->push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendTranslate(0, 0, 0);
+ operations->push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendTranslate(0, 0, 0);
+ to_add->AppendTranslate(0, 0, 0);
+ operations->push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendScale(1, 1, 1);
+ operations->push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendScale(1, 1, 1);
+ to_add->AppendScale(1, 1, 1);
+ operations->push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendSkew(0, 0);
+ operations->push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendSkew(0, 0);
+ to_add->AppendSkew(0, 0);
+ operations->push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendRotate(0, 0, 1, 0);
+ operations->push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendRotate(0, 0, 1, 0);
+ to_add->AppendRotate(0, 0, 1, 0);
+ operations->push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendMatrix(gfx::Transform());
+ operations->push_back(to_add);
+
+ to_add = new TransformOperations();
+ to_add->AppendMatrix(gfx::Transform());
+ to_add->AppendMatrix(gfx::Transform());
+ operations->push_back(to_add);
+}
+
+TEST(TransformOperationTest, IdentityAlwaysMatches) {
+ ScopedVector<TransformOperations> operations;
+ GetIdentityOperations(&operations);
+
+ for (size_t i = 0; i < operations.size(); ++i) {
+ for (size_t j = 0; j < operations.size(); ++j)
+ EXPECT_TRUE(operations[i]->MatchesTypes(*operations[j]));
+ }
+}
+
+TEST(TransformOperationTest, ApplyTranslate) {
+ double x = 1;
+ double y = 2;
+ double z = 3;
+ TransformOperations operations;
+ operations.AppendTranslate(x, y, z);
+ gfx::Transform expected;
+ expected.Translate3d(x, y, z);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected, operations.Apply());
+}
+
+TEST(TransformOperationTest, ApplyRotate) {
+ double x = 1;
+ double y = 2;
+ double z = 3;
+ double degrees = 80;
+ TransformOperations operations;
+ operations.AppendRotate(x, y, z, degrees);
+ gfx::Transform expected;
+ expected.RotateAbout(gfx::Vector3dF(x, y, z), degrees);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected, operations.Apply());
+}
+
+TEST(TransformOperationTest, ApplyScale) {
+ double x = 1;
+ double y = 2;
+ double z = 3;
+ TransformOperations operations;
+ operations.AppendScale(x, y, z);
+ gfx::Transform expected;
+ expected.Scale3d(x, y, z);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected, operations.Apply());
+}
+
+TEST(TransformOperationTest, ApplySkew) {
+ double x = 1;
+ double y = 2;
+ TransformOperations operations;
+ operations.AppendSkew(x, y);
+ gfx::Transform expected;
+ expected.SkewX(x);
+ expected.SkewY(y);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected, operations.Apply());
+}
+
+TEST(TransformOperationTest, ApplyPerspective) {
+ double depth = 800;
+ TransformOperations operations;
+ operations.AppendPerspective(depth);
+ gfx::Transform expected;
+ expected.ApplyPerspectiveDepth(depth);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected, operations.Apply());
+}
+
+TEST(TransformOperationTest, ApplyMatrix) {
+ double dx = 1;
+ double dy = 2;
+ double dz = 3;
+ gfx::Transform expected_matrix;
+ expected_matrix.Translate3d(dx, dy, dz);
+ TransformOperations matrix_transform;
+ matrix_transform.AppendMatrix(expected_matrix);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_matrix, matrix_transform.Apply());
+}
+
+TEST(TransformOperationTest, ApplyOrder) {
+ double sx = 2;
+ double sy = 4;
+ double sz = 8;
+
+ double dx = 1;
+ double dy = 2;
+ double dz = 3;
+
+ TransformOperations operations;
+ operations.AppendScale(sx, sy, sz);
+ operations.AppendTranslate(dx, dy, dz);
+
+ gfx::Transform expected_scale_matrix;
+ expected_scale_matrix.Scale3d(sx, sy, sz);
+
+ gfx::Transform expected_translate_matrix;
+ expected_translate_matrix.Translate3d(dx, dy, dz);
+
+ gfx::Transform expected_combined_matrix = expected_scale_matrix;
+ expected_combined_matrix.PreconcatTransform(expected_translate_matrix);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_combined_matrix, operations.Apply());
+}
+
+TEST(TransformOperationTest, BlendOrder) {
+ double sx1 = 2;
+ double sy1 = 4;
+ double sz1 = 8;
+
+ double dx1 = 1;
+ double dy1 = 2;
+ double dz1 = 3;
+
+ double sx2 = 4;
+ double sy2 = 8;
+ double sz2 = 16;
+
+ double dx2 = 10;
+ double dy2 = 20;
+ double dz2 = 30;
+
+ TransformOperations operations_from;
+ operations_from.AppendScale(sx1, sy1, sz1);
+ operations_from.AppendTranslate(dx1, dy1, dz1);
+
+ TransformOperations operations_to;
+ operations_to.AppendScale(sx2, sy2, sz2);
+ operations_to.AppendTranslate(dx2, dy2, dz2);
+
+ gfx::Transform scale_from;
+ scale_from.Scale3d(sx1, sy1, sz1);
+ gfx::Transform translate_from;
+ translate_from.Translate3d(dx1, dy1, dz1);
+
+ gfx::Transform scale_to;
+ scale_to.Scale3d(sx2, sy2, sz2);
+ gfx::Transform translate_to;
+ translate_to.Translate3d(dx2, dy2, dz2);
+
+ double progress = 0.25;
+
+ gfx::Transform blended_scale = scale_to;
+ blended_scale.Blend(scale_from, progress);
+
+ gfx::Transform blended_translate = translate_to;
+ blended_translate.Blend(translate_from, progress);
+
+ gfx::Transform expected = blended_scale;
+ expected.PreconcatTransform(blended_translate);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations_to.Blend(operations_from, progress));
+}
+
+static void CheckProgress(double progress,
+ const gfx::Transform& from_matrix,
+ const gfx::Transform& to_matrix,
+ const TransformOperations& from_transform,
+ const TransformOperations& to_transform) {
+ gfx::Transform expected_matrix = to_matrix;
+ expected_matrix.Blend(from_matrix, progress);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected_matrix, to_transform.Blend(from_transform, progress));
+}
+
+TEST(TransformOperationTest, BlendProgress) {
+ double sx = 2;
+ double sy = 4;
+ double sz = 8;
+ TransformOperations operations_from;
+ operations_from.AppendScale(sx, sy, sz);
+
+ gfx::Transform matrix_from;
+ matrix_from.Scale3d(sx, sy, sz);
+
+ sx = 4;
+ sy = 8;
+ sz = 16;
+ TransformOperations operations_to;
+ operations_to.AppendScale(sx, sy, sz);
+
+ gfx::Transform matrix_to;
+ matrix_to.Scale3d(sx, sy, sz);
+
+ CheckProgress(-1, matrix_from, matrix_to, operations_from, operations_to);
+ CheckProgress(0, matrix_from, matrix_to, operations_from, operations_to);
+ CheckProgress(0.25, matrix_from, matrix_to, operations_from, operations_to);
+ CheckProgress(0.5, matrix_from, matrix_to, operations_from, operations_to);
+ CheckProgress(1, matrix_from, matrix_to, operations_from, operations_to);
+ CheckProgress(2, matrix_from, matrix_to, operations_from, operations_to);
+}
+
+TEST(TransformOperationTest, BlendWhenTypesDoNotMatch) {
+ double sx1 = 2;
+ double sy1 = 4;
+ double sz1 = 8;
+
+ double dx1 = 1;
+ double dy1 = 2;
+ double dz1 = 3;
+
+ double sx2 = 4;
+ double sy2 = 8;
+ double sz2 = 16;
+
+ double dx2 = 10;
+ double dy2 = 20;
+ double dz2 = 30;
+
+ TransformOperations operations_from;
+ operations_from.AppendScale(sx1, sy1, sz1);
+ operations_from.AppendTranslate(dx1, dy1, dz1);
+
+ TransformOperations operations_to;
+ operations_to.AppendTranslate(dx2, dy2, dz2);
+ operations_to.AppendScale(sx2, sy2, sz2);
+
+ gfx::Transform from;
+ from.Scale3d(sx1, sy1, sz1);
+ from.Translate3d(dx1, dy1, dz1);
+
+ gfx::Transform to;
+ to.Translate3d(dx2, dy2, dz2);
+ to.Scale3d(sx2, sy2, sz2);
+
+ double progress = 0.25;
+
+ gfx::Transform expected = to;
+ expected.Blend(from, progress);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations_to.Blend(operations_from, progress));
+}
+
+TEST(TransformOperationTest, LargeRotationsWithSameAxis) {
+ TransformOperations operations_from;
+ operations_from.AppendRotate(0, 0, 1, 0);
+
+ TransformOperations operations_to;
+ operations_to.AppendRotate(0, 0, 2, 360);
+
+ double progress = 0.5;
+
+ gfx::Transform expected;
+ expected.RotateAbout(gfx::Vector3dF(0, 0, 1), 180);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations_to.Blend(operations_from, progress));
+}
+
+TEST(TransformOperationTest, LargeRotationsWithSameAxisInDifferentDirection) {
+ TransformOperations operations_from;
+ operations_from.AppendRotate(0, 0, 1, 180);
+
+ TransformOperations operations_to;
+ operations_to.AppendRotate(0, 0, -1, 180);
+
+ double progress = 0.5;
+
+ gfx::Transform expected;
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations_to.Blend(operations_from, progress));
+}
+
+TEST(TransformOperationTest, LargeRotationsWithDifferentAxes) {
+ TransformOperations operations_from;
+ operations_from.AppendRotate(0, 0, 1, 175);
+
+ TransformOperations operations_to;
+ operations_to.AppendRotate(0, 1, 0, 175);
+
+ double progress = 0.5;
+ gfx::Transform matrix_from;
+ matrix_from.RotateAbout(gfx::Vector3dF(0, 0, 1), 175);
+
+ gfx::Transform matrix_to;
+ matrix_to.RotateAbout(gfx::Vector3dF(0, 1, 0), 175);
+
+ gfx::Transform expected = matrix_to;
+ expected.Blend(matrix_from, progress);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations_to.Blend(operations_from, progress));
+}
+
+TEST(TransformOperationTest, BlendRotationFromIdentity) {
+ ScopedVector<TransformOperations> identity_operations;
+ GetIdentityOperations(&identity_operations);
+
+ for (size_t i = 0; i < identity_operations.size(); ++i) {
+ TransformOperations operations;
+ operations.AppendRotate(0, 0, 1, 360);
+
+ double progress = 0.5;
+
+ gfx::Transform expected;
+ expected.RotateAbout(gfx::Vector3dF(0, 0, 1), 180);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations.Blend(*identity_operations[i], progress));
+
+ progress = -0.5;
+
+ expected.MakeIdentity();
+ expected.RotateAbout(gfx::Vector3dF(0, 0, 1), -180);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations.Blend(*identity_operations[i], progress));
+
+ progress = 1.5;
+
+ expected.MakeIdentity();
+ expected.RotateAbout(gfx::Vector3dF(0, 0, 1), 540);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations.Blend(*identity_operations[i], progress));
+ }
+}
+
+TEST(TransformOperationTest, BlendTranslationFromIdentity) {
+ ScopedVector<TransformOperations> identity_operations;
+ GetIdentityOperations(&identity_operations);
+
+ for (size_t i = 0; i < identity_operations.size(); ++i) {
+ TransformOperations operations;
+ operations.AppendTranslate(2, 2, 2);
+
+ double progress = 0.5;
+
+ gfx::Transform expected;
+ expected.Translate3d(1, 1, 1);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations.Blend(*identity_operations[i], progress));
+
+ progress = -0.5;
+
+ expected.MakeIdentity();
+ expected.Translate3d(-1, -1, -1);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations.Blend(*identity_operations[i], progress));
+
+ progress = 1.5;
+
+ expected.MakeIdentity();
+ expected.Translate3d(3, 3, 3);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations.Blend(*identity_operations[i], progress));
+ }
+}
+
+TEST(TransformOperationTest, BlendScaleFromIdentity) {
+ ScopedVector<TransformOperations> identity_operations;
+ GetIdentityOperations(&identity_operations);
+
+ for (size_t i = 0; i < identity_operations.size(); ++i) {
+ TransformOperations operations;
+ operations.AppendScale(3, 3, 3);
+
+ double progress = 0.5;
+
+ gfx::Transform expected;
+ expected.Scale3d(2, 2, 2);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations.Blend(*identity_operations[i], progress));
+
+ progress = -0.5;
+
+ expected.MakeIdentity();
+ expected.Scale3d(0, 0, 0);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations.Blend(*identity_operations[i], progress));
+
+ progress = 1.5;
+
+ expected.MakeIdentity();
+ expected.Scale3d(4, 4, 4);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations.Blend(*identity_operations[i], progress));
+ }
+}
+
+TEST(TransformOperationTest, BlendSkewFromIdentity) {
+ ScopedVector<TransformOperations> identity_operations;
+ GetIdentityOperations(&identity_operations);
+
+ for (size_t i = 0; i < identity_operations.size(); ++i) {
+ TransformOperations operations;
+ operations.AppendSkew(2, 2);
+
+ double progress = 0.5;
+
+ gfx::Transform expected;
+ expected.SkewX(1);
+ expected.SkewY(1);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations.Blend(*identity_operations[i], progress));
+
+ progress = -0.5;
+
+ expected.MakeIdentity();
+ expected.SkewX(-1);
+ expected.SkewY(-1);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations.Blend(*identity_operations[i], progress));
+
+ progress = 1.5;
+
+ expected.MakeIdentity();
+ expected.SkewX(3);
+ expected.SkewY(3);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations.Blend(*identity_operations[i], progress));
+ }
+}
+
+TEST(TransformOperationTest, BlendPerspectiveFromIdentity) {
+ ScopedVector<TransformOperations> identity_operations;
+ GetIdentityOperations(&identity_operations);
+
+ for (size_t i = 0; i < identity_operations.size(); ++i) {
+ TransformOperations operations;
+ operations.AppendPerspective(1000);
+
+ double progress = 0.5;
+
+ gfx::Transform expected;
+ expected.ApplyPerspectiveDepth(
+ 500 + 0.5 * std::numeric_limits<double>::max());
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations.Blend(*identity_operations[i], progress));
+ }
+}
+
+TEST(TransformOperationTest, BlendRotationToIdentity) {
+ ScopedVector<TransformOperations> identity_operations;
+ GetIdentityOperations(&identity_operations);
+
+ for (size_t i = 0; i < identity_operations.size(); ++i) {
+ TransformOperations operations;
+ operations.AppendRotate(0, 0, 1, 360);
+
+ double progress = 0.5;
+
+ gfx::Transform expected;
+ expected.RotateAbout(gfx::Vector3dF(0, 0, 1), 180);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, identity_operations[i]->Blend(operations, progress));
+ }
+}
+
+TEST(TransformOperationTest, BlendTranslationToIdentity) {
+ ScopedVector<TransformOperations> identity_operations;
+ GetIdentityOperations(&identity_operations);
+
+ for (size_t i = 0; i < identity_operations.size(); ++i) {
+ TransformOperations operations;
+ operations.AppendTranslate(2, 2, 2);
+
+ double progress = 0.5;
+
+ gfx::Transform expected;
+ expected.Translate3d(1, 1, 1);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, identity_operations[i]->Blend(operations, progress));
+ }
+}
+
+TEST(TransformOperationTest, BlendScaleToIdentity) {
+ ScopedVector<TransformOperations> identity_operations;
+ GetIdentityOperations(&identity_operations);
+
+ for (size_t i = 0; i < identity_operations.size(); ++i) {
+ TransformOperations operations;
+ operations.AppendScale(3, 3, 3);
+
+ double progress = 0.5;
+
+ gfx::Transform expected;
+ expected.Scale3d(2, 2, 2);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, identity_operations[i]->Blend(operations, progress));
+ }
+}
+
+TEST(TransformOperationTest, BlendSkewToIdentity) {
+ ScopedVector<TransformOperations> identity_operations;
+ GetIdentityOperations(&identity_operations);
+
+ for (size_t i = 0; i < identity_operations.size(); ++i) {
+ TransformOperations operations;
+ operations.AppendSkew(2, 2);
+
+ double progress = 0.5;
+
+ gfx::Transform expected;
+ expected.SkewX(1);
+ expected.SkewY(1);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, identity_operations[i]->Blend(operations, progress));
+ }
+}
+
+TEST(TransformOperationTest, BlendPerspectiveToIdentity) {
+ ScopedVector<TransformOperations> identity_operations;
+ GetIdentityOperations(&identity_operations);
+
+ for (size_t i = 0; i < identity_operations.size(); ++i) {
+ TransformOperations operations;
+ operations.AppendPerspective(1000);
+
+ double progress = 0.5;
+
+ gfx::Transform expected;
+ expected.ApplyPerspectiveDepth(
+ 500 + 0.5 * std::numeric_limits<double>::max());
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, identity_operations[i]->Blend(operations, progress));
+ }
+}
+
+TEST(TransformOperationTest, ExtrapolatePerspectiveBlending) {
+ TransformOperations operations1;
+ operations1.AppendPerspective(1000);
+
+ TransformOperations operations2;
+ operations2.AppendPerspective(500);
+
+ gfx::Transform expected;
+ expected.ApplyPerspectiveDepth(250);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations1.Blend(operations2, -0.5));
+
+ expected.MakeIdentity();
+ expected.ApplyPerspectiveDepth(1250);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations1.Blend(operations2, 1.5));
+}
+
+TEST(TransformOperationTest, ExtrapolateMatrixBlending) {
+ gfx::Transform transform1;
+ transform1.Translate3d(1, 1, 1);
+ TransformOperations operations1;
+ operations1.AppendMatrix(transform1);
+
+ gfx::Transform transform2;
+ transform2.Translate3d(3, 3, 3);
+ TransformOperations operations2;
+ operations2.AppendMatrix(transform2);
+
+ gfx::Transform expected;
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations1.Blend(operations2, 1.5));
+
+ expected.Translate3d(4, 4, 4);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, operations1.Blend(operations2, -0.5));
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/base/cc_export.h b/chromium/cc/base/cc_export.h
new file mode 100644
index 00000000000..fe0e0e369c7
--- /dev/null
+++ b/chromium/cc/base/cc_export.h
@@ -0,0 +1,29 @@
+// Copyright (c) 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.
+
+#ifndef CC_BASE_CC_EXPORT_H_
+#define CC_BASE_CC_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+#if defined(WIN32)
+
+#if defined(CC_IMPLEMENTATION)
+#define CC_EXPORT __declspec(dllexport)
+#else
+#define CC_EXPORT __declspec(dllimport)
+#endif // defined(CC_IMPLEMENTATION)
+
+#else // defined(WIN32)
+#if defined(CC_IMPLEMENTATION)
+#define CC_EXPORT __attribute__((visibility("default")))
+#else
+#define CC_EXPORT
+#endif
+#endif
+
+#else // defined(COMPONENT_BUILD)
+#define CC_EXPORT
+#endif
+
+#endif // CC_BASE_CC_EXPORT_H_
diff --git a/chromium/cc/base/completion_event.h b/chromium/cc/base/completion_event.h
new file mode 100644
index 00000000000..759ce9c3a27
--- /dev/null
+++ b/chromium/cc/base/completion_event.h
@@ -0,0 +1,63 @@
+// Copyright 2011 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.
+
+#ifndef CC_BASE_COMPLETION_EVENT_H_
+#define CC_BASE_COMPLETION_EVENT_H_
+
+#include "base/logging.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread_restrictions.h"
+
+namespace cc {
+
+// Used for making blocking calls from one thread to another. Use only when
+// absolutely certain that doing-so will not lead to a deadlock.
+//
+// It is safe to destroy this object as soon as Wait() returns.
+class CompletionEvent {
+ public:
+ CompletionEvent()
+ : event_(false /* manual_reset */, false /* initially_signaled */) {
+#ifndef NDEBUG
+ waited_ = false;
+ signaled_ = false;
+#endif
+ }
+
+ ~CompletionEvent() {
+#ifndef NDEBUG
+ DCHECK(waited_);
+ DCHECK(signaled_);
+#endif
+ }
+
+ void Wait() {
+#ifndef NDEBUG
+ DCHECK(!waited_);
+ waited_ = true;
+#endif
+ base::ThreadRestrictions::ScopedAllowWait allow_wait;
+ event_.Wait();
+ }
+
+ void Signal() {
+#ifndef NDEBUG
+ DCHECK(!signaled_);
+ signaled_ = true;
+#endif
+ event_.Signal();
+ }
+
+ private:
+ base::WaitableEvent event_;
+#ifndef NDEBUG
+ // Used to assert that Wait() and Signal() are each called exactly once.
+ bool waited_;
+ bool signaled_;
+#endif
+};
+
+} // namespace cc
+
+#endif // CC_BASE_COMPLETION_EVENT_H_
diff --git a/chromium/cc/base/float_quad_unittest.cc b/chromium/cc/base/float_quad_unittest.cc
new file mode 100644
index 00000000000..bb3446c0d69
--- /dev/null
+++ b/chromium/cc/base/float_quad_unittest.cc
@@ -0,0 +1,65 @@
+// Copyright 2011 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 "cc/base/math_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/quad_f.h"
+#include "ui/gfx/rect_f.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+namespace {
+
+// TODO(danakj) Move this test to ui/gfx/ when we don't need MathUtil::MapQuad.
+TEST(FloatQuadTest, IsRectilinearTest) {
+ const int kNumRectilinear = 8;
+ gfx::Transform rectilinear_trans[kNumRectilinear];
+ rectilinear_trans[1].Rotate(90.0);
+ rectilinear_trans[2].Rotate(180.0);
+ rectilinear_trans[3].Rotate(270.0);
+ rectilinear_trans[4].SkewX(0.00000000001);
+ rectilinear_trans[5].SkewY(0.00000000001);
+ rectilinear_trans[6].Scale(0.00001, 0.00001);
+ rectilinear_trans[6].Rotate(180.0);
+ rectilinear_trans[7].Scale(100000, 100000);
+ rectilinear_trans[7].Rotate(180.0);
+
+ for (int i = 0; i < kNumRectilinear; ++i) {
+ bool clipped = false;
+ gfx::QuadF quad = MathUtil::MapQuad(
+ rectilinear_trans[i],
+ gfx::QuadF(
+ gfx::RectF(0.01010101f, 0.01010101f, 100.01010101f, 100.01010101f)),
+ &clipped);
+ ASSERT_TRUE(!clipped);
+ EXPECT_TRUE(quad.IsRectilinear());
+ }
+
+ const int kNumNonRectilinear = 10;
+ gfx::Transform non_rectilinear_trans[kNumNonRectilinear];
+ non_rectilinear_trans[0].Rotate(359.999);
+ non_rectilinear_trans[1].Rotate(0.0000001);
+ non_rectilinear_trans[2].Rotate(89.999999);
+ non_rectilinear_trans[3].Rotate(90.0000001);
+ non_rectilinear_trans[4].Rotate(179.999999);
+ non_rectilinear_trans[5].Rotate(180.0000001);
+ non_rectilinear_trans[6].Rotate(269.999999);
+ non_rectilinear_trans[7].Rotate(270.0000001);
+ non_rectilinear_trans[8].SkewX(0.00001);
+ non_rectilinear_trans[9].SkewY(0.00001);
+
+ for (int i = 0; i < kNumNonRectilinear; ++i) {
+ bool clipped = false;
+ gfx::QuadF quad = MathUtil::MapQuad(
+ non_rectilinear_trans[i],
+ gfx::QuadF(
+ gfx::RectF(0.01010101f, 0.01010101f, 100.01010101f, 100.01010101f)),
+ &clipped);
+ ASSERT_TRUE(!clipped);
+ EXPECT_FALSE(quad.IsRectilinear());
+ }
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/base/invalidation_region.cc b/chromium/cc/base/invalidation_region.cc
new file mode 100644
index 00000000000..2c39d25ba06
--- /dev/null
+++ b/chromium/cc/base/invalidation_region.cc
@@ -0,0 +1,48 @@
+// Copyright 2013 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 "cc/base/invalidation_region.h"
+
+#include "base/metrics/histogram.h"
+
+namespace {
+
+const int kMaxInvalidationRectCount = 256;
+
+} // namespace
+
+namespace cc {
+
+InvalidationRegion::InvalidationRegion() {}
+
+InvalidationRegion::~InvalidationRegion() {}
+
+void InvalidationRegion::Swap(Region* region) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Renderer4.InvalidationRegionApproximateRectCount",
+ region_.GetRegionComplexity(),
+ 1,
+ 5000,
+ 50);
+
+ SimplifyIfNeeded();
+ region_.Swap(region);
+}
+
+void InvalidationRegion::Clear() {
+ region_.Clear();
+}
+
+void InvalidationRegion::Union(gfx::Rect rect) {
+ // TODO(vmpstr): We should simplify the region after Union() after we get a
+ // good idea of what kind of regions are typical (from the UMA histogram).
+ region_.Union(rect);
+}
+
+void InvalidationRegion::SimplifyIfNeeded() {
+ if (region_.GetRegionComplexity() > kMaxInvalidationRectCount)
+ region_ = region_.bounds();
+}
+
+} // namespace cc
diff --git a/chromium/cc/base/invalidation_region.h b/chromium/cc/base/invalidation_region.h
new file mode 100644
index 00000000000..fd061e8cba3
--- /dev/null
+++ b/chromium/cc/base/invalidation_region.h
@@ -0,0 +1,34 @@
+// Copyright 2013 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.
+
+#ifndef CC_BASE_INVALIDATION_REGION_H_
+#define CC_BASE_INVALIDATION_REGION_H_
+
+#include "cc/base/cc_export.h"
+#include "cc/base/region.h"
+#include "ui/gfx/rect.h"
+
+namespace cc {
+
+// This class behaves similarly to Region, but it may have false positives. That
+// is, InvalidationRegion can be simplified to encompass a larger area than the
+// collection of rects unioned.
+class CC_EXPORT InvalidationRegion {
+ public:
+ InvalidationRegion();
+ ~InvalidationRegion();
+
+ void Swap(Region* region);
+ void Clear();
+ void Union(gfx::Rect rect);
+
+ private:
+ void SimplifyIfNeeded();
+
+ Region region_;
+};
+
+} // namespace cc
+
+#endif // CC_BASE_INVALIDATION_REGION_H_
diff --git a/chromium/cc/base/math_util.cc b/chromium/cc/base/math_util.cc
new file mode 100644
index 00000000000..fa7b21a8c29
--- /dev/null
+++ b/chromium/cc/base/math_util.cc
@@ -0,0 +1,576 @@
+// 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 "cc/base/math_util.h"
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+
+#include "base/values.h"
+#include "ui/gfx/quad_f.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/rect_f.h"
+#include "ui/gfx/transform.h"
+#include "ui/gfx/vector2d_f.h"
+
+namespace cc {
+
+const double MathUtil::kPiDouble = 3.14159265358979323846;
+const float MathUtil::kPiFloat = 3.14159265358979323846f;
+
+static HomogeneousCoordinate ProjectHomogeneousPoint(
+ const gfx::Transform& transform,
+ gfx::PointF p) {
+ // In this case, the layer we are trying to project onto is perpendicular to
+ // ray (point p and z-axis direction) that we are trying to project. This
+ // happens when the layer is rotated so that it is infinitesimally thin, or
+ // when it is co-planar with the camera origin -- i.e. when the layer is
+ // invisible anyway.
+ if (!transform.matrix().get(2, 2))
+ return HomogeneousCoordinate(0.0, 0.0, 0.0, 1.0);
+
+ SkMScalar z = -(transform.matrix().get(2, 0) * p.x() +
+ transform.matrix().get(2, 1) * p.y() +
+ transform.matrix().get(2, 3)) /
+ transform.matrix().get(2, 2);
+ HomogeneousCoordinate result(p.x(), p.y(), z, 1.0);
+ transform.matrix().mapMScalars(result.vec, result.vec);
+ return result;
+}
+
+static HomogeneousCoordinate MapHomogeneousPoint(
+ const gfx::Transform& transform,
+ const gfx::Point3F& p) {
+ HomogeneousCoordinate result(p.x(), p.y(), p.z(), 1.0);
+ transform.matrix().mapMScalars(result.vec, result.vec);
+ return result;
+}
+
+static HomogeneousCoordinate ComputeClippedPointForEdge(
+ const HomogeneousCoordinate& h1,
+ const HomogeneousCoordinate& h2) {
+ // Points h1 and h2 form a line in 4d, and any point on that line can be
+ // represented as an interpolation between h1 and h2:
+ // p = (1-t) h1 + (t) h2
+ //
+ // We want to compute point p such that p.w == epsilon, where epsilon is a
+ // small non-zero number. (but the smaller the number is, the higher the risk
+ // of overflow)
+ // To do this, we solve for t in the following equation:
+ // p.w = epsilon = (1-t) * h1.w + (t) * h2.w
+ //
+ // Once paramter t is known, the rest of p can be computed via
+ // p = (1-t) h1 + (t) h2.
+
+ // Technically this is a special case of the following assertion, but its a
+ // good idea to keep it an explicit sanity check here.
+ DCHECK_NE(h2.w(), h1.w());
+ // Exactly one of h1 or h2 (but not both) must be on the negative side of the
+ // w plane when this is called.
+ DCHECK(h1.ShouldBeClipped() ^ h2.ShouldBeClipped());
+
+ SkMScalar w = 0.00001; // or any positive non-zero small epsilon
+
+ SkMScalar t = (w - h1.w()) / (h2.w() - h1.w());
+
+ SkMScalar x = (1 - t) * h1.x() + t * h2.x();
+ SkMScalar y = (1 - t) * h1.y() + t * h2.y();
+ SkMScalar z = (1 - t) * h1.z() + t * h2.z();
+
+ return HomogeneousCoordinate(x, y, z, w);
+}
+
+static inline void ExpandBoundsToIncludePoint(float* xmin,
+ float* xmax,
+ float* ymin,
+ float* ymax,
+ gfx::PointF p) {
+ *xmin = std::min(p.x(), *xmin);
+ *xmax = std::max(p.x(), *xmax);
+ *ymin = std::min(p.y(), *ymin);
+ *ymax = std::max(p.y(), *ymax);
+}
+
+static inline void AddVertexToClippedQuad(gfx::PointF new_vertex,
+ gfx::PointF clipped_quad[8],
+ int* num_vertices_in_clipped_quad) {
+ clipped_quad[*num_vertices_in_clipped_quad] = new_vertex;
+ (*num_vertices_in_clipped_quad)++;
+}
+
+gfx::Rect MathUtil::MapClippedRect(const gfx::Transform& transform,
+ gfx::Rect src_rect) {
+ return gfx::ToEnclosingRect(MapClippedRect(transform, gfx::RectF(src_rect)));
+}
+
+gfx::RectF MathUtil::MapClippedRect(const gfx::Transform& transform,
+ const gfx::RectF& src_rect) {
+ if (transform.IsIdentityOrTranslation())
+ return src_rect +
+ gfx::Vector2dF(
+ static_cast<float>(transform.matrix().getDouble(0, 3)),
+ static_cast<float>(transform.matrix().getDouble(1, 3)));
+
+ // Apply the transform, but retain the result in homogeneous coordinates.
+
+ double quad[4 * 2]; // input: 4 x 2D points
+ quad[0] = src_rect.x();
+ quad[1] = src_rect.y();
+ quad[2] = src_rect.right();
+ quad[3] = src_rect.y();
+ quad[4] = src_rect.right();
+ quad[5] = src_rect.bottom();
+ quad[6] = src_rect.x();
+ quad[7] = src_rect.bottom();
+
+ double result[4 * 4]; // output: 4 x 4D homogeneous points
+ transform.matrix().map2(quad, 4, result);
+
+ HomogeneousCoordinate hc0(result[0], result[1], result[2], result[3]);
+ HomogeneousCoordinate hc1(result[4], result[5], result[6], result[7]);
+ HomogeneousCoordinate hc2(result[8], result[9], result[10], result[11]);
+ HomogeneousCoordinate hc3(result[12], result[13], result[14], result[15]);
+ return ComputeEnclosingClippedRect(hc0, hc1, hc2, hc3);
+}
+
+gfx::RectF MathUtil::ProjectClippedRect(const gfx::Transform& transform,
+ const gfx::RectF& src_rect) {
+ if (transform.IsIdentityOrTranslation()) {
+ return src_rect +
+ gfx::Vector2dF(
+ static_cast<float>(transform.matrix().getDouble(0, 3)),
+ static_cast<float>(transform.matrix().getDouble(1, 3)));
+ }
+
+ // Perform the projection, but retain the result in homogeneous coordinates.
+ gfx::QuadF q = gfx::QuadF(src_rect);
+ HomogeneousCoordinate h1 = ProjectHomogeneousPoint(transform, q.p1());
+ HomogeneousCoordinate h2 = ProjectHomogeneousPoint(transform, q.p2());
+ HomogeneousCoordinate h3 = ProjectHomogeneousPoint(transform, q.p3());
+ HomogeneousCoordinate h4 = ProjectHomogeneousPoint(transform, q.p4());
+
+ return ComputeEnclosingClippedRect(h1, h2, h3, h4);
+}
+
+void MathUtil::MapClippedQuad(const gfx::Transform& transform,
+ const gfx::QuadF& src_quad,
+ gfx::PointF clipped_quad[8],
+ int* num_vertices_in_clipped_quad) {
+ HomogeneousCoordinate h1 =
+ MapHomogeneousPoint(transform, gfx::Point3F(src_quad.p1()));
+ HomogeneousCoordinate h2 =
+ MapHomogeneousPoint(transform, gfx::Point3F(src_quad.p2()));
+ HomogeneousCoordinate h3 =
+ MapHomogeneousPoint(transform, gfx::Point3F(src_quad.p3()));
+ HomogeneousCoordinate h4 =
+ MapHomogeneousPoint(transform, gfx::Point3F(src_quad.p4()));
+
+ // The order of adding the vertices to the array is chosen so that
+ // clockwise / counter-clockwise orientation is retained.
+
+ *num_vertices_in_clipped_quad = 0;
+
+ if (!h1.ShouldBeClipped()) {
+ AddVertexToClippedQuad(
+ h1.CartesianPoint2d(), clipped_quad, num_vertices_in_clipped_quad);
+ }
+
+ if (h1.ShouldBeClipped() ^ h2.ShouldBeClipped()) {
+ AddVertexToClippedQuad(
+ ComputeClippedPointForEdge(h1, h2).CartesianPoint2d(),
+ clipped_quad,
+ num_vertices_in_clipped_quad);
+ }
+
+ if (!h2.ShouldBeClipped()) {
+ AddVertexToClippedQuad(
+ h2.CartesianPoint2d(), clipped_quad, num_vertices_in_clipped_quad);
+ }
+
+ if (h2.ShouldBeClipped() ^ h3.ShouldBeClipped()) {
+ AddVertexToClippedQuad(
+ ComputeClippedPointForEdge(h2, h3).CartesianPoint2d(),
+ clipped_quad,
+ num_vertices_in_clipped_quad);
+ }
+
+ if (!h3.ShouldBeClipped()) {
+ AddVertexToClippedQuad(
+ h3.CartesianPoint2d(), clipped_quad, num_vertices_in_clipped_quad);
+ }
+
+ if (h3.ShouldBeClipped() ^ h4.ShouldBeClipped()) {
+ AddVertexToClippedQuad(
+ ComputeClippedPointForEdge(h3, h4).CartesianPoint2d(),
+ clipped_quad,
+ num_vertices_in_clipped_quad);
+ }
+
+ if (!h4.ShouldBeClipped()) {
+ AddVertexToClippedQuad(
+ h4.CartesianPoint2d(), clipped_quad, num_vertices_in_clipped_quad);
+ }
+
+ if (h4.ShouldBeClipped() ^ h1.ShouldBeClipped()) {
+ AddVertexToClippedQuad(
+ ComputeClippedPointForEdge(h4, h1).CartesianPoint2d(),
+ clipped_quad,
+ num_vertices_in_clipped_quad);
+ }
+
+ DCHECK_LE(*num_vertices_in_clipped_quad, 8);
+}
+
+gfx::RectF MathUtil::ComputeEnclosingRectOfVertices(gfx::PointF vertices[],
+ int num_vertices) {
+ if (num_vertices < 2)
+ return gfx::RectF();
+
+ float xmin = std::numeric_limits<float>::max();
+ float xmax = -std::numeric_limits<float>::max();
+ float ymin = std::numeric_limits<float>::max();
+ float ymax = -std::numeric_limits<float>::max();
+
+ for (int i = 0; i < num_vertices; ++i)
+ ExpandBoundsToIncludePoint(&xmin, &xmax, &ymin, &ymax, vertices[i]);
+
+ return gfx::RectF(gfx::PointF(xmin, ymin),
+ gfx::SizeF(xmax - xmin, ymax - ymin));
+}
+
+gfx::RectF MathUtil::ComputeEnclosingClippedRect(
+ const HomogeneousCoordinate& h1,
+ const HomogeneousCoordinate& h2,
+ const HomogeneousCoordinate& h3,
+ const HomogeneousCoordinate& h4) {
+ // This function performs clipping as necessary and computes the enclosing 2d
+ // gfx::RectF of the vertices. Doing these two steps simultaneously allows us
+ // to avoid the overhead of storing an unknown number of clipped vertices.
+
+ // If no vertices on the quad are clipped, then we can simply return the
+ // enclosing rect directly.
+ bool something_clipped = h1.ShouldBeClipped() || h2.ShouldBeClipped() ||
+ h3.ShouldBeClipped() || h4.ShouldBeClipped();
+ if (!something_clipped) {
+ gfx::QuadF mapped_quad = gfx::QuadF(h1.CartesianPoint2d(),
+ h2.CartesianPoint2d(),
+ h3.CartesianPoint2d(),
+ h4.CartesianPoint2d());
+ return mapped_quad.BoundingBox();
+ }
+
+ bool everything_clipped = h1.ShouldBeClipped() && h2.ShouldBeClipped() &&
+ h3.ShouldBeClipped() && h4.ShouldBeClipped();
+ if (everything_clipped)
+ return gfx::RectF();
+
+ float xmin = std::numeric_limits<float>::max();
+ float xmax = -std::numeric_limits<float>::max();
+ float ymin = std::numeric_limits<float>::max();
+ float ymax = -std::numeric_limits<float>::max();
+
+ if (!h1.ShouldBeClipped())
+ ExpandBoundsToIncludePoint(&xmin, &xmax, &ymin, &ymax,
+ h1.CartesianPoint2d());
+
+ if (h1.ShouldBeClipped() ^ h2.ShouldBeClipped())
+ ExpandBoundsToIncludePoint(&xmin,
+ &xmax,
+ &ymin,
+ &ymax,
+ ComputeClippedPointForEdge(h1, h2)
+ .CartesianPoint2d());
+
+ if (!h2.ShouldBeClipped())
+ ExpandBoundsToIncludePoint(&xmin, &xmax, &ymin, &ymax,
+ h2.CartesianPoint2d());
+
+ if (h2.ShouldBeClipped() ^ h3.ShouldBeClipped())
+ ExpandBoundsToIncludePoint(&xmin,
+ &xmax,
+ &ymin,
+ &ymax,
+ ComputeClippedPointForEdge(h2, h3)
+ .CartesianPoint2d());
+
+ if (!h3.ShouldBeClipped())
+ ExpandBoundsToIncludePoint(&xmin, &xmax, &ymin, &ymax,
+ h3.CartesianPoint2d());
+
+ if (h3.ShouldBeClipped() ^ h4.ShouldBeClipped())
+ ExpandBoundsToIncludePoint(&xmin,
+ &xmax,
+ &ymin,
+ &ymax,
+ ComputeClippedPointForEdge(h3, h4)
+ .CartesianPoint2d());
+
+ if (!h4.ShouldBeClipped())
+ ExpandBoundsToIncludePoint(&xmin, &xmax, &ymin, &ymax,
+ h4.CartesianPoint2d());
+
+ if (h4.ShouldBeClipped() ^ h1.ShouldBeClipped())
+ ExpandBoundsToIncludePoint(&xmin,
+ &xmax,
+ &ymin,
+ &ymax,
+ ComputeClippedPointForEdge(h4, h1)
+ .CartesianPoint2d());
+
+ return gfx::RectF(gfx::PointF(xmin, ymin),
+ gfx::SizeF(xmax - xmin, ymax - ymin));
+}
+
+gfx::QuadF MathUtil::MapQuad(const gfx::Transform& transform,
+ const gfx::QuadF& q,
+ bool* clipped) {
+ if (transform.IsIdentityOrTranslation()) {
+ gfx::QuadF mapped_quad(q);
+ mapped_quad +=
+ gfx::Vector2dF(static_cast<float>(transform.matrix().getDouble(0, 3)),
+ static_cast<float>(transform.matrix().getDouble(1, 3)));
+ *clipped = false;
+ return mapped_quad;
+ }
+
+ HomogeneousCoordinate h1 =
+ MapHomogeneousPoint(transform, gfx::Point3F(q.p1()));
+ HomogeneousCoordinate h2 =
+ MapHomogeneousPoint(transform, gfx::Point3F(q.p2()));
+ HomogeneousCoordinate h3 =
+ MapHomogeneousPoint(transform, gfx::Point3F(q.p3()));
+ HomogeneousCoordinate h4 =
+ MapHomogeneousPoint(transform, gfx::Point3F(q.p4()));
+
+ *clipped = h1.ShouldBeClipped() || h2.ShouldBeClipped() ||
+ h3.ShouldBeClipped() || h4.ShouldBeClipped();
+
+ // Result will be invalid if clipped == true. But, compute it anyway just in
+ // case, to emulate existing behavior.
+ return gfx::QuadF(h1.CartesianPoint2d(),
+ h2.CartesianPoint2d(),
+ h3.CartesianPoint2d(),
+ h4.CartesianPoint2d());
+}
+
+gfx::PointF MathUtil::MapPoint(const gfx::Transform& transform,
+ gfx::PointF p,
+ bool* clipped) {
+ HomogeneousCoordinate h = MapHomogeneousPoint(transform, gfx::Point3F(p));
+
+ if (h.w() > 0) {
+ *clipped = false;
+ return h.CartesianPoint2d();
+ }
+
+ // The cartesian coordinates will be invalid after dividing by w.
+ *clipped = true;
+
+ // Avoid dividing by w if w == 0.
+ if (!h.w())
+ return gfx::PointF();
+
+ // This return value will be invalid because clipped == true, but (1) users of
+ // this code should be ignoring the return value when clipped == true anyway,
+ // and (2) this behavior is more consistent with existing behavior of WebKit
+ // transforms if the user really does not ignore the return value.
+ return h.CartesianPoint2d();
+}
+
+gfx::Point3F MathUtil::MapPoint(const gfx::Transform& transform,
+ const gfx::Point3F& p,
+ bool* clipped) {
+ HomogeneousCoordinate h = MapHomogeneousPoint(transform, p);
+
+ if (h.w() > 0) {
+ *clipped = false;
+ return h.CartesianPoint3d();
+ }
+
+ // The cartesian coordinates will be invalid after dividing by w.
+ *clipped = true;
+
+ // Avoid dividing by w if w == 0.
+ if (!h.w())
+ return gfx::Point3F();
+
+ // This return value will be invalid because clipped == true, but (1) users of
+ // this code should be ignoring the return value when clipped == true anyway,
+ // and (2) this behavior is more consistent with existing behavior of WebKit
+ // transforms if the user really does not ignore the return value.
+ return h.CartesianPoint3d();
+}
+
+gfx::QuadF MathUtil::ProjectQuad(const gfx::Transform& transform,
+ const gfx::QuadF& q,
+ bool* clipped) {
+ gfx::QuadF projected_quad;
+ bool clipped_point;
+ projected_quad.set_p1(ProjectPoint(transform, q.p1(), &clipped_point));
+ *clipped = clipped_point;
+ projected_quad.set_p2(ProjectPoint(transform, q.p2(), &clipped_point));
+ *clipped |= clipped_point;
+ projected_quad.set_p3(ProjectPoint(transform, q.p3(), &clipped_point));
+ *clipped |= clipped_point;
+ projected_quad.set_p4(ProjectPoint(transform, q.p4(), &clipped_point));
+ *clipped |= clipped_point;
+
+ return projected_quad;
+}
+
+gfx::PointF MathUtil::ProjectPoint(const gfx::Transform& transform,
+ gfx::PointF p,
+ bool* clipped) {
+ HomogeneousCoordinate h = ProjectHomogeneousPoint(transform, p);
+
+ if (h.w() > 0) {
+ // The cartesian coordinates will be valid in this case.
+ *clipped = false;
+ return h.CartesianPoint2d();
+ }
+
+ // The cartesian coordinates will be invalid after dividing by w.
+ *clipped = true;
+
+ // Avoid dividing by w if w == 0.
+ if (!h.w())
+ return gfx::PointF();
+
+ // This return value will be invalid because clipped == true, but (1) users of
+ // this code should be ignoring the return value when clipped == true anyway,
+ // and (2) this behavior is more consistent with existing behavior of WebKit
+ // transforms if the user really does not ignore the return value.
+ return h.CartesianPoint2d();
+}
+
+static inline float ScaleOnAxis(double a, double b, double c) {
+ return std::sqrt(a * a + b * b + c * c);
+}
+
+gfx::Vector2dF MathUtil::ComputeTransform2dScaleComponents(
+ const gfx::Transform& transform,
+ float fallback_value) {
+ if (transform.HasPerspective())
+ return gfx::Vector2dF(fallback_value, fallback_value);
+ float x_scale = ScaleOnAxis(transform.matrix().getDouble(0, 0),
+ transform.matrix().getDouble(1, 0),
+ transform.matrix().getDouble(2, 0));
+ float y_scale = ScaleOnAxis(transform.matrix().getDouble(0, 1),
+ transform.matrix().getDouble(1, 1),
+ transform.matrix().getDouble(2, 1));
+ return gfx::Vector2dF(x_scale, y_scale);
+}
+
+float MathUtil::SmallestAngleBetweenVectors(gfx::Vector2dF v1,
+ gfx::Vector2dF v2) {
+ double dot_product = gfx::DotProduct(v1, v2) / v1.Length() / v2.Length();
+ // Clamp to compensate for rounding errors.
+ dot_product = std::max(-1.0, std::min(1.0, dot_product));
+ return static_cast<float>(Rad2Deg(std::acos(dot_product)));
+}
+
+gfx::Vector2dF MathUtil::ProjectVector(gfx::Vector2dF source,
+ gfx::Vector2dF destination) {
+ float projected_length =
+ gfx::DotProduct(source, destination) / destination.LengthSquared();
+ return gfx::Vector2dF(projected_length * destination.x(),
+ projected_length * destination.y());
+}
+
+scoped_ptr<base::Value> MathUtil::AsValue(gfx::Size s) {
+ scoped_ptr<base::DictionaryValue> res(new base::DictionaryValue());
+ res->SetDouble("width", s.width());
+ res->SetDouble("height", s.height());
+ return res.PassAs<base::Value>();
+}
+
+scoped_ptr<base::Value> MathUtil::AsValue(gfx::SizeF s) {
+ scoped_ptr<base::DictionaryValue> res(new base::DictionaryValue());
+ res->SetDouble("width", s.width());
+ res->SetDouble("height", s.height());
+ return res.PassAs<base::Value>();
+}
+
+scoped_ptr<base::Value> MathUtil::AsValue(gfx::Rect r) {
+ scoped_ptr<base::ListValue> res(new base::ListValue());
+ res->AppendInteger(r.x());
+ res->AppendInteger(r.y());
+ res->AppendInteger(r.width());
+ res->AppendInteger(r.height());
+ return res.PassAs<base::Value>();
+}
+
+bool MathUtil::FromValue(const base::Value* raw_value, gfx::Rect* out_rect) {
+ const base::ListValue* value = NULL;
+ if (!raw_value->GetAsList(&value))
+ return false;
+
+ if (value->GetSize() != 4)
+ return false;
+
+ int x, y, w, h;
+ bool ok = true;
+ ok &= value->GetInteger(0, &x);
+ ok &= value->GetInteger(1, &y);
+ ok &= value->GetInteger(2, &w);
+ ok &= value->GetInteger(3, &h);
+ if (!ok)
+ return false;
+
+ *out_rect = gfx::Rect(x, y, w, h);
+ return true;
+}
+
+scoped_ptr<base::Value> MathUtil::AsValue(gfx::PointF pt) {
+ scoped_ptr<base::ListValue> res(new base::ListValue());
+ res->AppendDouble(pt.x());
+ res->AppendDouble(pt.y());
+ return res.PassAs<base::Value>();
+}
+
+scoped_ptr<base::Value> MathUtil::AsValue(const gfx::QuadF& q) {
+ scoped_ptr<base::ListValue> res(new base::ListValue());
+ res->AppendDouble(q.p1().x());
+ res->AppendDouble(q.p1().y());
+ res->AppendDouble(q.p2().x());
+ res->AppendDouble(q.p2().y());
+ res->AppendDouble(q.p3().x());
+ res->AppendDouble(q.p3().y());
+ res->AppendDouble(q.p4().x());
+ res->AppendDouble(q.p4().y());
+ return res.PassAs<base::Value>();
+}
+
+scoped_ptr<base::Value> MathUtil::AsValue(const gfx::RectF& rect) {
+ scoped_ptr<base::ListValue> res(new base::ListValue());
+ res->AppendDouble(rect.x());
+ res->AppendDouble(rect.y());
+ res->AppendDouble(rect.width());
+ res->AppendDouble(rect.height());
+ return res.PassAs<base::Value>();
+}
+
+scoped_ptr<base::Value> MathUtil::AsValue(const gfx::Transform& transform) {
+ scoped_ptr<base::ListValue> res(new base::ListValue());
+ const SkMatrix44& m = transform.matrix();
+ for (int row = 0; row < 4; ++row) {
+ for (int col = 0; col < 4; ++col)
+ res->AppendDouble(m.getDouble(row, col));
+ }
+ return res.PassAs<base::Value>();
+}
+
+scoped_ptr<base::Value> MathUtil::AsValueSafely(double value) {
+ return scoped_ptr<base::Value>(base::Value::CreateDoubleValue(
+ std::min(value, std::numeric_limits<double>::max())));
+}
+
+scoped_ptr<base::Value> MathUtil::AsValueSafely(float value) {
+ return scoped_ptr<base::Value>(base::Value::CreateDoubleValue(
+ std::min(value, std::numeric_limits<float>::max())));
+}
+
+} // namespace cc
diff --git a/chromium/cc/base/math_util.h b/chromium/cc/base/math_util.h
new file mode 100644
index 00000000000..37f0c07602f
--- /dev/null
+++ b/chromium/cc/base/math_util.h
@@ -0,0 +1,175 @@
+// 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.
+
+#ifndef CC_BASE_MATH_UTIL_H_
+#define CC_BASE_MATH_UTIL_H_
+
+#include <algorithm>
+#include <cmath>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "ui/gfx/point3_f.h"
+#include "ui/gfx/point_f.h"
+#include "ui/gfx/size.h"
+#include "ui/gfx/transform.h"
+
+namespace base { class Value; }
+
+namespace gfx {
+class QuadF;
+class Rect;
+class RectF;
+class Transform;
+class Vector2dF;
+}
+
+namespace cc {
+
+struct HomogeneousCoordinate {
+ HomogeneousCoordinate(SkMScalar x, SkMScalar y, SkMScalar z, SkMScalar w) {
+ vec[0] = x;
+ vec[1] = y;
+ vec[2] = z;
+ vec[3] = w;
+ }
+
+ bool ShouldBeClipped() const { return w() <= 0.0; }
+
+ gfx::PointF CartesianPoint2d() const {
+ if (w() == 1.0)
+ return gfx::PointF(x(), y());
+
+ // For now, because this code is used privately only by MathUtil, it should
+ // never be called when w == 0, and we do not yet need to handle that case.
+ DCHECK(w());
+ double inv_w = 1.0 / w();
+ return gfx::PointF(x() * inv_w, y() * inv_w);
+ }
+
+ gfx::Point3F CartesianPoint3d() const {
+ if (w() == 1)
+ return gfx::Point3F(x(), y(), z());
+
+ // For now, because this code is used privately only by MathUtil, it should
+ // never be called when w == 0, and we do not yet need to handle that case.
+ DCHECK(w());
+ double inv_w = 1.0 / w();
+ return gfx::Point3F(x() * inv_w, y() * inv_w, z() * inv_w);
+ }
+
+ SkMScalar x() const { return vec[0]; }
+ SkMScalar y() const { return vec[1]; }
+ SkMScalar z() const { return vec[2]; }
+ SkMScalar w() const { return vec[3]; }
+
+ SkMScalar vec[4];
+};
+
+class CC_EXPORT MathUtil {
+ public:
+ static const double kPiDouble;
+ static const float kPiFloat;
+
+ static double Deg2Rad(double deg) { return deg * kPiDouble / 180.0; }
+ static double Rad2Deg(double rad) { return rad * 180.0 / kPiDouble; }
+
+ static float Deg2Rad(float deg) { return deg * kPiFloat / 180.0f; }
+ static float Rad2Deg(float rad) { return rad * 180.0f / kPiFloat; }
+
+ static float Round(float f) {
+ return (f > 0.f) ? std::floor(f + 0.5f) : std::ceil(f - 0.5f);
+ }
+ static double Round(double d) {
+ return (d > 0.0) ? std::floor(d + 0.5) : std::ceil(d - 0.5);
+ }
+
+ template <typename T> static T ClampToRange(T value, T min, T max) {
+ return std::min(std::max(value, min), max);
+ }
+
+ // Background: Existing transform code does not do the right thing in
+ // MapRect / MapQuad / ProjectQuad when there is a perspective projection that
+ // causes one of the transformed vertices to go to w < 0. In those cases, it
+ // is necessary to perform clipping in homogeneous coordinates, after applying
+ // the transform, before dividing-by-w to convert to cartesian coordinates.
+ //
+ // These functions return the axis-aligned rect that encloses the correctly
+ // clipped, transformed polygon.
+ static gfx::Rect MapClippedRect(const gfx::Transform& transform,
+ gfx::Rect rect);
+ static gfx::RectF MapClippedRect(const gfx::Transform& transform,
+ const gfx::RectF& rect);
+ static gfx::RectF ProjectClippedRect(const gfx::Transform& transform,
+ const gfx::RectF& rect);
+
+ // Returns an array of vertices that represent the clipped polygon. After
+ // returning, indexes from 0 to num_vertices_in_clipped_quad are valid in the
+ // clipped_quad array. Note that num_vertices_in_clipped_quad may be zero,
+ // which means the entire quad was clipped, and none of the vertices in the
+ // array are valid.
+ static void MapClippedQuad(const gfx::Transform& transform,
+ const gfx::QuadF& src_quad,
+ gfx::PointF clipped_quad[8],
+ int* num_vertices_in_clipped_quad);
+
+ static gfx::RectF ComputeEnclosingRectOfVertices(gfx::PointF vertices[],
+ int num_vertices);
+ static gfx::RectF ComputeEnclosingClippedRect(
+ const HomogeneousCoordinate& h1,
+ const HomogeneousCoordinate& h2,
+ const HomogeneousCoordinate& h3,
+ const HomogeneousCoordinate& h4);
+
+ // NOTE: These functions do not do correct clipping against w = 0 plane, but
+ // they correctly detect the clipped condition via the boolean clipped.
+ static gfx::QuadF MapQuad(const gfx::Transform& transform,
+ const gfx::QuadF& quad,
+ bool* clipped);
+ static gfx::PointF MapPoint(const gfx::Transform& transform,
+ gfx::PointF point,
+ bool* clipped);
+ static gfx::Point3F MapPoint(const gfx::Transform&,
+ const gfx::Point3F&,
+ bool* clipped);
+ static gfx::QuadF ProjectQuad(const gfx::Transform& transform,
+ const gfx::QuadF& quad,
+ bool* clipped);
+ static gfx::PointF ProjectPoint(const gfx::Transform& transform,
+ gfx::PointF point,
+ bool* clipped);
+
+ static gfx::Vector2dF ComputeTransform2dScaleComponents(const gfx::Transform&,
+ float fallbackValue);
+
+ // Returns the smallest angle between the given two vectors in degrees.
+ // Neither vector is assumed to be normalized.
+ static float SmallestAngleBetweenVectors(gfx::Vector2dF v1,
+ gfx::Vector2dF v2);
+
+ // Projects the |source| vector onto |destination|. Neither vector is assumed
+ // to be normalized.
+ static gfx::Vector2dF ProjectVector(gfx::Vector2dF source,
+ gfx::Vector2dF destination);
+
+ // Conversion to value.
+ static scoped_ptr<base::Value> AsValue(gfx::Size s);
+ static scoped_ptr<base::Value> AsValue(gfx::SizeF s);
+ static scoped_ptr<base::Value> AsValue(gfx::Rect r);
+ static bool FromValue(const base::Value*, gfx::Rect* out_rect);
+ static scoped_ptr<base::Value> AsValue(gfx::PointF q);
+ static scoped_ptr<base::Value> AsValue(const gfx::QuadF& q);
+ static scoped_ptr<base::Value> AsValue(const gfx::RectF& rect);
+ static scoped_ptr<base::Value> AsValue(const gfx::Transform& transform);
+
+ // Returns a base::Value representation of the floating point value.
+ // If the value is inf, returns max double/float representation.
+ static scoped_ptr<base::Value> AsValueSafely(double value);
+ static scoped_ptr<base::Value> AsValueSafely(float value);
+};
+
+} // namespace cc
+
+#endif // CC_BASE_MATH_UTIL_H_
diff --git a/chromium/cc/base/math_util_unittest.cc b/chromium/cc/base/math_util_unittest.cc
new file mode 100644
index 00000000000..d62280ddb5f
--- /dev/null
+++ b/chromium/cc/base/math_util_unittest.cc
@@ -0,0 +1,122 @@
+// 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 "cc/base/math_util.h"
+
+#include <cmath>
+
+#include "cc/test/geometry_test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/rect_f.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+namespace {
+
+TEST(MathUtilTest, ProjectionOfPerpendicularPlane) {
+ // In this case, the m33() element of the transform becomes zero, which could
+ // cause a divide-by-zero when projecting points/quads.
+
+ gfx::Transform transform;
+ transform.MakeIdentity();
+ transform.matrix().setDouble(2, 2, 0);
+
+ gfx::RectF rect = gfx::RectF(0, 0, 1, 1);
+ gfx::RectF projected_rect = MathUtil::ProjectClippedRect(transform, rect);
+
+ EXPECT_EQ(0, projected_rect.x());
+ EXPECT_EQ(0, projected_rect.y());
+ EXPECT_TRUE(projected_rect.IsEmpty());
+}
+
+TEST(MathUtilTest, EnclosingClippedRectUsesCorrectInitialBounds) {
+ HomogeneousCoordinate h1(-100, -100, 0, 1);
+ HomogeneousCoordinate h2(-10, -10, 0, 1);
+ HomogeneousCoordinate h3(10, 10, 0, -1);
+ HomogeneousCoordinate h4(100, 100, 0, -1);
+
+ // The bounds of the enclosing clipped rect should be -100 to -10 for both x
+ // and y. However, if there is a bug where the initial xmin/xmax/ymin/ymax are
+ // initialized to numeric_limits<float>::min() (which is zero, not -flt_max)
+ // then the enclosing clipped rect will be computed incorrectly.
+ gfx::RectF result = MathUtil::ComputeEnclosingClippedRect(h1, h2, h3, h4);
+
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(gfx::PointF(-100, -100), gfx::SizeF(90, 90)),
+ result);
+}
+
+TEST(MathUtilTest, EnclosingRectOfVerticesUsesCorrectInitialBounds) {
+ gfx::PointF vertices[3];
+ int num_vertices = 3;
+
+ vertices[0] = gfx::PointF(-10, -100);
+ vertices[1] = gfx::PointF(-100, -10);
+ vertices[2] = gfx::PointF(-30, -30);
+
+ // The bounds of the enclosing rect should be -100 to -10 for both x and y.
+ // However, if there is a bug where the initial xmin/xmax/ymin/ymax are
+ // initialized to numeric_limits<float>::min() (which is zero, not -flt_max)
+ // then the enclosing clipped rect will be computed incorrectly.
+ gfx::RectF result =
+ MathUtil::ComputeEnclosingRectOfVertices(vertices, num_vertices);
+
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(gfx::PointF(-100, -100), gfx::SizeF(90, 90)),
+ result);
+}
+
+TEST(MathUtilTest, SmallestAngleBetweenVectors) {
+ gfx::Vector2dF x(1, 0);
+ gfx::Vector2dF y(0, 1);
+ gfx::Vector2dF test_vector(0.5, 0.5);
+
+ // Orthogonal vectors are at an angle of 90 degress.
+ EXPECT_EQ(90, MathUtil::SmallestAngleBetweenVectors(x, y));
+
+ // A vector makes a zero angle with itself.
+ EXPECT_EQ(0, MathUtil::SmallestAngleBetweenVectors(x, x));
+ EXPECT_EQ(0, MathUtil::SmallestAngleBetweenVectors(y, y));
+ EXPECT_EQ(0, MathUtil::SmallestAngleBetweenVectors(test_vector, test_vector));
+
+ // Parallel but reversed vectors are at 180 degrees.
+ EXPECT_FLOAT_EQ(180, MathUtil::SmallestAngleBetweenVectors(x, -x));
+ EXPECT_FLOAT_EQ(180, MathUtil::SmallestAngleBetweenVectors(y, -y));
+ EXPECT_FLOAT_EQ(
+ 180, MathUtil::SmallestAngleBetweenVectors(test_vector, -test_vector));
+
+ // The test vector is at a known angle.
+ EXPECT_FLOAT_EQ(
+ 45, std::floor(MathUtil::SmallestAngleBetweenVectors(test_vector, x)));
+ EXPECT_FLOAT_EQ(
+ 45, std::floor(MathUtil::SmallestAngleBetweenVectors(test_vector, y)));
+}
+
+TEST(MathUtilTest, VectorProjection) {
+ gfx::Vector2dF x(1, 0);
+ gfx::Vector2dF y(0, 1);
+ gfx::Vector2dF test_vector(0.3f, 0.7f);
+
+ // Orthogonal vectors project to a zero vector.
+ EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), MathUtil::ProjectVector(x, y));
+ EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), MathUtil::ProjectVector(y, x));
+
+ // Projecting a vector onto the orthonormal basis gives the corresponding
+ // component of the vector.
+ EXPECT_VECTOR_EQ(gfx::Vector2dF(test_vector.x(), 0),
+ MathUtil::ProjectVector(test_vector, x));
+ EXPECT_VECTOR_EQ(gfx::Vector2dF(0, test_vector.y()),
+ MathUtil::ProjectVector(test_vector, y));
+
+ // Finally check than an arbitrary vector projected to another one gives a
+ // vector parallel to the second vector.
+ gfx::Vector2dF target_vector(0.5, 0.2f);
+ gfx::Vector2dF projected_vector =
+ MathUtil::ProjectVector(test_vector, target_vector);
+ EXPECT_EQ(projected_vector.x() / target_vector.x(),
+ projected_vector.y() / target_vector.y());
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/base/region.cc b/chromium/cc/base/region.cc
new file mode 100644
index 00000000000..1cda32d3ba2
--- /dev/null
+++ b/chromium/cc/base/region.cc
@@ -0,0 +1,133 @@
+// Copyright (c) 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 "cc/base/region.h"
+#include "base/values.h"
+
+namespace cc {
+
+Region::Region() {
+}
+
+Region::Region(const Region& region)
+ : skregion_(region.skregion_) {
+}
+
+Region::Region(gfx::Rect rect)
+ : skregion_(gfx::RectToSkIRect(rect)) {
+}
+
+Region::~Region() {
+}
+
+const Region& Region::operator=(gfx::Rect rect) {
+ skregion_ = SkRegion(gfx::RectToSkIRect(rect));
+ return *this;
+}
+
+const Region& Region::operator=(const Region& region) {
+ skregion_ = region.skregion_;
+ return *this;
+}
+
+void Region::Swap(Region* region) {
+ region->skregion_.swap(skregion_);
+}
+
+void Region::Clear() {
+ skregion_.setEmpty();
+}
+
+bool Region::IsEmpty() const {
+ return skregion_.isEmpty();
+}
+
+int Region::GetRegionComplexity() const {
+ return skregion_.computeRegionComplexity();
+}
+
+bool Region::Contains(gfx::Point point) const {
+ return skregion_.contains(point.x(), point.y());
+}
+
+bool Region::Contains(gfx::Rect rect) const {
+ if (rect.IsEmpty())
+ return true;
+ return skregion_.contains(gfx::RectToSkIRect(rect));
+}
+
+bool Region::Contains(const Region& region) const {
+ if (region.IsEmpty())
+ return true;
+ return skregion_.contains(region.skregion_);
+}
+
+bool Region::Intersects(gfx::Rect rect) const {
+ return skregion_.intersects(gfx::RectToSkIRect(rect));
+}
+
+bool Region::Intersects(const Region& region) const {
+ return skregion_.intersects(region.skregion_);
+}
+
+void Region::Subtract(gfx::Rect rect) {
+ skregion_.op(gfx::RectToSkIRect(rect), SkRegion::kDifference_Op);
+}
+
+void Region::Subtract(const Region& region) {
+ skregion_.op(region.skregion_, SkRegion::kDifference_Op);
+}
+
+void Region::Union(gfx::Rect rect) {
+ skregion_.op(gfx::RectToSkIRect(rect), SkRegion::kUnion_Op);
+}
+
+void Region::Union(const Region& region) {
+ skregion_.op(region.skregion_, SkRegion::kUnion_Op);
+}
+
+void Region::Intersect(gfx::Rect rect) {
+ skregion_.op(gfx::RectToSkIRect(rect), SkRegion::kIntersect_Op);
+}
+
+void Region::Intersect(const Region& region) {
+ skregion_.op(region.skregion_, SkRegion::kIntersect_Op);
+}
+
+std::string Region::ToString() const {
+ if (IsEmpty())
+ return gfx::Rect().ToString();
+
+ std::string result;
+ for (Iterator it(*this); it.has_rect(); it.next()) {
+ if (!result.empty())
+ result += " | ";
+ result += it.rect().ToString();
+ }
+ return result;
+}
+
+scoped_ptr<base::Value> Region::AsValue() const {
+ scoped_ptr<base::ListValue> result(new base::ListValue());
+ for (Iterator it(*this); it.has_rect(); it.next()) {
+ gfx::Rect rect(it.rect());
+ result->AppendInteger(rect.x());
+ result->AppendInteger(rect.y());
+ result->AppendInteger(rect.width());
+ result->AppendInteger(rect.height());
+ }
+ return result.PassAs<base::Value>();
+}
+
+Region::Iterator::Iterator() {
+}
+
+Region::Iterator::Iterator(const Region& region)
+ : it_(region.skregion_) {
+}
+
+Region::Iterator::~Iterator() {
+}
+
+} // namespace cc
diff --git a/chromium/cc/base/region.h b/chromium/cc/base/region.h
new file mode 100644
index 00000000000..26c1d2b1990
--- /dev/null
+++ b/chromium/cc/base/region.h
@@ -0,0 +1,135 @@
+// Copyright (c) 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.
+
+#ifndef CC_BASE_REGION_H_
+#define CC_BASE_REGION_H_
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "third_party/skia/include/core/SkRegion.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/skia_util.h"
+
+namespace base {
+class Value;
+}
+
+namespace cc {
+
+class CC_EXPORT Region {
+ public:
+ Region();
+ Region(const Region& region);
+ Region(gfx::Rect rect); // NOLINT(runtime/explicit)
+ ~Region();
+
+ const Region& operator=(gfx::Rect rect);
+ const Region& operator=(const Region& region);
+
+ void Swap(Region* region);
+ void Clear();
+ bool IsEmpty() const;
+ int GetRegionComplexity() const;
+
+ bool Contains(gfx::Point point) const;
+ bool Contains(gfx::Rect rect) const;
+ bool Contains(const Region& region) const;
+
+ bool Intersects(gfx::Rect rect) const;
+ bool Intersects(const Region& region) const;
+
+ void Subtract(gfx::Rect rect);
+ void Subtract(const Region& region);
+ void Union(gfx::Rect rect);
+ void Union(const Region& region);
+ void Intersect(gfx::Rect rect);
+ void Intersect(const Region& region);
+
+ bool Equals(const Region& other) const {
+ return skregion_ == other.skregion_;
+ }
+
+ gfx::Rect bounds() const {
+ return gfx::SkIRectToRect(skregion_.getBounds());
+ }
+
+ std::string ToString() const;
+ scoped_ptr<base::Value> AsValue() const;
+
+ class CC_EXPORT Iterator {
+ public:
+ Iterator();
+ explicit Iterator(const Region& region);
+ ~Iterator();
+
+ gfx::Rect rect() const {
+ return gfx::SkIRectToRect(it_.rect());
+ }
+
+ void next() {
+ it_.next();
+ }
+
+ bool has_rect() const {
+ return !it_.done();
+ }
+
+ private:
+ SkRegion::Iterator it_;
+ };
+
+ private:
+ SkRegion skregion_;
+};
+
+inline bool operator==(const Region& a, const Region& b) {
+ return a.Equals(b);
+}
+
+inline bool operator!=(const Region& a, const Region& b) {
+ return !(a == b);
+}
+
+inline Region SubtractRegions(const Region& a, const Region& b) {
+ Region result = a;
+ result.Subtract(b);
+ return result;
+}
+
+inline Region SubtractRegions(const Region& a, gfx::Rect b) {
+ Region result = a;
+ result.Subtract(b);
+ return result;
+}
+
+inline Region IntersectRegions(const Region& a, const Region& b) {
+ Region result = a;
+ result.Intersect(b);
+ return result;
+}
+
+inline Region IntersectRegions(const Region& a, gfx::Rect b) {
+ Region result = a;
+ result.Intersect(b);
+ return result;
+}
+
+inline Region UnionRegions(const Region& a, const Region& b) {
+ Region result = a;
+ result.Union(b);
+ return result;
+}
+
+inline Region UnionRegions(const Region& a, gfx::Rect b) {
+ Region result = a;
+ result.Union(b);
+ return result;
+}
+
+} // namespace cc
+
+#endif // CC_BASE_REGION_H_
diff --git a/chromium/cc/base/region_unittest.cc b/chromium/cc/base/region_unittest.cc
new file mode 100644
index 00000000000..c9a218d692a
--- /dev/null
+++ b/chromium/cc/base/region_unittest.cc
@@ -0,0 +1,454 @@
+// 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 "cc/base/region.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+#define TEST_INSIDE_RECT(r, x, y, w, h) \
+ EXPECT_TRUE(r.Contains(gfx::Point(x, y))); \
+ EXPECT_TRUE(r.Contains(gfx::Point(x + w - 1, y))); \
+ EXPECT_TRUE(r.Contains(gfx::Point(x, y + h - 1))); \
+ EXPECT_TRUE(r.Contains(gfx::Point(x + w - 1, y + h - 1))); \
+ EXPECT_TRUE(r.Contains(gfx::Point(x, y + h / 2))); \
+ EXPECT_TRUE(r.Contains(gfx::Point(x + w - 1, y + h / 2))); \
+ EXPECT_TRUE(r.Contains(gfx::Point(x + w / 2, y))); \
+ EXPECT_TRUE(r.Contains(gfx::Point(x + w / 2, y + h - 1))); \
+ EXPECT_TRUE(r.Contains(gfx::Point(x + w / 2, y + h / 2))); \
+
+#define TEST_LEFT_OF_RECT(r, x, y, w, h) \
+ EXPECT_FALSE(r.Contains(gfx::Point(x - 1, y))); \
+ EXPECT_FALSE(r.Contains(gfx::Point(x - 1, y + h - 1))); \
+
+#define TEST_RIGHT_OF_RECT(r, x, y, w, h) \
+ EXPECT_FALSE(r.Contains(gfx::Point(x + w, y))); \
+ EXPECT_FALSE(r.Contains(gfx::Point(x + w, y + h - 1))); \
+
+#define TEST_TOP_OF_RECT(r, x, y, w, h) \
+ EXPECT_FALSE(r.Contains(gfx::Point(x, y - 1))); \
+ EXPECT_FALSE(r.Contains(gfx::Point(x + w - 1, y - 1))); \
+
+#define TEST_BOTTOM_OF_RECT(r, x, y, w, h) \
+ EXPECT_FALSE(r.Contains(gfx::Point(x, y + h))); \
+ EXPECT_FALSE(r.Contains(gfx::Point(x + w - 1, y + h))); \
+
+TEST(RegionTest, ContainsPoint) {
+ Region r;
+
+ EXPECT_FALSE(r.Contains(gfx::Point(0, 0)));
+
+ r.Union(gfx::Rect(35, 35, 1, 1));
+ TEST_INSIDE_RECT(r, 35, 35, 1, 1);
+ TEST_LEFT_OF_RECT(r, 35, 35, 1, 1);
+ TEST_RIGHT_OF_RECT(r, 35, 35, 1, 1);
+ TEST_TOP_OF_RECT(r, 35, 35, 1, 1);
+ TEST_BOTTOM_OF_RECT(r, 35, 35, 1, 1);
+
+ r.Union(gfx::Rect(30, 30, 10, 10));
+ TEST_INSIDE_RECT(r, 30, 30, 10, 10);
+ TEST_LEFT_OF_RECT(r, 30, 30, 10, 10);
+ TEST_RIGHT_OF_RECT(r, 30, 30, 10, 10);
+ TEST_TOP_OF_RECT(r, 30, 30, 10, 10);
+ TEST_BOTTOM_OF_RECT(r, 30, 30, 10, 10);
+
+ r.Union(gfx::Rect(31, 40, 10, 10));
+ EXPECT_FALSE(r.Contains(gfx::Point(30, 40)));
+ EXPECT_TRUE(r.Contains(gfx::Point(31, 40)));
+ EXPECT_FALSE(r.Contains(gfx::Point(40, 39)));
+ EXPECT_TRUE(r.Contains(gfx::Point(40, 40)));
+
+ TEST_INSIDE_RECT(r, 30, 30, 10, 10);
+ TEST_LEFT_OF_RECT(r, 30, 30, 10, 10);
+ TEST_RIGHT_OF_RECT(r, 30, 30, 10, 10);
+ TEST_TOP_OF_RECT(r, 30, 30, 10, 10);
+ TEST_INSIDE_RECT(r, 31, 40, 10, 10);
+ TEST_LEFT_OF_RECT(r, 31, 40, 10, 10);
+ TEST_RIGHT_OF_RECT(r, 31, 40, 10, 10);
+ TEST_BOTTOM_OF_RECT(r, 31, 40, 10, 10);
+
+ r.Union(gfx::Rect(42, 40, 10, 10));
+
+ TEST_INSIDE_RECT(r, 42, 40, 10, 10);
+ TEST_LEFT_OF_RECT(r, 42, 40, 10, 10);
+ TEST_RIGHT_OF_RECT(r, 42, 40, 10, 10);
+ TEST_TOP_OF_RECT(r, 42, 40, 10, 10);
+ TEST_BOTTOM_OF_RECT(r, 42, 40, 10, 10);
+
+ TEST_INSIDE_RECT(r, 30, 30, 10, 10);
+ TEST_LEFT_OF_RECT(r, 30, 30, 10, 10);
+ TEST_RIGHT_OF_RECT(r, 30, 30, 10, 10);
+ TEST_TOP_OF_RECT(r, 30, 30, 10, 10);
+ TEST_INSIDE_RECT(r, 31, 40, 10, 10);
+ TEST_LEFT_OF_RECT(r, 31, 40, 10, 10);
+ TEST_RIGHT_OF_RECT(r, 31, 40, 10, 10);
+ TEST_BOTTOM_OF_RECT(r, 31, 40, 10, 10);
+}
+
+TEST(RegionTest, EmptySpan) {
+ Region r;
+ r.Union(gfx::Rect(5, 0, 10, 10));
+ r.Union(gfx::Rect(0, 5, 10, 10));
+ r.Subtract(gfx::Rect(7, 7, 10, 0));
+
+ for (Region::Iterator it(r); it.has_rect(); it.next())
+ EXPECT_FALSE(it.rect().IsEmpty());
+}
+
+#define TEST_NO_INTERSECT(a, b) { \
+ Region ar = a; \
+ Region br = b; \
+ EXPECT_FALSE(ar.Intersects(br)); \
+ EXPECT_FALSE(br.Intersects(ar)); \
+ EXPECT_FALSE(ar.Intersects(b)); \
+ EXPECT_FALSE(br.Intersects(a)); \
+}
+
+#define TEST_INTERSECT(a, b) { \
+ Region ar = a; \
+ Region br = b; \
+ EXPECT_TRUE(ar.Intersects(br)); \
+ EXPECT_TRUE(br.Intersects(ar)); \
+ EXPECT_TRUE(ar.Intersects(b)); \
+ EXPECT_TRUE(br.Intersects(a)); \
+}
+
+TEST(RegionTest, IntersectsRegion) {
+ Region r;
+
+ TEST_NO_INTERSECT(gfx::Rect(), gfx::Rect());
+ TEST_NO_INTERSECT(gfx::Rect(), gfx::Rect(0, 0, 1, 1));
+ TEST_NO_INTERSECT(gfx::Rect(), gfx::Rect(1, 1, 1, 1));
+
+ TEST_NO_INTERSECT(gfx::Rect(-1, -1, 2, 2), gfx::Rect());
+
+ r.Union(gfx::Rect(0, 0, 1, 1));
+ TEST_NO_INTERSECT(r, gfx::Rect());
+ TEST_INTERSECT(r, gfx::Rect(0, 0, 1, 1));
+ TEST_INTERSECT(r, gfx::Rect(0, 0, 2, 2));
+ TEST_INTERSECT(r, gfx::Rect(-1, 0, 2, 2));
+ TEST_INTERSECT(r, gfx::Rect(-1, -1, 2, 2));
+ TEST_INTERSECT(r, gfx::Rect(0, -1, 2, 2));
+ TEST_INTERSECT(r, gfx::Rect(-1, -1, 3, 3));
+
+ r.Union(gfx::Rect(0, 0, 3, 3));
+ r.Union(gfx::Rect(10, 0, 3, 3));
+ r.Union(gfx::Rect(0, 10, 13, 3));
+ TEST_NO_INTERSECT(r, gfx::Rect());
+ TEST_INTERSECT(r, gfx::Rect(1, 1, 1, 1));
+ TEST_INTERSECT(r, gfx::Rect(0, 0, 2, 2));
+ TEST_INTERSECT(r, gfx::Rect(1, 0, 2, 2));
+ TEST_INTERSECT(r, gfx::Rect(1, 1, 2, 2));
+ TEST_INTERSECT(r, gfx::Rect(0, 1, 2, 2));
+ TEST_INTERSECT(r, gfx::Rect(0, 0, 3, 3));
+ TEST_INTERSECT(r, gfx::Rect(-1, -1, 2, 2));
+ TEST_INTERSECT(r, gfx::Rect(2, -1, 2, 2));
+ TEST_INTERSECT(r, gfx::Rect(2, 2, 2, 2));
+ TEST_INTERSECT(r, gfx::Rect(-1, 2, 2, 2));
+
+ TEST_INTERSECT(r, gfx::Rect(11, 1, 1, 1));
+ TEST_INTERSECT(r, gfx::Rect(10, 0, 2, 2));
+ TEST_INTERSECT(r, gfx::Rect(11, 0, 2, 2));
+ TEST_INTERSECT(r, gfx::Rect(11, 1, 2, 2));
+ TEST_INTERSECT(r, gfx::Rect(10, 1, 2, 2));
+ TEST_INTERSECT(r, gfx::Rect(10, 0, 3, 3));
+ TEST_INTERSECT(r, gfx::Rect(9, -1, 2, 2));
+ TEST_INTERSECT(r, gfx::Rect(12, -1, 2, 2));
+ TEST_INTERSECT(r, gfx::Rect(12, 2, 2, 2));
+ TEST_INTERSECT(r, gfx::Rect(9, 2, 2, 2));
+
+ TEST_INTERSECT(r, gfx::Rect(0, -1, 13, 5));
+ TEST_INTERSECT(r, gfx::Rect(1, -1, 11, 5));
+ TEST_INTERSECT(r, gfx::Rect(2, -1, 9, 5));
+ TEST_INTERSECT(r, gfx::Rect(2, -1, 8, 5));
+ TEST_INTERSECT(r, gfx::Rect(3, -1, 8, 5));
+ TEST_NO_INTERSECT(r, gfx::Rect(3, -1, 7, 5));
+
+ TEST_INTERSECT(r, gfx::Rect(0, 1, 13, 1));
+ TEST_INTERSECT(r, gfx::Rect(1, 1, 11, 1));
+ TEST_INTERSECT(r, gfx::Rect(2, 1, 9, 1));
+ TEST_INTERSECT(r, gfx::Rect(2, 1, 8, 1));
+ TEST_INTERSECT(r, gfx::Rect(3, 1, 8, 1));
+ TEST_NO_INTERSECT(r, gfx::Rect(3, 1, 7, 1));
+
+ TEST_INTERSECT(r, gfx::Rect(0, 0, 13, 13));
+ TEST_INTERSECT(r, gfx::Rect(0, 1, 13, 11));
+ TEST_INTERSECT(r, gfx::Rect(0, 2, 13, 9));
+ TEST_INTERSECT(r, gfx::Rect(0, 2, 13, 8));
+ TEST_INTERSECT(r, gfx::Rect(0, 3, 13, 8));
+ TEST_NO_INTERSECT(r, gfx::Rect(0, 3, 13, 7));
+}
+
+TEST(RegionTest, ReadPastFullSpanVectorInIntersectsTest) {
+ Region r;
+
+ // This region has enough spans to fill its allocated Vector exactly.
+ r.Union(gfx::Rect(400, 300, 1, 800));
+ r.Union(gfx::Rect(785, 585, 1, 1));
+ r.Union(gfx::Rect(787, 585, 1, 1));
+ r.Union(gfx::Rect(0, 587, 16, 162));
+ r.Union(gfx::Rect(26, 590, 300, 150));
+ r.Union(gfx::Rect(196, 750, 1, 1));
+ r.Union(gfx::Rect(0, 766, 1, 1));
+ r.Union(gfx::Rect(0, 782, 1, 1));
+ r.Union(gfx::Rect(745, 798, 1, 1));
+ r.Union(gfx::Rect(795, 882, 10, 585));
+ r.Union(gfx::Rect(100, 1499, 586, 1));
+ r.Union(gfx::Rect(100, 1500, 585, 784));
+ // This query rect goes past the bottom of the Region, causing the
+ // test to reach the last span and try go past it. It should not read
+ // memory off the end of the span Vector.
+ TEST_NO_INTERSECT(r, gfx::Rect(0, 2184, 1, 150));
+}
+
+#define TEST_NO_CONTAINS(a, b) \
+ { \
+ Region ar = a; \
+ Region br = b; \
+ EXPECT_FALSE(ar.Contains(br)); \
+ EXPECT_FALSE(ar.Contains(b)); \
+ }
+
+#define TEST_CONTAINS(a, b) \
+ { \
+ Region ar = a; \
+ Region br = b; \
+ EXPECT_TRUE(ar.Contains(br)); \
+ EXPECT_TRUE(ar.Contains(b)); \
+ }
+
+TEST(RegionTest, ContainsRegion) {
+ TEST_CONTAINS(gfx::Rect(), gfx::Rect());
+ TEST_CONTAINS(gfx::Rect(0, 0, 1, 1), gfx::Rect());
+ TEST_CONTAINS(gfx::Rect(10, 10, 1, 1), gfx::Rect());
+
+ TEST_NO_CONTAINS(gfx::Rect(), gfx::Rect(0, 0, 1, 1));
+ TEST_NO_CONTAINS(gfx::Rect(), gfx::Rect(1, 1, 1, 1));
+
+ TEST_NO_CONTAINS(gfx::Rect(10, 10, 1, 1), gfx::Rect(11, 10, 1, 1));
+ TEST_NO_CONTAINS(gfx::Rect(10, 10, 1, 1), gfx::Rect(10, 11, 1, 1));
+ TEST_NO_CONTAINS(gfx::Rect(10, 10, 1, 1), gfx::Rect(9, 10, 1, 1));
+ TEST_NO_CONTAINS(gfx::Rect(10, 10, 1, 1), gfx::Rect(10, 9, 1, 1));
+ TEST_NO_CONTAINS(gfx::Rect(10, 10, 1, 1), gfx::Rect(9, 9, 2, 2));
+ TEST_NO_CONTAINS(gfx::Rect(10, 10, 1, 1), gfx::Rect(10, 9, 2, 2));
+ TEST_NO_CONTAINS(gfx::Rect(10, 10, 1, 1), gfx::Rect(9, 10, 2, 2));
+ TEST_NO_CONTAINS(gfx::Rect(10, 10, 1, 1), gfx::Rect(10, 10, 2, 2));
+ TEST_NO_CONTAINS(gfx::Rect(10, 10, 1, 1), gfx::Rect(9, 9, 3, 3));
+
+ Region h_lines;
+ for (int i = 10; i < 20; i += 2)
+ h_lines.Union(gfx::Rect(i, 10, 1, 10));
+
+ TEST_CONTAINS(gfx::Rect(10, 10, 9, 10), h_lines);
+ TEST_NO_CONTAINS(gfx::Rect(10, 10, 9, 9), h_lines);
+ TEST_NO_CONTAINS(gfx::Rect(10, 11, 9, 9), h_lines);
+ TEST_NO_CONTAINS(gfx::Rect(10, 10, 8, 10), h_lines);
+ TEST_NO_CONTAINS(gfx::Rect(11, 10, 8, 10), h_lines);
+
+ Region v_lines;
+ for (int i = 10; i < 20; i += 2)
+ v_lines.Union(gfx::Rect(10, i, 10, 1));
+
+ TEST_CONTAINS(gfx::Rect(10, 10, 10, 9), v_lines);
+ TEST_NO_CONTAINS(gfx::Rect(10, 10, 9, 9), v_lines);
+ TEST_NO_CONTAINS(gfx::Rect(11, 10, 9, 9), v_lines);
+ TEST_NO_CONTAINS(gfx::Rect(10, 10, 10, 8), v_lines);
+ TEST_NO_CONTAINS(gfx::Rect(10, 11, 10, 8), v_lines);
+
+ Region grid;
+ for (int i = 10; i < 20; i += 2)
+ for (int j = 10; j < 20; j += 2)
+ grid.Union(gfx::Rect(i, j, 1, 1));
+
+ TEST_CONTAINS(gfx::Rect(10, 10, 9, 9), grid);
+ TEST_NO_CONTAINS(gfx::Rect(10, 10, 9, 8), grid);
+ TEST_NO_CONTAINS(gfx::Rect(10, 11, 9, 8), grid);
+ TEST_NO_CONTAINS(gfx::Rect(10, 10, 8, 9), grid);
+ TEST_NO_CONTAINS(gfx::Rect(11, 10, 8, 9), grid);
+
+ TEST_CONTAINS(h_lines, h_lines);
+ TEST_CONTAINS(v_lines, v_lines);
+ TEST_NO_CONTAINS(v_lines, h_lines);
+ TEST_NO_CONTAINS(h_lines, v_lines);
+ TEST_CONTAINS(grid, grid);
+ TEST_CONTAINS(h_lines, grid);
+ TEST_CONTAINS(v_lines, grid);
+ TEST_NO_CONTAINS(grid, h_lines);
+ TEST_NO_CONTAINS(grid, v_lines);
+
+ for (int i = 10; i < 20; i += 2)
+ TEST_CONTAINS(h_lines, gfx::Rect(i, 10, 1, 10));
+
+ for (int i = 10; i < 20; i += 2)
+ TEST_CONTAINS(v_lines, gfx::Rect(10, i, 10, 1));
+
+ for (int i = 10; i < 20; i += 2)
+ for (int j = 10; j < 20; j += 2)
+ TEST_CONTAINS(grid, gfx::Rect(i, j, 1, 1));
+
+ Region container;
+ container.Union(gfx::Rect(0, 0, 40, 20));
+ container.Union(gfx::Rect(0, 20, 41, 20));
+ TEST_CONTAINS(container, gfx::Rect(5, 5, 30, 30));
+
+ container.Clear();
+ container.Union(gfx::Rect(0, 0, 10, 10));
+ container.Union(gfx::Rect(0, 30, 10, 10));
+ container.Union(gfx::Rect(30, 30, 10, 10));
+ container.Union(gfx::Rect(30, 0, 10, 10));
+ TEST_NO_CONTAINS(container, gfx::Rect(5, 5, 30, 30));
+
+ container.Clear();
+ container.Union(gfx::Rect(0, 0, 10, 10));
+ container.Union(gfx::Rect(0, 30, 10, 10));
+ container.Union(gfx::Rect(30, 0, 10, 40));
+ TEST_NO_CONTAINS(container, gfx::Rect(5, 5, 30, 30));
+
+ container.Clear();
+ container.Union(gfx::Rect(30, 0, 10, 10));
+ container.Union(gfx::Rect(30, 30, 10, 10));
+ container.Union(gfx::Rect(0, 0, 10, 40));
+ TEST_NO_CONTAINS(container, gfx::Rect(5, 5, 30, 30));
+
+ container.Clear();
+ container.Union(gfx::Rect(0, 0, 10, 40));
+ container.Union(gfx::Rect(30, 0, 10, 40));
+ TEST_NO_CONTAINS(container, gfx::Rect(5, 5, 30, 30));
+
+ container.Clear();
+ container.Union(gfx::Rect(0, 0, 40, 40));
+ TEST_NO_CONTAINS(container, gfx::Rect(10, -1, 20, 10));
+
+ container.Clear();
+ container.Union(gfx::Rect(0, 0, 40, 40));
+ TEST_NO_CONTAINS(container, gfx::Rect(10, 31, 20, 10));
+
+ container.Clear();
+ container.Union(gfx::Rect(0, 0, 40, 20));
+ container.Union(gfx::Rect(0, 20, 41, 20));
+ TEST_NO_CONTAINS(container, gfx::Rect(-1, 10, 10, 20));
+
+ container.Clear();
+ container.Union(gfx::Rect(0, 0, 40, 20));
+ container.Union(gfx::Rect(0, 20, 41, 20));
+ TEST_NO_CONTAINS(container, gfx::Rect(31, 10, 10, 20));
+
+ container.Clear();
+ container.Union(gfx::Rect(0, 0, 40, 40));
+ container.Subtract(gfx::Rect(0, 20, 60, 0));
+ TEST_NO_CONTAINS(container, gfx::Rect(31, 10, 10, 20));
+
+ container.Clear();
+ container.Union(gfx::Rect(0, 0, 60, 20));
+ container.Union(gfx::Rect(30, 20, 10, 20));
+ TEST_NO_CONTAINS(container, gfx::Rect(0, 0, 10, 39));
+ TEST_NO_CONTAINS(container, gfx::Rect(0, 0, 10, 40));
+ TEST_NO_CONTAINS(container, gfx::Rect(0, 0, 10, 41));
+ TEST_NO_CONTAINS(container, gfx::Rect(29, 0, 10, 39));
+ TEST_CONTAINS(container, gfx::Rect(30, 0, 10, 40));
+ TEST_NO_CONTAINS(container, gfx::Rect(31, 0, 10, 41));
+ TEST_NO_CONTAINS(container, gfx::Rect(49, 0, 10, 39));
+ TEST_NO_CONTAINS(container, gfx::Rect(50, 0, 10, 40));
+ TEST_NO_CONTAINS(container, gfx::Rect(51, 0, 10, 41));
+
+ container.Clear();
+ container.Union(gfx::Rect(30, 0, 10, 20));
+ container.Union(gfx::Rect(0, 20, 60, 20));
+ TEST_NO_CONTAINS(container, gfx::Rect(0, 0, 10, 39));
+ TEST_NO_CONTAINS(container, gfx::Rect(0, 0, 10, 40));
+ TEST_NO_CONTAINS(container, gfx::Rect(0, 0, 10, 41));
+ TEST_NO_CONTAINS(container, gfx::Rect(29, 0, 10, 39));
+ TEST_CONTAINS(container, gfx::Rect(30, 0, 10, 40));
+ TEST_NO_CONTAINS(container, gfx::Rect(31, 0, 10, 41));
+ TEST_NO_CONTAINS(container, gfx::Rect(49, 0, 10, 39));
+ TEST_NO_CONTAINS(container, gfx::Rect(50, 0, 10, 40));
+ TEST_NO_CONTAINS(container, gfx::Rect(51, 0, 10, 41));
+}
+
+TEST(RegionTest, Union) {
+ Region r;
+ Region r2;
+
+ // A rect uniting a contained rect does not change the region.
+ r2 = r = gfx::Rect(0, 0, 50, 50);
+ r2.Union(gfx::Rect(20, 20, 10, 10));
+ EXPECT_EQ(r, r2);
+
+ // A rect uniting a containing rect gives back the containing rect.
+ r = gfx::Rect(0, 0, 50, 50);
+ r.Union(gfx::Rect(0, 0, 100, 100));
+ EXPECT_EQ(Region(gfx::Rect(0, 0, 100, 100)), r);
+
+ // A complex region uniting a contained rect does not change the region.
+ r = gfx::Rect(0, 0, 50, 50);
+ r.Union(gfx::Rect(100, 0, 50, 50));
+ r2 = r;
+ r2.Union(gfx::Rect(20, 20, 10, 10));
+ EXPECT_EQ(r, r2);
+
+ // A complex region uniting a containing rect gives back the containing rect.
+ r = gfx::Rect(0, 0, 50, 50);
+ r.Union(gfx::Rect(100, 0, 50, 50));
+ r.Union(gfx::Rect(0, 0, 500, 500));
+ EXPECT_EQ(Region(gfx::Rect(0, 0, 500, 500)), r);
+}
+
+TEST(RegionTest, IsEmpty) {
+ EXPECT_TRUE(Region().IsEmpty());
+ EXPECT_TRUE(Region(gfx::Rect()).IsEmpty());
+ EXPECT_TRUE(Region(Region()).IsEmpty());
+ EXPECT_TRUE(Region(gfx::Rect(10, 10, 10, 0)).IsEmpty());
+ EXPECT_TRUE(Region(gfx::Rect(10, 10, 0, 10)).IsEmpty());
+ EXPECT_TRUE(Region(gfx::Rect(-10, 10, 10, 0)).IsEmpty());
+ EXPECT_TRUE(Region(gfx::Rect(-10, 10, 0, 10)).IsEmpty());
+ EXPECT_FALSE(Region(gfx::Rect(-1, -1, 1, 1)).IsEmpty());
+ EXPECT_FALSE(Region(gfx::Rect(0, 0, 1, 1)).IsEmpty());
+ EXPECT_FALSE(Region(gfx::Rect(0, 0, 2, 2)).IsEmpty());
+
+ EXPECT_TRUE(SkIRect::MakeXYWH(10, 10, 10, 0).isEmpty());
+ EXPECT_TRUE(SkIRect::MakeXYWH(10, 10, 0, 10).isEmpty());
+ EXPECT_TRUE(SkIRect::MakeXYWH(-10, 10, 10, 0).isEmpty());
+ EXPECT_TRUE(SkIRect::MakeXYWH(-10, 10, 0, 10).isEmpty());
+ EXPECT_FALSE(SkIRect::MakeXYWH(-1, -1, 1, 1).isEmpty());
+ EXPECT_FALSE(SkIRect::MakeXYWH(0, 0, 1, 1).isEmpty());
+ EXPECT_FALSE(SkIRect::MakeXYWH(0, 0, 2, 2).isEmpty());
+}
+
+TEST(RegionTest, Clear) {
+ Region r;
+
+ r = gfx::Rect(0, 0, 50, 50);
+ EXPECT_FALSE(r.IsEmpty());
+ r.Clear();
+ EXPECT_TRUE(r.IsEmpty());
+
+ r = gfx::Rect(0, 0, 50, 50);
+ r.Union(gfx::Rect(100, 0, 50, 50));
+ r.Union(gfx::Rect(0, 0, 500, 500));
+ EXPECT_FALSE(r.IsEmpty());
+ r.Clear();
+ EXPECT_TRUE(r.IsEmpty());
+}
+
+TEST(RegionSwap, Swap) {
+ Region r1, r2, r3;
+
+ r1 = gfx::Rect(0, 0, 50, 50);
+ r1.Swap(&r2);
+ EXPECT_TRUE(r1.IsEmpty());
+ EXPECT_EQ(r2.ToString(), Region(gfx::Rect(0, 0, 50, 50)).ToString());
+
+ r1 = gfx::Rect(0, 0, 50, 50);
+ r1.Union(gfx::Rect(100, 0, 50, 50));
+ r1.Union(gfx::Rect(0, 0, 500, 500));
+ r3 = r1;
+ r1.Swap(&r2);
+ EXPECT_EQ(r1.ToString(), Region(gfx::Rect(0, 0, 50, 50)).ToString());
+ EXPECT_EQ(r2.ToString(), r3.ToString());
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/base/scoped_ptr_algorithm.h b/chromium/cc/base/scoped_ptr_algorithm.h
new file mode 100644
index 00000000000..79f4eee49e6
--- /dev/null
+++ b/chromium/cc/base/scoped_ptr_algorithm.h
@@ -0,0 +1,30 @@
+// 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.
+
+#ifndef CC_BASE_SCOPED_PTR_ALGORITHM_H_
+#define CC_BASE_SCOPED_PTR_ALGORITHM_H_
+
+namespace cc {
+
+// ScopedContainers need to implement a swap() method since they do not allow
+// assignment to their iterators.
+template <class ForwardIterator, class Predicate, class ScopedContainer>
+ForwardIterator remove_if(
+ ScopedContainer* container,
+ ForwardIterator first,
+ ForwardIterator last,
+ Predicate predicate) {
+ ForwardIterator result = first;
+ for (; first != last; ++first) {
+ if (!predicate(*first)) {
+ container->swap(first, result);
+ ++result;
+ }
+ }
+ return result;
+}
+
+} // namespace cc
+
+#endif // CC_BASE_SCOPED_PTR_ALGORITHM_H_
diff --git a/chromium/cc/base/scoped_ptr_deque.h b/chromium/cc/base/scoped_ptr_deque.h
new file mode 100644
index 00000000000..cb4adfc15be
--- /dev/null
+++ b/chromium/cc/base/scoped_ptr_deque.h
@@ -0,0 +1,137 @@
+// 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.
+
+#ifndef CC_BASE_SCOPED_PTR_DEQUE_H_
+#define CC_BASE_SCOPED_PTR_DEQUE_H_
+
+#include <algorithm>
+#include <deque>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+
+namespace cc {
+
+// This type acts like a deque<scoped_ptr> based on top of std::deque. The
+// ScopedPtrDeque has ownership of all elements in the deque.
+template <typename T>
+class ScopedPtrDeque {
+ public:
+ typedef typename std::deque<T*>::const_iterator const_iterator;
+ typedef typename std::deque<T*>::reverse_iterator reverse_iterator;
+ typedef typename std::deque<T*>::const_reverse_iterator
+ const_reverse_iterator;
+
+#if defined(OS_ANDROID)
+ // On Android the iterator is not a class, so we can't block assignment.
+ typedef typename std::deque<T*>::iterator iterator;
+#else
+ // Ban setting values on the iterator directly. New pointers must be passed
+ // to methods on the ScopedPtrDeque class to appear in the deque.
+ class iterator : public std::deque<T*>::iterator {
+ public:
+ explicit iterator(const typename std::deque<T*>::iterator& other)
+ : std::deque<T*>::iterator(other) {}
+ T* const& operator*() { return std::deque<T*>::iterator::operator*(); }
+ };
+#endif
+
+ ScopedPtrDeque() {}
+
+ ~ScopedPtrDeque() { clear(); }
+
+ size_t size() const {
+ return data_.size();
+ }
+
+ T* at(size_t index) const {
+ DCHECK(index < size());
+ return data_[index];
+ }
+
+ T* operator[](size_t index) const {
+ return at(index);
+ }
+
+ T* front() const {
+ DCHECK(!empty());
+ return at(0);
+ }
+
+ T* back() const {
+ DCHECK(!empty());
+ return at(size() - 1);
+ }
+
+ bool empty() const {
+ return data_.empty();
+ }
+
+ scoped_ptr<T> take_front() {
+ scoped_ptr<T> ret(front());
+ data_.pop_front();
+ return ret.Pass();
+ }
+
+ scoped_ptr<T> take_back() {
+ scoped_ptr<T> ret(back());
+ data_.pop_back();
+ return ret.Pass();
+ }
+
+ void clear() {
+ STLDeleteElements(&data_);
+ }
+
+ void push_front(scoped_ptr<T> item) {
+ data_.push_front(item.release());
+ }
+
+ void push_back(scoped_ptr<T> item) {
+ data_.push_back(item.release());
+ }
+
+ void insert(iterator position, scoped_ptr<T> item) {
+ DCHECK(position <= end());
+ data_.insert(position, item.release());
+ }
+
+ scoped_ptr<T> take(iterator position) {
+ DCHECK(position < end());
+ scoped_ptr<T> ret(*position);
+ data_.erase(position);
+ return ret.Pass();
+ }
+
+ void swap(iterator a, iterator b) {
+ DCHECK(a < end());
+ DCHECK(b < end());
+ if (a == end() || b == end() || a == b)
+ return;
+ typename std::deque<T*>::iterator writable_a = a;
+ typename std::deque<T*>::iterator writable_b = b;
+ std::swap(*writable_a, *writable_b);
+ }
+
+ iterator begin() { return static_cast<iterator>(data_.begin()); }
+ const_iterator begin() const { return data_.begin(); }
+ iterator end() { return static_cast<iterator>(data_.end()); }
+ const_iterator end() const { return data_.end(); }
+
+ reverse_iterator rbegin() { return data_.rbegin(); }
+ const_reverse_iterator rbegin() const { return data_.rbegin(); }
+ reverse_iterator rend() { return data_.rend(); }
+ const_reverse_iterator rend() const { return data_.rend(); }
+
+ private:
+ std::deque<T*> data_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedPtrDeque);
+};
+
+} // namespace cc
+
+#endif // CC_BASE_SCOPED_PTR_DEQUE_H_
diff --git a/chromium/cc/base/scoped_ptr_hash_map.h b/chromium/cc/base/scoped_ptr_hash_map.h
new file mode 100644
index 00000000000..47135822d2d
--- /dev/null
+++ b/chromium/cc/base/scoped_ptr_hash_map.h
@@ -0,0 +1,157 @@
+// 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.
+
+#ifndef CC_BASE_SCOPED_PTR_HASH_MAP_H_
+#define CC_BASE_SCOPED_PTR_HASH_MAP_H_
+
+#include <algorithm>
+#include <utility>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+
+namespace cc {
+
+// This type acts like a hash_map<K, scoped_ptr<V> >, based on top of
+// base::hash_map. The ScopedPtrHashMap has ownership of all values in the data
+// structure.
+template <typename Key, typename Value>
+class ScopedPtrHashMap {
+ typedef base::hash_map<Key, Value*> Container;
+
+ public:
+ typedef typename Container::iterator iterator;
+ typedef typename Container::const_iterator const_iterator;
+
+ ScopedPtrHashMap() {}
+
+ ~ScopedPtrHashMap() { clear(); }
+
+ void swap(ScopedPtrHashMap<Key, Value>& other) {
+ data_.swap(other.data_);
+ }
+
+ std::pair<iterator, bool> insert(
+ std::pair<Key, const scoped_ptr<Value> > pair) {
+ return data_.insert(
+ std::pair<Key, Value*>(pair.first, pair.second.release()));
+ }
+
+ // Replaces value but not key if key is already present.
+ std::pair<iterator, bool> set(Key key, scoped_ptr<Value> data) {
+ iterator it = find(key);
+ if (it != end())
+ erase(it);
+ Value* raw_ptr = data.release();
+ return data_.insert(std::pair<Key, Value*>(key, raw_ptr));
+ }
+
+ // Does nothing if key is already present
+ std::pair<iterator, bool> add(Key key, scoped_ptr<Value> data) {
+ Value* raw_ptr = data.release();
+ return data_.insert(std::pair<Key, Value*>(key, raw_ptr));
+ }
+
+ void erase(iterator it) {
+ if (it->second)
+ delete it->second;
+ data_.erase(it);
+ }
+
+ size_t erase(const Key& k) {
+ iterator it = data_.find(k);
+ if (it == data_.end())
+ return 0;
+ erase(it);
+ return 1;
+ }
+
+ scoped_ptr<Value> take(iterator it) {
+ DCHECK(it != data_.end());
+ if (it == data_.end())
+ return scoped_ptr<Value>();
+
+ Key key = it->first;
+ scoped_ptr<Value> ret(it->second);
+ data_.erase(it);
+ data_.insert(std::pair<Key, Value*>(key, static_cast<Value*>(NULL)));
+ return ret.Pass();
+ }
+
+ scoped_ptr<Value> take(const Key& k) {
+ iterator it = find(k);
+ if (it == data_.end())
+ return scoped_ptr<Value>();
+
+ return take(it);
+ }
+
+ scoped_ptr<Value> take_and_erase(iterator it) {
+ DCHECK(it != data_.end());
+ if (it == data_.end())
+ return scoped_ptr<Value>();
+
+ scoped_ptr<Value> ret(it->second);
+ data_.erase(it);
+ return ret.Pass();
+ }
+
+ scoped_ptr<Value> take_and_erase(const Key& k) {
+ iterator it = find(k);
+ if (it == data_.end())
+ return scoped_ptr<Value>();
+
+ return take_and_erase(it);
+ }
+
+ // Returns the first element in the hash_map that matches the given key.
+ // If no such element exists it returns NULL.
+ Value* get(const Key& k) const {
+ const_iterator it = find(k);
+ if (it == end())
+ return 0;
+ return it->second;
+ }
+
+ inline bool contains(const Key& k) const { return data_.count(k) > 0; }
+
+ inline void clear() { STLDeleteValues(&data_); }
+
+ inline const_iterator find(const Key& k) const { return data_.find(k); }
+ inline iterator find(const Key& k) { return data_.find(k); }
+
+ inline size_t count(const Key& k) const { return data_.count(k); }
+ inline std::pair<const_iterator, const_iterator> equal_range(
+ const Key& k) const {
+ return data_.equal_range(k);
+ }
+ inline std::pair<iterator, iterator> equal_range(const Key& k) {
+ return data_.equal_range(k);
+ }
+
+ inline size_t size() const { return data_.size(); }
+ inline size_t max_size() const { return data_.max_size(); }
+
+ inline bool empty() const { return data_.empty(); }
+
+ inline size_t bucket_count() const { return data_.bucket_count(); }
+ inline void resize(size_t size) const { return data_.resize(size); }
+
+ inline iterator begin() { return data_.begin(); }
+ inline const_iterator begin() const { return data_.begin(); }
+ inline iterator end() { return data_.end(); }
+ inline const_iterator end() const { return data_.end(); }
+
+ private:
+ Container data_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedPtrHashMap);
+};
+
+} // namespace cc
+
+#endif // CC_BASE_SCOPED_PTR_HASH_MAP_H_
diff --git a/chromium/cc/base/scoped_ptr_vector.h b/chromium/cc/base/scoped_ptr_vector.h
new file mode 100644
index 00000000000..856e2f51cd1
--- /dev/null
+++ b/chromium/cc/base/scoped_ptr_vector.h
@@ -0,0 +1,180 @@
+// 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.
+
+#ifndef CC_BASE_SCOPED_PTR_VECTOR_H_
+#define CC_BASE_SCOPED_PTR_VECTOR_H_
+
+#include <algorithm>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+
+namespace cc {
+
+// This type acts like a vector<scoped_ptr> based on top of std::vector. The
+// ScopedPtrVector has ownership of all elements in the vector.
+template <typename T>
+class ScopedPtrVector {
+ public:
+ typedef typename std::vector<T*>::const_iterator const_iterator;
+ typedef typename std::vector<T*>::reverse_iterator reverse_iterator;
+ typedef typename std::vector<T*>::const_reverse_iterator
+ const_reverse_iterator;
+
+#if defined(OS_ANDROID)
+ // On Android the iterator is not a class, so we can't block assignment.
+ typedef typename std::vector<T*>::iterator iterator;
+#else
+ // Ban setting values on the iterator directly. New pointers must be passed
+ // to methods on the ScopedPtrVector class to appear in the vector.
+ class iterator : public std::vector<T*>::iterator {
+ public:
+ iterator(const typename std::vector<T*>::iterator& other) // NOLINT
+ : std::vector<T*>::iterator(other) {}
+ T* const& operator*() { return std::vector<T*>::iterator::operator*(); }
+ };
+#endif
+
+ ScopedPtrVector() {}
+
+ ~ScopedPtrVector() { clear(); }
+
+ size_t size() const {
+ return data_.size();
+ }
+
+ T* at(size_t index) const {
+ DCHECK(index < size());
+ return data_[index];
+ }
+
+ T* operator[](size_t index) const {
+ return at(index);
+ }
+
+ T* front() const {
+ DCHECK(!empty());
+ return at(0);
+ }
+
+ T* back() const {
+ DCHECK(!empty());
+ return at(size() - 1);
+ }
+
+ bool empty() const {
+ return data_.empty();
+ }
+
+ scoped_ptr<T> take(iterator position) {
+ if (position == end())
+ return scoped_ptr<T>();
+ DCHECK(position < end());
+
+ typename std::vector<T*>::iterator writable_position = position;
+ scoped_ptr<T> ret(*writable_position);
+ *writable_position = NULL;
+ return ret.Pass();
+ }
+
+ scoped_ptr<T> take_back() {
+ DCHECK(!empty());
+ if (empty())
+ return scoped_ptr<T>(NULL);
+ return take(end() - 1);
+ }
+
+ void erase(iterator position) {
+ if (position == end())
+ return;
+ typename std::vector<T*>::iterator writable_position = position;
+ delete *writable_position;
+ data_.erase(position);
+ }
+
+ void erase(iterator first, iterator last) {
+ DCHECK(first <= last);
+ for (iterator it = first; it != last; ++it) {
+ DCHECK(it < end());
+
+ typename std::vector<T*>::iterator writable_it = it;
+ delete *writable_it;
+ }
+ data_.erase(first, last);
+ }
+
+ void reserve(size_t size) {
+ data_.reserve(size);
+ }
+
+ void clear() {
+ STLDeleteElements(&data_);
+ }
+
+ void push_back(scoped_ptr<T> item) {
+ data_.push_back(item.release());
+ }
+
+ void pop_back() {
+ delete data_.back();
+ data_.pop_back();
+ }
+
+ void insert(iterator position, scoped_ptr<T> item) {
+ DCHECK(position <= end());
+ data_.insert(position, item.release());
+ }
+
+ void insert_and_take(iterator position,
+ ScopedPtrVector<T>& other) {
+ std::vector<T*> tmp_data;
+ for (ScopedPtrVector<T>::iterator it = other.begin();
+ it != other.end();
+ ++it) {
+ tmp_data.push_back(other.take(it).release());
+ }
+ data_.insert(position, tmp_data.begin(), tmp_data.end());
+ }
+
+ void swap(ScopedPtrVector<T>& other) {
+ data_.swap(other.data_);
+ }
+
+ void swap(iterator a, iterator b) {
+ DCHECK(a < end());
+ DCHECK(b < end());
+ if (a == end() || b == end() || a == b)
+ return;
+ typename std::vector<T*>::iterator writable_a = a;
+ typename std::vector<T*>::iterator writable_b = b;
+ std::swap(*writable_a, *writable_b);
+ }
+
+ template<class Compare>
+ inline void sort(Compare comp) {
+ std::sort(data_.begin(), data_.end(), comp);
+ }
+
+ iterator begin() { return static_cast<iterator>(data_.begin()); }
+ const_iterator begin() const { return data_.begin(); }
+ iterator end() { return static_cast<iterator>(data_.end()); }
+ const_iterator end() const { return data_.end(); }
+
+ reverse_iterator rbegin() { return data_.rbegin(); }
+ const_reverse_iterator rbegin() const { return data_.rbegin(); }
+ reverse_iterator rend() { return data_.rend(); }
+ const_reverse_iterator rend() const { return data_.rend(); }
+
+ private:
+ std::vector<T*> data_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedPtrVector);
+};
+
+} // namespace cc
+
+#endif // CC_BASE_SCOPED_PTR_VECTOR_H_
diff --git a/chromium/cc/base/scoped_ptr_vector_unittest.cc b/chromium/cc/base/scoped_ptr_vector_unittest.cc
new file mode 100644
index 00000000000..4e450b9c823
--- /dev/null
+++ b/chromium/cc/base/scoped_ptr_vector_unittest.cc
@@ -0,0 +1,72 @@
+// 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 "cc/base/scoped_ptr_vector.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace cc {
+namespace {
+
+class Data {
+ public:
+ static scoped_ptr<Data> Create(int i) { return make_scoped_ptr(new Data(i)); }
+ int data() const { return data_; }
+ private:
+ explicit Data(int i) : data_(i) {}
+ int data_;
+};
+
+TEST(ScopedPtrVectorTest, PushBack) {
+ ScopedPtrVector<Data> v;
+
+ // Insert 5 things into the vector.
+ v.push_back(Data::Create(1));
+ v.push_back(Data::Create(2));
+ v.push_back(Data::Create(3));
+ v.push_back(Data::Create(4));
+ v.push_back(Data::Create(5));
+
+ EXPECT_EQ(5u, v.size());
+ EXPECT_EQ(1, v[0]->data());
+ EXPECT_EQ(2, v[1]->data());
+ EXPECT_EQ(3, v[2]->data());
+ EXPECT_EQ(4, v[3]->data());
+ EXPECT_EQ(5, v[4]->data());
+}
+
+TEST(ScopedPtrVectorTest, InsertAndTake) {
+ // Insert 3 things into each vector.
+ ScopedPtrVector<Data> v;
+ v.push_back(Data::Create(1));
+ v.push_back(Data::Create(2));
+ v.push_back(Data::Create(6));
+
+ ScopedPtrVector<Data> v2;
+ v2.push_back(Data::Create(3));
+ v2.push_back(Data::Create(4));
+ v2.push_back(Data::Create(5));
+
+ ScopedPtrVector<Data>::iterator it = v.begin();
+ ++it;
+ ++it;
+ EXPECT_EQ(6, (*it)->data());
+
+ v.insert_and_take(it, v2);
+
+ EXPECT_EQ(6u, v.size());
+ EXPECT_EQ(1, v[0]->data());
+ EXPECT_EQ(2, v[1]->data());
+ EXPECT_EQ(3, v[2]->data());
+ EXPECT_EQ(4, v[3]->data());
+ EXPECT_EQ(5, v[4]->data());
+ EXPECT_EQ(6, v[5]->data());
+
+ EXPECT_EQ(3u, v2.size());
+ EXPECT_EQ(NULL, v2[0]);
+ EXPECT_EQ(NULL, v2[1]);
+ EXPECT_EQ(NULL, v2[2]);
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/base/switches.cc b/chromium/cc/base/switches.cc
new file mode 100644
index 00000000000..2dac58b6911
--- /dev/null
+++ b/chromium/cc/base/switches.cc
@@ -0,0 +1,150 @@
+// Copyright (c) 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 "cc/base/switches.h"
+
+#include "base/command_line.h"
+
+namespace cc {
+namespace switches {
+
+// On platforms where checkerboards are used, prefer background colors instead
+// of checkerboards.
+const char kBackgroundColorInsteadOfCheckerboard[] =
+ "background-color-instead-of-checkerboard";
+
+const char kDisableThreadedAnimation[] = "disable-threaded-animation";
+
+// Disables layer-edge anti-aliasing in the compositor.
+const char kDisableCompositedAntialiasing[] =
+ "disable-composited-antialiasing";
+
+// Paint content on the main thread instead of the compositor thread.
+// Overrides the kEnableImplSidePainting flag.
+const char kDisableImplSidePainting[] = "disable-impl-side-painting";
+
+// Paint content on the compositor thread instead of the main thread.
+const char kEnableImplSidePainting[] = "enable-impl-side-painting";
+
+const char kEnableTopControlsPositionCalculation[] =
+ "enable-top-controls-position-calculation";
+
+// For any layers that can get drawn directly to screen, draw them with the Skia
+// GPU backend. Only valid with gl rendering + threaded compositing + impl-side
+// painting.
+const char kForceDirectLayerDrawing[] = "force-direct-layer-drawing";
+
+// The height of the movable top controls.
+const char kTopControlsHeight[] = "top-controls-height";
+
+// Percentage of the top controls need to be hidden before they will auto hide.
+const char kTopControlsHideThreshold[] = "top-controls-hide-threshold";
+
+// Percentage of the top controls need to be shown before they will auto show.
+const char kTopControlsShowThreshold[] = "top-controls-show-threshold";
+
+// Number of worker threads used to rasterize content.
+const char kNumRasterThreads[] = "num-raster-threads";
+
+// Show metrics about overdraw in about:tracing recordings, such as the number
+// of pixels culled, and the number of pixels drawn, for each frame.
+const char kTraceOverdraw[] = "trace-overdraw";
+
+// Re-rasters everything multiple times to simulate a much slower machine.
+// Give a scale factor to cause raster to take that many times longer to
+// complete, such as --slow-down-raster-scale-factor=25.
+const char kSlowDownRasterScaleFactor[] = "slow-down-raster-scale-factor";
+
+// The scale factor for low resolution tile contents.
+const char kLowResolutionContentsScaleFactor[] =
+ "low-resolution-contents-scale-factor";
+
+// Max tiles allowed for each tilings interest area.
+const char kMaxTilesForInterestArea[] = "max-tiles-for-interest-area";
+
+// The amount of unused resource memory compositor is allowed to keep around.
+const char kMaxUnusedResourceMemoryUsagePercentage[] =
+ "max-unused-resource-memory-usage-percentage";
+
+// Causes the compositor to render to textures which are then sent to the parent
+// through the texture mailbox mechanism.
+// Requires --enable-compositor-frame-message.
+const char kCompositeToMailbox[] = "composite-to-mailbox";
+
+// Check that property changes during paint do not occur.
+const char kStrictLayerPropertyChangeChecking[] =
+ "strict-layer-property-change-checking";
+
+// Virtual viewport for fixed-position elements, scrollbars during pinch.
+const char kEnablePinchVirtualViewport[] = "enable-pinch-virtual-viewport";
+
+const char kEnablePartialSwap[] = "enable-partial-swap";
+// Disable partial swap which is needed for some OpenGL drivers / emulators.
+const char kUIDisablePartialSwap[] = "ui-disable-partial-swap";
+
+const char kEnablePerTilePainting[] = "enable-per-tile-painting";
+const char kUIEnablePerTilePainting[] = "ui-enable-per-tile-painting";
+
+// Renders a border around compositor layers to help debug and study
+// layer compositing.
+const char kShowCompositedLayerBorders[] = "show-composited-layer-borders";
+const char kUIShowCompositedLayerBorders[] = "ui-show-layer-borders";
+
+// Draws a FPS indicator
+const char kShowFPSCounter[] = "show-fps-counter";
+const char kUIShowFPSCounter[] = "ui-show-fps-counter";
+
+// Show rects in the HUD around layers whose properties have changed.
+const char kShowPropertyChangedRects[] = "show-property-changed-rects";
+const char kUIShowPropertyChangedRects[] = "ui-show-property-changed-rects";
+
+// Show rects in the HUD around damage as it is recorded into each render
+// surface.
+const char kShowSurfaceDamageRects[] = "show-surface-damage-rects";
+const char kUIShowSurfaceDamageRects[] = "ui-show-surface-damage-rects";
+
+// Show rects in the HUD around the screen-space transformed bounds of every
+// layer.
+const char kShowScreenSpaceRects[] = "show-screenspace-rects";
+const char kUIShowScreenSpaceRects[] = "ui-show-screenspace-rects";
+
+// Show rects in the HUD around the screen-space transformed bounds of every
+// layer's replica, when they have one.
+const char kShowReplicaScreenSpaceRects[] = "show-replica-screenspace-rects";
+const char kUIShowReplicaScreenSpaceRects[] =
+ "ui-show-replica-screenspace-rects";
+
+// Show rects in the HUD wherever something is known to be drawn opaque and is
+// considered occluding the pixels behind it.
+const char kShowOccludingRects[] = "show-occluding-rects";
+const char kUIShowOccludingRects[] = "ui-show-occluding-rects";
+
+// Show rects in the HUD wherever something is not known to be drawn opaque and
+// is not considered to be occluding the pixels behind it.
+const char kShowNonOccludingRects[] = "show-nonoccluding-rects";
+const char kUIShowNonOccludingRects[] = "ui-show-nonoccluding-rects";
+
+// Enable the codepath that uses images within TileManager.
+const char kUseMapImage[] = "use-map-image";
+
+// Prevents the layer tree unit tests from timing out.
+const char kCCLayerTreeTestNoTimeout[] = "cc-layer-tree-test-no-timeout";
+
+bool IsImplSidePaintingEnabled() {
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+
+ if (command_line.HasSwitch(cc::switches::kDisableImplSidePainting))
+ return false;
+ else if (command_line.HasSwitch(cc::switches::kEnableImplSidePainting))
+ return true;
+
+#if defined(OS_ANDROID)
+ return true;
+#else
+ return false;
+#endif
+}
+
+} // namespace switches
+} // namespace cc
diff --git a/chromium/cc/base/switches.h b/chromium/cc/base/switches.h
new file mode 100644
index 00000000000..f87eb53ee16
--- /dev/null
+++ b/chromium/cc/base/switches.h
@@ -0,0 +1,73 @@
+// Copyright (c) 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.
+
+// Defines all the "cc" command-line switches.
+
+#ifndef CC_BASE_SWITCHES_H_
+#define CC_BASE_SWITCHES_H_
+
+#include "cc/base/cc_export.h"
+
+// Since cc is used from the render process, anything that goes here also needs
+// to be added to render_process_host_impl.cc.
+
+namespace cc {
+namespace switches {
+
+// Switches for the renderer compositor only.
+CC_EXPORT extern const char kBackgroundColorInsteadOfCheckerboard[];
+CC_EXPORT extern const char kDisableImplSidePainting[];
+CC_EXPORT extern const char kDisableThreadedAnimation[];
+CC_EXPORT extern const char kDisableCompositedAntialiasing[];
+CC_EXPORT extern const char kEnableImplSidePainting[];
+CC_EXPORT extern const char kEnableTopControlsPositionCalculation[];
+CC_EXPORT extern const char kForceDirectLayerDrawing[];
+CC_EXPORT extern const char kJankInsteadOfCheckerboard[];
+CC_EXPORT extern const char kNumRasterThreads[];
+CC_EXPORT extern const char kTopControlsHeight[];
+CC_EXPORT extern const char kTopControlsHideThreshold[];
+CC_EXPORT extern const char kTraceOverdraw[];
+CC_EXPORT extern const char kTopControlsShowThreshold[];
+CC_EXPORT extern const char kSlowDownRasterScaleFactor[];
+CC_EXPORT extern const char kLowResolutionContentsScaleFactor[];
+CC_EXPORT extern const char kCompositeToMailbox[];
+CC_EXPORT extern const char kMaxTilesForInterestArea[];
+CC_EXPORT extern const char kMaxUnusedResourceMemoryUsagePercentage[];
+CC_EXPORT extern const char kEnablePinchVirtualViewport[];
+CC_EXPORT extern const char kEnablePartialSwap[];
+CC_EXPORT extern const char kStrictLayerPropertyChangeChecking[];
+CC_EXPORT extern const char kUseMapImage[];
+
+// Switches for both the renderer and ui compositors.
+CC_EXPORT extern const char kUIDisablePartialSwap[];
+CC_EXPORT extern const char kEnablePerTilePainting[];
+CC_EXPORT extern const char kUIEnablePerTilePainting[];
+
+// Debug visualizations.
+CC_EXPORT extern const char kShowCompositedLayerBorders[];
+CC_EXPORT extern const char kUIShowCompositedLayerBorders[];
+CC_EXPORT extern const char kShowFPSCounter[];
+CC_EXPORT extern const char kUIShowFPSCounter[];
+CC_EXPORT extern const char kShowPropertyChangedRects[];
+CC_EXPORT extern const char kUIShowPropertyChangedRects[];
+CC_EXPORT extern const char kShowSurfaceDamageRects[];
+CC_EXPORT extern const char kUIShowSurfaceDamageRects[];
+CC_EXPORT extern const char kShowScreenSpaceRects[];
+CC_EXPORT extern const char kUIShowScreenSpaceRects[];
+CC_EXPORT extern const char kShowReplicaScreenSpaceRects[];
+CC_EXPORT extern const char kUIShowReplicaScreenSpaceRects[];
+CC_EXPORT extern const char kShowOccludingRects[];
+CC_EXPORT extern const char kUIShowOccludingRects[];
+CC_EXPORT extern const char kShowNonOccludingRects[];
+CC_EXPORT extern const char kUIShowNonOccludingRects[];
+
+// Unit test related.
+CC_EXPORT extern const char kCCLayerTreeTestNoTimeout[];
+
+CC_EXPORT bool IsImplSidePaintingEnabled();
+
+} // namespace switches
+} // namespace cc
+
+#endif // CC_BASE_SWITCHES_H_
diff --git a/chromium/cc/base/tiling_data.cc b/chromium/cc/base/tiling_data.cc
new file mode 100644
index 00000000000..1045d89addd
--- /dev/null
+++ b/chromium/cc/base/tiling_data.cc
@@ -0,0 +1,408 @@
+// Copyright 2010 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 "cc/base/tiling_data.h"
+
+#include <algorithm>
+
+#include "ui/gfx/rect.h"
+#include "ui/gfx/vector2d.h"
+
+namespace cc {
+
+static int ComputeNumTiles(int max_texture_size,
+ int total_size,
+ int border_texels) {
+ if (max_texture_size - 2 * border_texels <= 0)
+ return total_size > 0 && max_texture_size >= total_size ? 1 : 0;
+
+ int num_tiles = std::max(1,
+ 1 + (total_size - 1 - 2 * border_texels) /
+ (max_texture_size - 2 * border_texels));
+ return total_size > 0 ? num_tiles : 0;
+}
+
+TilingData::TilingData()
+ : border_texels_(0) {
+ RecomputeNumTiles();
+}
+
+TilingData::TilingData(
+ gfx::Size max_texture_size,
+ gfx::Size total_size,
+ bool has_border_texels)
+ : max_texture_size_(max_texture_size),
+ total_size_(total_size),
+ border_texels_(has_border_texels ? 1 : 0) {
+ RecomputeNumTiles();
+}
+
+TilingData::TilingData(
+ gfx::Size max_texture_size,
+ gfx::Size total_size,
+ int border_texels)
+ : max_texture_size_(max_texture_size),
+ total_size_(total_size),
+ border_texels_(border_texels) {
+ RecomputeNumTiles();
+}
+
+void TilingData::SetTotalSize(gfx::Size total_size) {
+ total_size_ = total_size;
+ RecomputeNumTiles();
+}
+
+void TilingData::SetMaxTextureSize(gfx::Size max_texture_size) {
+ max_texture_size_ = max_texture_size;
+ RecomputeNumTiles();
+}
+
+void TilingData::SetHasBorderTexels(bool has_border_texels) {
+ border_texels_ = has_border_texels ? 1 : 0;
+ RecomputeNumTiles();
+}
+
+void TilingData::SetBorderTexels(int border_texels) {
+ border_texels_ = border_texels;
+ RecomputeNumTiles();
+}
+
+int TilingData::TileXIndexFromSrcCoord(int src_position) const {
+ if (num_tiles_x_ <= 1)
+ return 0;
+
+ DCHECK_GT(max_texture_size_.width() - 2 * border_texels_, 0);
+ int x = (src_position - border_texels_) /
+ (max_texture_size_.width() - 2 * border_texels_);
+ return std::min(std::max(x, 0), num_tiles_x_ - 1);
+}
+
+int TilingData::TileYIndexFromSrcCoord(int src_position) const {
+ if (num_tiles_y_ <= 1)
+ return 0;
+
+ DCHECK_GT(max_texture_size_.height() - 2 * border_texels_, 0);
+ int y = (src_position - border_texels_) /
+ (max_texture_size_.height() - 2 * border_texels_);
+ return std::min(std::max(y, 0), num_tiles_y_ - 1);
+}
+
+int TilingData::FirstBorderTileXIndexFromSrcCoord(int src_position) const {
+ if (num_tiles_x_ <= 1)
+ return 0;
+
+ DCHECK_GT(max_texture_size_.width() - 2 * border_texels_, 0);
+ int inner_tile_size = max_texture_size_.width() - 2 * border_texels_;
+ int x = (src_position - 2 * border_texels_) / inner_tile_size;
+ return std::min(std::max(x, 0), num_tiles_x_ - 1);
+}
+
+int TilingData::FirstBorderTileYIndexFromSrcCoord(int src_position) const {
+ if (num_tiles_y_ <= 1)
+ return 0;
+
+ DCHECK_GT(max_texture_size_.height() - 2 * border_texels_, 0);
+ int inner_tile_size = max_texture_size_.height() - 2 * border_texels_;
+ int y = (src_position - 2 * border_texels_) / inner_tile_size;
+ return std::min(std::max(y, 0), num_tiles_y_ - 1);
+}
+
+int TilingData::LastBorderTileXIndexFromSrcCoord(int src_position) const {
+ if (num_tiles_x_ <= 1)
+ return 0;
+
+ DCHECK_GT(max_texture_size_.width() - 2 * border_texels_, 0);
+ int inner_tile_size = max_texture_size_.width() - 2 * border_texels_;
+ int x = src_position / inner_tile_size;
+ return std::min(std::max(x, 0), num_tiles_x_ - 1);
+}
+
+int TilingData::LastBorderTileYIndexFromSrcCoord(int src_position) const {
+ if (num_tiles_y_ <= 1)
+ return 0;
+
+ DCHECK_GT(max_texture_size_.height() - 2 * border_texels_, 0);
+ int inner_tile_size = max_texture_size_.height() - 2 * border_texels_;
+ int y = src_position / inner_tile_size;
+ return std::min(std::max(y, 0), num_tiles_y_ - 1);
+}
+
+gfx::Rect TilingData::TileBounds(int i, int j) const {
+ AssertTile(i, j);
+ int max_texture_size_x = max_texture_size_.width() - 2 * border_texels_;
+ int max_texture_size_y = max_texture_size_.height() - 2 * border_texels_;
+ int total_size_x = total_size_.width();
+ int total_size_y = total_size_.height();
+
+ int lo_x = max_texture_size_x * i;
+ if (i != 0)
+ lo_x += border_texels_;
+
+ int lo_y = max_texture_size_y * j;
+ if (j != 0)
+ lo_y += border_texels_;
+
+ int hi_x = max_texture_size_x * (i + 1) + border_texels_;
+ if (i + 1 == num_tiles_x_)
+ hi_x += border_texels_;
+
+ int hi_y = max_texture_size_y * (j + 1) + border_texels_;
+ if (j + 1 == num_tiles_y_)
+ hi_y += border_texels_;
+
+ hi_x = std::min(hi_x, total_size_x);
+ hi_y = std::min(hi_y, total_size_y);
+
+ int x = lo_x;
+ int y = lo_y;
+ int width = hi_x - lo_x;
+ int height = hi_y - lo_y;
+ DCHECK_GE(x, 0);
+ DCHECK_GE(y, 0);
+ DCHECK_GE(width, 0);
+ DCHECK_GE(height, 0);
+ DCHECK_LE(x, total_size_.width());
+ DCHECK_LE(y, total_size_.height());
+ return gfx::Rect(x, y, width, height);
+}
+
+gfx::Rect TilingData::TileBoundsWithBorder(int i, int j) const {
+ AssertTile(i, j);
+ int max_texture_size_x = max_texture_size_.width() - 2 * border_texels_;
+ int max_texture_size_y = max_texture_size_.height() - 2 * border_texels_;
+ int total_size_x = total_size_.width();
+ int total_size_y = total_size_.height();
+
+ int lo_x = max_texture_size_x * i;
+ int lo_y = max_texture_size_y * j;
+
+ int hi_x = lo_x + max_texture_size_x + 2 * border_texels_;
+ int hi_y = lo_y + max_texture_size_y + 2 * border_texels_;
+
+ hi_x = std::min(hi_x, total_size_x);
+ hi_y = std::min(hi_y, total_size_y);
+
+ int x = lo_x;
+ int y = lo_y;
+ int width = hi_x - lo_x;
+ int height = hi_y - lo_y;
+ DCHECK_GE(x, 0);
+ DCHECK_GE(y, 0);
+ DCHECK_GE(width, 0);
+ DCHECK_GE(height, 0);
+ DCHECK_LE(x, total_size_.width());
+ DCHECK_LE(y, total_size_.height());
+ return gfx::Rect(x, y, width, height);
+}
+
+int TilingData::TilePositionX(int x_index) const {
+ DCHECK_GE(x_index, 0);
+ DCHECK_LT(x_index, num_tiles_x_);
+
+ int pos = (max_texture_size_.width() - 2 * border_texels_) * x_index;
+ if (x_index != 0)
+ pos += border_texels_;
+
+ return pos;
+}
+
+int TilingData::TilePositionY(int y_index) const {
+ DCHECK_GE(y_index, 0);
+ DCHECK_LT(y_index, num_tiles_y_);
+
+ int pos = (max_texture_size_.height() - 2 * border_texels_) * y_index;
+ if (y_index != 0)
+ pos += border_texels_;
+
+ return pos;
+}
+
+int TilingData::TileSizeX(int x_index) const {
+ DCHECK_GE(x_index, 0);
+ DCHECK_LT(x_index, num_tiles_x_);
+
+ if (!x_index && num_tiles_x_ == 1)
+ return total_size_.width();
+ if (!x_index && num_tiles_x_ > 1)
+ return max_texture_size_.width() - border_texels_;
+ if (x_index < num_tiles_x_ - 1)
+ return max_texture_size_.width() - 2 * border_texels_;
+ if (x_index == num_tiles_x_ - 1)
+ return total_size_.width() - TilePositionX(x_index);
+
+ NOTREACHED();
+ return 0;
+}
+
+int TilingData::TileSizeY(int y_index) const {
+ DCHECK_GE(y_index, 0);
+ DCHECK_LT(y_index, num_tiles_y_);
+
+ if (!y_index && num_tiles_y_ == 1)
+ return total_size_.height();
+ if (!y_index && num_tiles_y_ > 1)
+ return max_texture_size_.height() - border_texels_;
+ if (y_index < num_tiles_y_ - 1)
+ return max_texture_size_.height() - 2 * border_texels_;
+ if (y_index == num_tiles_y_ - 1)
+ return total_size_.height() - TilePositionY(y_index);
+
+ NOTREACHED();
+ return 0;
+}
+
+gfx::Vector2d TilingData::TextureOffset(int x_index, int y_index) const {
+ int left = (!x_index || num_tiles_x_ == 1) ? 0 : border_texels_;
+ int top = (!y_index || num_tiles_y_ == 1) ? 0 : border_texels_;
+
+ return gfx::Vector2d(left, top);
+}
+
+void TilingData::RecomputeNumTiles() {
+ num_tiles_x_ = ComputeNumTiles(
+ max_texture_size_.width(), total_size_.width(), border_texels_);
+ num_tiles_y_ = ComputeNumTiles(
+ max_texture_size_.height(), total_size_.height(), border_texels_);
+}
+
+TilingData::BaseIterator::BaseIterator(const TilingData* tiling_data)
+ : tiling_data_(tiling_data),
+ index_x_(-1),
+ index_y_(-1) {
+}
+
+TilingData::Iterator::Iterator(const TilingData* tiling_data, gfx::Rect rect)
+ : BaseIterator(tiling_data),
+ left_(-1),
+ right_(-1),
+ bottom_(-1) {
+ if (tiling_data_->num_tiles_x() <= 0 || tiling_data_->num_tiles_y() <= 0) {
+ done();
+ return;
+ }
+
+ rect.Intersect(gfx::Rect(tiling_data_->total_size()));
+ index_x_ = tiling_data_->FirstBorderTileXIndexFromSrcCoord(rect.x());
+ index_y_ = tiling_data_->FirstBorderTileYIndexFromSrcCoord(rect.y());
+ left_ = index_x_;
+ right_ = tiling_data_->LastBorderTileXIndexFromSrcCoord(rect.right() - 1);
+ bottom_ = tiling_data_->LastBorderTileYIndexFromSrcCoord(rect.bottom() - 1);
+
+ // Index functions always return valid indices, so explicitly check
+ // for non-intersecting rects.
+ gfx::Rect new_rect = tiling_data_->TileBoundsWithBorder(index_x_, index_y_);
+ if (!new_rect.Intersects(rect))
+ done();
+}
+
+TilingData::Iterator& TilingData::Iterator::operator++() {
+ if (!*this)
+ return *this;
+
+ index_x_++;
+ if (index_x_ > right_) {
+ index_x_ = left_;
+ index_y_++;
+ if (index_y_ > bottom_)
+ done();
+ }
+
+ return *this;
+}
+
+TilingData::DifferenceIterator::DifferenceIterator(
+ const TilingData* tiling_data,
+ gfx::Rect consider,
+ gfx::Rect ignore)
+ : BaseIterator(tiling_data),
+ consider_left_(-1),
+ consider_top_(-1),
+ consider_right_(-1),
+ consider_bottom_(-1),
+ ignore_left_(-1),
+ ignore_top_(-1),
+ ignore_right_(-1),
+ ignore_bottom_(-1) {
+ if (tiling_data_->num_tiles_x() <= 0 || tiling_data_->num_tiles_y() <= 0) {
+ done();
+ return;
+ }
+
+ gfx::Rect bounds(tiling_data_->total_size());
+ consider.Intersect(bounds);
+ ignore.Intersect(bounds);
+ if (consider.IsEmpty()) {
+ done();
+ return;
+ }
+
+ consider_left_ =
+ tiling_data_->FirstBorderTileXIndexFromSrcCoord(consider.x());
+ consider_top_ =
+ tiling_data_->FirstBorderTileYIndexFromSrcCoord(consider.y());
+ consider_right_ =
+ tiling_data_->LastBorderTileXIndexFromSrcCoord(consider.right() - 1);
+ consider_bottom_ =
+ tiling_data_->LastBorderTileYIndexFromSrcCoord(consider.bottom() - 1);
+
+ if (!ignore.IsEmpty()) {
+ ignore_left_ =
+ tiling_data_->FirstBorderTileXIndexFromSrcCoord(ignore.x());
+ ignore_top_ =
+ tiling_data_->FirstBorderTileYIndexFromSrcCoord(ignore.y());
+ ignore_right_ =
+ tiling_data_->LastBorderTileXIndexFromSrcCoord(ignore.right() - 1);
+ ignore_bottom_ =
+ tiling_data_->LastBorderTileYIndexFromSrcCoord(ignore.bottom() - 1);
+
+ // Clamp ignore indices to consider indices.
+ ignore_left_ = std::max(ignore_left_, consider_left_);
+ ignore_top_ = std::max(ignore_top_, consider_top_);
+ ignore_right_ = std::min(ignore_right_, consider_right_);
+ ignore_bottom_ = std::min(ignore_bottom_, consider_bottom_);
+ }
+
+ if (ignore_left_ == consider_left_ && ignore_right_ == consider_right_ &&
+ ignore_top_ == consider_top_ && ignore_bottom_ == consider_bottom_) {
+ done();
+ return;
+ }
+
+ index_x_ = consider_left_;
+ index_y_ = consider_top_;
+
+ if (in_ignore_rect())
+ ++(*this);
+}
+
+TilingData::DifferenceIterator& TilingData::DifferenceIterator::operator++() {
+ if (!*this)
+ return *this;
+
+ index_x_++;
+ if (in_ignore_rect())
+ index_x_ = ignore_right_ + 1;
+
+ if (index_x_ > consider_right_) {
+ index_x_ = consider_left_;
+ index_y_++;
+
+ if (in_ignore_rect()) {
+ index_x_ = ignore_right_ + 1;
+ // If the ignore rect spans the whole consider rect horizontally, then
+ // ignore_right + 1 will be out of bounds.
+ if (in_ignore_rect() || index_x_ > consider_right_) {
+ index_y_ = ignore_bottom_ + 1;
+ index_x_ = consider_left_;
+ }
+ }
+
+ if (index_y_ > consider_bottom_)
+ done();
+ }
+
+ return *this;
+}
+
+} // namespace cc
diff --git a/chromium/cc/base/tiling_data.h b/chromium/cc/base/tiling_data.h
new file mode 100644
index 00000000000..3bf9809579f
--- /dev/null
+++ b/chromium/cc/base/tiling_data.h
@@ -0,0 +1,148 @@
+// Copyright 2010 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.
+
+#ifndef CC_BASE_TILING_DATA_H_
+#define CC_BASE_TILING_DATA_H_
+
+#include <utility>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "cc/base/cc_export.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size.h"
+
+namespace gfx {
+class Vector2d;
+}
+
+namespace cc {
+
+class CC_EXPORT TilingData {
+ public:
+ TilingData();
+ TilingData(
+ gfx::Size max_texture_size,
+ gfx::Size total_size,
+ bool has_border_texels);
+ TilingData(
+ gfx::Size max_texture_size,
+ gfx::Size total_size,
+ int border_texels);
+
+ gfx::Size total_size() const { return total_size_; }
+ void SetTotalSize(const gfx::Size total_size);
+
+ gfx::Size max_texture_size() const { return max_texture_size_; }
+ void SetMaxTextureSize(gfx::Size max_texture_size);
+
+ int border_texels() const { return border_texels_; }
+ void SetHasBorderTexels(bool has_border_texels);
+ void SetBorderTexels(int border_texels);
+
+ bool has_empty_bounds() const { return !num_tiles_x_ || !num_tiles_y_; }
+ int num_tiles_x() const { return num_tiles_x_; }
+ int num_tiles_y() const { return num_tiles_y_; }
+ // Return the tile index whose non-border texels include src_position.
+ int TileXIndexFromSrcCoord(int src_position) const;
+ int TileYIndexFromSrcCoord(int src_position) const;
+ // Return the lowest tile index whose border texels include src_position.
+ int FirstBorderTileXIndexFromSrcCoord(int src_position) const;
+ int FirstBorderTileYIndexFromSrcCoord(int src_position) const;
+ // Return the highest tile index whose border texels include src_position.
+ int LastBorderTileXIndexFromSrcCoord(int src_position) const;
+ int LastBorderTileYIndexFromSrcCoord(int src_position) const;
+
+ gfx::Rect TileBounds(int i, int j) const;
+ gfx::Rect TileBoundsWithBorder(int i, int j) const;
+ int TilePositionX(int x_index) const;
+ int TilePositionY(int y_index) const;
+ int TileSizeX(int x_index) const;
+ int TileSizeY(int y_index) const;
+
+ // Difference between TileBound's and TileBoundWithBorder's origin().
+ gfx::Vector2d TextureOffset(int x_index, int y_index) const;
+
+ class CC_EXPORT BaseIterator {
+ public:
+ operator bool() const { return index_x_ != -1 && index_y_ != -1; }
+
+ int index_x() const { return index_x_; }
+ int index_y() const { return index_y_; }
+ std::pair<int, int> index() const {
+ return std::make_pair(index_x_, index_y_);
+ }
+
+ protected:
+ explicit BaseIterator(const TilingData* tiling_data);
+ void done() {
+ index_x_ = -1;
+ index_y_ = -1;
+ }
+
+ const TilingData* tiling_data_;
+ int index_x_;
+ int index_y_;
+ };
+
+ // Iterate through all indices whose bounds + border intersect with |rect|.
+ class CC_EXPORT Iterator : public BaseIterator {
+ public:
+ Iterator(const TilingData* tiling_data, gfx::Rect rect);
+ Iterator& operator++();
+
+ private:
+ int left_;
+ int right_;
+ int bottom_;
+ };
+
+ // Iterate through all indices whose bounds + border intersect with
+ // |consider| but which also do not intersect with |ignore|.
+ class CC_EXPORT DifferenceIterator : public BaseIterator {
+ public:
+ DifferenceIterator(
+ const TilingData* tiling_data,
+ gfx::Rect consider,
+ gfx::Rect ignore);
+ DifferenceIterator& operator++();
+
+ private:
+ bool in_ignore_rect() const {
+ return index_x_ >= ignore_left_ && index_x_ <= ignore_right_ &&
+ index_y_ >= ignore_top_ && index_y_ <= ignore_bottom_;
+ }
+
+ int consider_left_;
+ int consider_top_;
+ int consider_right_;
+ int consider_bottom_;
+ int ignore_left_;
+ int ignore_top_;
+ int ignore_right_;
+ int ignore_bottom_;
+ };
+
+ private:
+ void AssertTile(int i, int j) const {
+ DCHECK_GE(i, 0);
+ DCHECK_LT(i, num_tiles_x_);
+ DCHECK_GE(j, 0);
+ DCHECK_LT(j, num_tiles_y_);
+ }
+
+ void RecomputeNumTiles();
+
+ gfx::Size max_texture_size_;
+ gfx::Size total_size_;
+ int border_texels_;
+
+ // These are computed values.
+ int num_tiles_x_;
+ int num_tiles_y_;
+};
+
+} // namespace cc
+
+#endif // CC_BASE_TILING_DATA_H_
diff --git a/chromium/cc/base/tiling_data_unittest.cc b/chromium/cc/base/tiling_data_unittest.cc
new file mode 100644
index 00000000000..db2d6f215a9
--- /dev/null
+++ b/chromium/cc/base/tiling_data_unittest.cc
@@ -0,0 +1,1171 @@
+// Copyright 2013 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 "cc/base/tiling_data.h"
+
+#include <vector>
+
+#include "cc/test/geometry_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+int NumTiles(
+ gfx::Size max_texture_size,
+ gfx::Size total_size,
+ bool has_border_texels) {
+ TilingData tiling(max_texture_size, total_size, has_border_texels);
+ int num_tiles = tiling.num_tiles_x() * tiling.num_tiles_y();
+
+ // Assert no overflow.
+ EXPECT_GE(num_tiles, 0);
+ if (num_tiles > 0)
+ EXPECT_EQ(num_tiles / tiling.num_tiles_x(), tiling.num_tiles_y());
+
+ return num_tiles;
+}
+
+int XIndex(
+ gfx::Size max_texture_size,
+ gfx::Size total_size,
+ bool has_border_texels,
+ int x_coord) {
+ TilingData tiling(max_texture_size, total_size, has_border_texels);
+ return tiling.TileXIndexFromSrcCoord(x_coord);
+}
+
+int YIndex(
+ gfx::Size max_texture_size,
+ gfx::Size total_size,
+ bool has_border_texels,
+ int y_coord) {
+ TilingData tiling(max_texture_size, total_size, has_border_texels);
+ return tiling.TileYIndexFromSrcCoord(y_coord);
+}
+
+int MinBorderXIndex(
+ gfx::Size max_texture_size,
+ gfx::Size total_size,
+ bool has_border_texels,
+ int x_coord) {
+ TilingData tiling(max_texture_size, total_size, has_border_texels);
+ return tiling.FirstBorderTileXIndexFromSrcCoord(x_coord);
+}
+
+int MinBorderYIndex(
+ gfx::Size max_texture_size,
+ gfx::Size total_size,
+ bool has_border_texels,
+ int y_coord) {
+ TilingData tiling(max_texture_size, total_size, has_border_texels);
+ return tiling.FirstBorderTileYIndexFromSrcCoord(y_coord);
+}
+
+int MaxBorderXIndex(
+ gfx::Size max_texture_size,
+ gfx::Size total_size,
+ bool has_border_texels,
+ int x_coord) {
+ TilingData tiling(max_texture_size, total_size, has_border_texels);
+ return tiling.LastBorderTileXIndexFromSrcCoord(x_coord);
+}
+
+int MaxBorderYIndex(
+ gfx::Size max_texture_size,
+ gfx::Size total_size,
+ bool has_border_texels,
+ int y_coord) {
+ TilingData tiling(max_texture_size, total_size, has_border_texels);
+ return tiling.LastBorderTileYIndexFromSrcCoord(y_coord);
+}
+
+int PosX(
+ gfx::Size max_texture_size,
+ gfx::Size total_size,
+ bool has_border_texels,
+ int x_index) {
+ TilingData tiling(max_texture_size, total_size, has_border_texels);
+ return tiling.TilePositionX(x_index);
+}
+
+int PosY(
+ gfx::Size max_texture_size,
+ gfx::Size total_size,
+ bool has_border_texels,
+ int y_index) {
+ TilingData tiling(max_texture_size, total_size, has_border_texels);
+ return tiling.TilePositionY(y_index);
+}
+
+int SizeX(
+ gfx::Size max_texture_size,
+ gfx::Size total_size,
+ bool has_border_texels,
+ int x_index) {
+ TilingData tiling(max_texture_size, total_size, has_border_texels);
+ return tiling.TileSizeX(x_index);
+}
+
+int SizeY(
+ gfx::Size max_texture_size,
+ gfx::Size total_size,
+ bool has_border_texels,
+ int y_index) {
+ TilingData tiling(max_texture_size, total_size, has_border_texels);
+ return tiling.TileSizeY(y_index);
+}
+
+TEST(TilingDataTest, NumTiles_NoTiling) {
+ EXPECT_EQ(1, NumTiles(gfx::Size(16, 16), gfx::Size(16, 16), false));
+ EXPECT_EQ(1, NumTiles(gfx::Size(16, 16), gfx::Size(15, 15), true));
+ EXPECT_EQ(1, NumTiles(gfx::Size(16, 16), gfx::Size(16, 16), true));
+ EXPECT_EQ(1, NumTiles(gfx::Size(16, 16), gfx::Size(1, 16), false));
+ EXPECT_EQ(1, NumTiles(gfx::Size(15, 15), gfx::Size(15, 15), true));
+ EXPECT_EQ(1, NumTiles(gfx::Size(32, 16), gfx::Size(32, 16), false));
+ EXPECT_EQ(1, NumTiles(gfx::Size(32, 16), gfx::Size(32, 16), true));
+}
+
+TEST(TilingDataTest, NumTiles_TilingNoBorders) {
+ EXPECT_EQ(0, NumTiles(gfx::Size(0, 0), gfx::Size(0, 0), false));
+ EXPECT_EQ(0, NumTiles(gfx::Size(0, 0), gfx::Size(4, 0), false));
+ EXPECT_EQ(0, NumTiles(gfx::Size(0, 0), gfx::Size(0, 4), false));
+ EXPECT_EQ(0, NumTiles(gfx::Size(4, 4), gfx::Size(4, 0), false));
+ EXPECT_EQ(0, NumTiles(gfx::Size(4, 4), gfx::Size(0, 4), false));
+ EXPECT_EQ(0, NumTiles(gfx::Size(0, 0), gfx::Size(1, 1), false));
+
+ EXPECT_EQ(1, NumTiles(gfx::Size(1, 1), gfx::Size(1, 1), false));
+ EXPECT_EQ(2, NumTiles(gfx::Size(1, 1), gfx::Size(1, 2), false));
+ EXPECT_EQ(2, NumTiles(gfx::Size(1, 1), gfx::Size(2, 1), false));
+ EXPECT_EQ(1, NumTiles(gfx::Size(2, 2), gfx::Size(1, 1), false));
+ EXPECT_EQ(1, NumTiles(gfx::Size(2, 2), gfx::Size(1, 2), false));
+ EXPECT_EQ(1, NumTiles(gfx::Size(2, 2), gfx::Size(2, 1), false));
+ EXPECT_EQ(1, NumTiles(gfx::Size(2, 2), gfx::Size(2, 2), false));
+ EXPECT_EQ(1, NumTiles(gfx::Size(3, 3), gfx::Size(3, 3), false));
+
+ EXPECT_EQ(1, NumTiles(gfx::Size(4, 4), gfx::Size(1, 4), false));
+ EXPECT_EQ(1, NumTiles(gfx::Size(4, 4), gfx::Size(2, 4), false));
+ EXPECT_EQ(1, NumTiles(gfx::Size(4, 4), gfx::Size(3, 4), false));
+ EXPECT_EQ(1, NumTiles(gfx::Size(4, 4), gfx::Size(4, 4), false));
+ EXPECT_EQ(2, NumTiles(gfx::Size(4, 4), gfx::Size(5, 4), false));
+ EXPECT_EQ(2, NumTiles(gfx::Size(4, 4), gfx::Size(6, 4), false));
+ EXPECT_EQ(2, NumTiles(gfx::Size(4, 4), gfx::Size(7, 4), false));
+ EXPECT_EQ(2, NumTiles(gfx::Size(4, 4), gfx::Size(8, 4), false));
+ EXPECT_EQ(3, NumTiles(gfx::Size(4, 4), gfx::Size(9, 4), false));
+ EXPECT_EQ(3, NumTiles(gfx::Size(4, 4), gfx::Size(10, 4), false));
+ EXPECT_EQ(3, NumTiles(gfx::Size(4, 4), gfx::Size(11, 4), false));
+
+ EXPECT_EQ(1, NumTiles(gfx::Size(5, 5), gfx::Size(1, 5), false));
+ EXPECT_EQ(1, NumTiles(gfx::Size(5, 5), gfx::Size(2, 5), false));
+ EXPECT_EQ(1, NumTiles(gfx::Size(5, 5), gfx::Size(3, 5), false));
+ EXPECT_EQ(1, NumTiles(gfx::Size(5, 5), gfx::Size(4, 5), false));
+ EXPECT_EQ(1, NumTiles(gfx::Size(5, 5), gfx::Size(5, 5), false));
+ EXPECT_EQ(2, NumTiles(gfx::Size(5, 5), gfx::Size(6, 5), false));
+ EXPECT_EQ(2, NumTiles(gfx::Size(5, 5), gfx::Size(7, 5), false));
+ EXPECT_EQ(2, NumTiles(gfx::Size(5, 5), gfx::Size(8, 5), false));
+ EXPECT_EQ(2, NumTiles(gfx::Size(5, 5), gfx::Size(9, 5), false));
+ EXPECT_EQ(2, NumTiles(gfx::Size(5, 5), gfx::Size(10, 5), false));
+ EXPECT_EQ(3, NumTiles(gfx::Size(5, 5), gfx::Size(11, 5), false));
+
+ EXPECT_EQ(1, NumTiles(gfx::Size(16, 16), gfx::Size(16, 16), false));
+ EXPECT_EQ(1, NumTiles(gfx::Size(17, 17), gfx::Size(16, 16), false));
+ EXPECT_EQ(4, NumTiles(gfx::Size(15, 15), gfx::Size(16, 16), false));
+ EXPECT_EQ(4, NumTiles(gfx::Size(8, 8), gfx::Size(16, 16), false));
+ EXPECT_EQ(6, NumTiles(gfx::Size(8, 8), gfx::Size(17, 16), false));
+
+ EXPECT_EQ(8, NumTiles(gfx::Size(5, 8), gfx::Size(17, 16), false));
+}
+
+TEST(TilingDataTest, NumTiles_TilingWithBorders) {
+ EXPECT_EQ(0, NumTiles(gfx::Size(0, 0), gfx::Size(0, 0), true));
+ EXPECT_EQ(0, NumTiles(gfx::Size(0, 0), gfx::Size(4, 0), true));
+ EXPECT_EQ(0, NumTiles(gfx::Size(0, 0), gfx::Size(0, 4), true));
+ EXPECT_EQ(0, NumTiles(gfx::Size(4, 4), gfx::Size(4, 0), true));
+ EXPECT_EQ(0, NumTiles(gfx::Size(4, 4), gfx::Size(0, 4), true));
+ EXPECT_EQ(0, NumTiles(gfx::Size(0, 0), gfx::Size(1, 1), true));
+
+ EXPECT_EQ(1, NumTiles(gfx::Size(1, 1), gfx::Size(1, 1), true));
+ EXPECT_EQ(0, NumTiles(gfx::Size(1, 1), gfx::Size(1, 2), true));
+ EXPECT_EQ(0, NumTiles(gfx::Size(1, 1), gfx::Size(2, 1), true));
+ EXPECT_EQ(1, NumTiles(gfx::Size(2, 2), gfx::Size(1, 1), true));
+ EXPECT_EQ(1, NumTiles(gfx::Size(2, 2), gfx::Size(1, 2), true));
+ EXPECT_EQ(1, NumTiles(gfx::Size(2, 2), gfx::Size(2, 1), true));
+ EXPECT_EQ(1, NumTiles(gfx::Size(2, 2), gfx::Size(2, 2), true));
+
+ EXPECT_EQ(1, NumTiles(gfx::Size(3, 3), gfx::Size(1, 3), true));
+ EXPECT_EQ(1, NumTiles(gfx::Size(3, 3), gfx::Size(2, 3), true));
+ EXPECT_EQ(1, NumTiles(gfx::Size(3, 3), gfx::Size(3, 3), true));
+ EXPECT_EQ(2, NumTiles(gfx::Size(3, 3), gfx::Size(4, 3), true));
+ EXPECT_EQ(3, NumTiles(gfx::Size(3, 3), gfx::Size(5, 3), true));
+ EXPECT_EQ(4, NumTiles(gfx::Size(3, 3), gfx::Size(6, 3), true));
+ EXPECT_EQ(5, NumTiles(gfx::Size(3, 3), gfx::Size(7, 3), true));
+
+ EXPECT_EQ(1, NumTiles(gfx::Size(4, 4), gfx::Size(1, 4), true));
+ EXPECT_EQ(1, NumTiles(gfx::Size(4, 4), gfx::Size(2, 4), true));
+ EXPECT_EQ(1, NumTiles(gfx::Size(4, 4), gfx::Size(3, 4), true));
+ EXPECT_EQ(1, NumTiles(gfx::Size(4, 4), gfx::Size(4, 4), true));
+ EXPECT_EQ(2, NumTiles(gfx::Size(4, 4), gfx::Size(5, 4), true));
+ EXPECT_EQ(2, NumTiles(gfx::Size(4, 4), gfx::Size(6, 4), true));
+ EXPECT_EQ(3, NumTiles(gfx::Size(4, 4), gfx::Size(7, 4), true));
+ EXPECT_EQ(3, NumTiles(gfx::Size(4, 4), gfx::Size(8, 4), true));
+ EXPECT_EQ(4, NumTiles(gfx::Size(4, 4), gfx::Size(9, 4), true));
+ EXPECT_EQ(4, NumTiles(gfx::Size(4, 4), gfx::Size(10, 4), true));
+ EXPECT_EQ(5, NumTiles(gfx::Size(4, 4), gfx::Size(11, 4), true));
+
+ EXPECT_EQ(1, NumTiles(gfx::Size(5, 5), gfx::Size(1, 5), true));
+ EXPECT_EQ(1, NumTiles(gfx::Size(5, 5), gfx::Size(2, 5), true));
+ EXPECT_EQ(1, NumTiles(gfx::Size(5, 5), gfx::Size(3, 5), true));
+ EXPECT_EQ(1, NumTiles(gfx::Size(5, 5), gfx::Size(4, 5), true));
+ EXPECT_EQ(1, NumTiles(gfx::Size(5, 5), gfx::Size(5, 5), true));
+ EXPECT_EQ(2, NumTiles(gfx::Size(5, 5), gfx::Size(6, 5), true));
+ EXPECT_EQ(2, NumTiles(gfx::Size(5, 5), gfx::Size(7, 5), true));
+ EXPECT_EQ(2, NumTiles(gfx::Size(5, 5), gfx::Size(8, 5), true));
+ EXPECT_EQ(3, NumTiles(gfx::Size(5, 5), gfx::Size(9, 5), true));
+ EXPECT_EQ(3, NumTiles(gfx::Size(5, 5), gfx::Size(10, 5), true));
+ EXPECT_EQ(3, NumTiles(gfx::Size(5, 5), gfx::Size(11, 5), true));
+
+ EXPECT_EQ(30, NumTiles(gfx::Size(8, 5), gfx::Size(16, 32), true));
+}
+
+TEST(TilingDataTest, TileXIndexFromSrcCoord) {
+ EXPECT_EQ(0, XIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 0));
+ EXPECT_EQ(0, XIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 1));
+ EXPECT_EQ(0, XIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 2));
+ EXPECT_EQ(1, XIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 3));
+ EXPECT_EQ(1, XIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 4));
+ EXPECT_EQ(1, XIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 5));
+ EXPECT_EQ(2, XIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 6));
+ EXPECT_EQ(2, XIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 7));
+ EXPECT_EQ(2, XIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 8));
+ EXPECT_EQ(3, XIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 9));
+ EXPECT_EQ(3, XIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 10));
+ EXPECT_EQ(3, XIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 11));
+
+ EXPECT_EQ(0, XIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 0));
+ EXPECT_EQ(0, XIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 1));
+ EXPECT_EQ(1, XIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 2));
+ EXPECT_EQ(2, XIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 3));
+ EXPECT_EQ(3, XIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 4));
+ EXPECT_EQ(4, XIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 5));
+ EXPECT_EQ(5, XIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 6));
+ EXPECT_EQ(6, XIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 7));
+ EXPECT_EQ(7, XIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 8));
+ EXPECT_EQ(7, XIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 9));
+ EXPECT_EQ(7, XIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 10));
+ EXPECT_EQ(7, XIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 11));
+
+ EXPECT_EQ(0, XIndex(gfx::Size(1, 1), gfx::Size(1, 1), false, 0));
+ EXPECT_EQ(0, XIndex(gfx::Size(2, 2), gfx::Size(2, 2), false, 0));
+ EXPECT_EQ(0, XIndex(gfx::Size(2, 2), gfx::Size(2, 2), false, 1));
+ EXPECT_EQ(0, XIndex(gfx::Size(3, 3), gfx::Size(3, 3), false, 0));
+ EXPECT_EQ(0, XIndex(gfx::Size(3, 3), gfx::Size(3, 3), false, 1));
+ EXPECT_EQ(0, XIndex(gfx::Size(3, 3), gfx::Size(3, 3), false, 2));
+
+ EXPECT_EQ(0, XIndex(gfx::Size(3, 3), gfx::Size(4, 3), false, 0));
+ EXPECT_EQ(0, XIndex(gfx::Size(3, 3), gfx::Size(4, 3), false, 1));
+ EXPECT_EQ(0, XIndex(gfx::Size(3, 3), gfx::Size(4, 3), false, 2));
+ EXPECT_EQ(1, XIndex(gfx::Size(3, 3), gfx::Size(4, 3), false, 3));
+
+ EXPECT_EQ(0, XIndex(gfx::Size(1, 1), gfx::Size(1, 1), true, 0));
+ EXPECT_EQ(0, XIndex(gfx::Size(2, 2), gfx::Size(2, 2), true, 0));
+ EXPECT_EQ(0, XIndex(gfx::Size(2, 2), gfx::Size(2, 2), true, 1));
+ EXPECT_EQ(0, XIndex(gfx::Size(3, 3), gfx::Size(3, 3), true, 0));
+ EXPECT_EQ(0, XIndex(gfx::Size(3, 3), gfx::Size(3, 3), true, 1));
+ EXPECT_EQ(0, XIndex(gfx::Size(3, 3), gfx::Size(3, 3), true, 2));
+
+ EXPECT_EQ(0, XIndex(gfx::Size(3, 3), gfx::Size(4, 3), true, 0));
+ EXPECT_EQ(0, XIndex(gfx::Size(3, 3), gfx::Size(4, 3), true, 1));
+ EXPECT_EQ(1, XIndex(gfx::Size(3, 3), gfx::Size(4, 3), true, 2));
+ EXPECT_EQ(1, XIndex(gfx::Size(3, 3), gfx::Size(4, 3), true, 3));
+}
+
+TEST(TilingDataTest, FirstBorderTileXIndexFromSrcCoord) {
+ EXPECT_EQ(0, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 0));
+ EXPECT_EQ(0, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 1));
+ EXPECT_EQ(0, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 2));
+ EXPECT_EQ(1, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 3));
+ EXPECT_EQ(1, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 4));
+ EXPECT_EQ(1, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 5));
+ EXPECT_EQ(2, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 6));
+ EXPECT_EQ(2, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 7));
+ EXPECT_EQ(2, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 8));
+ EXPECT_EQ(3, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 9));
+ EXPECT_EQ(3, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 10));
+ EXPECT_EQ(3, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 11));
+
+ EXPECT_EQ(0, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 0));
+ EXPECT_EQ(0, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 1));
+ EXPECT_EQ(0, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 2));
+ EXPECT_EQ(1, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 3));
+ EXPECT_EQ(2, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 4));
+ EXPECT_EQ(3, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 5));
+ EXPECT_EQ(4, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 6));
+ EXPECT_EQ(5, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 7));
+ EXPECT_EQ(6, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 8));
+ EXPECT_EQ(7, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 9));
+ EXPECT_EQ(7, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 10));
+ EXPECT_EQ(7, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 11));
+
+ EXPECT_EQ(0, MinBorderXIndex(gfx::Size(1, 1), gfx::Size(1, 1), false, 0));
+ EXPECT_EQ(0, MinBorderXIndex(gfx::Size(2, 2), gfx::Size(2, 2), false, 0));
+ EXPECT_EQ(0, MinBorderXIndex(gfx::Size(2, 2), gfx::Size(2, 2), false, 1));
+ EXPECT_EQ(0, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(3, 3), false, 0));
+ EXPECT_EQ(0, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(3, 3), false, 1));
+ EXPECT_EQ(0, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(3, 3), false, 2));
+
+ EXPECT_EQ(0, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(4, 3), false, 0));
+ EXPECT_EQ(0, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(4, 3), false, 1));
+ EXPECT_EQ(0, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(4, 3), false, 2));
+ EXPECT_EQ(1, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(4, 3), false, 3));
+
+ EXPECT_EQ(0, MinBorderXIndex(gfx::Size(1, 1), gfx::Size(1, 1), true, 0));
+ EXPECT_EQ(0, MinBorderXIndex(gfx::Size(2, 2), gfx::Size(2, 2), true, 0));
+ EXPECT_EQ(0, MinBorderXIndex(gfx::Size(2, 2), gfx::Size(2, 2), true, 1));
+ EXPECT_EQ(0, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(3, 3), true, 0));
+ EXPECT_EQ(0, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(3, 3), true, 1));
+ EXPECT_EQ(0, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(3, 3), true, 2));
+
+ EXPECT_EQ(0, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(4, 3), true, 0));
+ EXPECT_EQ(0, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(4, 3), true, 1));
+ EXPECT_EQ(0, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(4, 3), true, 2));
+ EXPECT_EQ(1, MinBorderXIndex(gfx::Size(3, 3), gfx::Size(4, 3), true, 3));
+}
+
+TEST(TilingDataTest, LastBorderTileXIndexFromSrcCoord) {
+ EXPECT_EQ(0, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 0));
+ EXPECT_EQ(0, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 1));
+ EXPECT_EQ(0, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 2));
+ EXPECT_EQ(1, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 3));
+ EXPECT_EQ(1, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 4));
+ EXPECT_EQ(1, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 5));
+ EXPECT_EQ(2, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 6));
+ EXPECT_EQ(2, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 7));
+ EXPECT_EQ(2, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 8));
+ EXPECT_EQ(3, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 9));
+ EXPECT_EQ(3, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 10));
+ EXPECT_EQ(3, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 11));
+
+ EXPECT_EQ(0, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 0));
+ EXPECT_EQ(1, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 1));
+ EXPECT_EQ(2, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 2));
+ EXPECT_EQ(3, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 3));
+ EXPECT_EQ(4, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 4));
+ EXPECT_EQ(5, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 5));
+ EXPECT_EQ(6, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 6));
+ EXPECT_EQ(7, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 7));
+ EXPECT_EQ(7, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 8));
+ EXPECT_EQ(7, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 9));
+ EXPECT_EQ(7, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 10));
+ EXPECT_EQ(7, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 11));
+
+ EXPECT_EQ(0, MaxBorderXIndex(gfx::Size(1, 1), gfx::Size(1, 1), false, 0));
+ EXPECT_EQ(0, MaxBorderXIndex(gfx::Size(2, 2), gfx::Size(2, 2), false, 0));
+ EXPECT_EQ(0, MaxBorderXIndex(gfx::Size(2, 2), gfx::Size(2, 2), false, 1));
+ EXPECT_EQ(0, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(3, 3), false, 0));
+ EXPECT_EQ(0, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(3, 3), false, 1));
+ EXPECT_EQ(0, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(3, 3), false, 2));
+
+ EXPECT_EQ(0, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(4, 3), false, 0));
+ EXPECT_EQ(0, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(4, 3), false, 1));
+ EXPECT_EQ(0, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(4, 3), false, 2));
+ EXPECT_EQ(1, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(4, 3), false, 3));
+
+ EXPECT_EQ(0, MaxBorderXIndex(gfx::Size(1, 1), gfx::Size(1, 1), true, 0));
+ EXPECT_EQ(0, MaxBorderXIndex(gfx::Size(2, 2), gfx::Size(2, 2), true, 0));
+ EXPECT_EQ(0, MaxBorderXIndex(gfx::Size(2, 2), gfx::Size(2, 2), true, 1));
+ EXPECT_EQ(0, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(3, 3), true, 0));
+ EXPECT_EQ(0, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(3, 3), true, 1));
+ EXPECT_EQ(0, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(3, 3), true, 2));
+
+ EXPECT_EQ(0, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(4, 3), true, 0));
+ EXPECT_EQ(1, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(4, 3), true, 1));
+ EXPECT_EQ(1, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(4, 3), true, 2));
+ EXPECT_EQ(1, MaxBorderXIndex(gfx::Size(3, 3), gfx::Size(4, 3), true, 3));
+}
+
+TEST(TilingDataTest, TileYIndexFromSrcCoord) {
+ EXPECT_EQ(0, YIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 0));
+ EXPECT_EQ(0, YIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 1));
+ EXPECT_EQ(0, YIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 2));
+ EXPECT_EQ(1, YIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 3));
+ EXPECT_EQ(1, YIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 4));
+ EXPECT_EQ(1, YIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 5));
+ EXPECT_EQ(2, YIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 6));
+ EXPECT_EQ(2, YIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 7));
+ EXPECT_EQ(2, YIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 8));
+ EXPECT_EQ(3, YIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 9));
+ EXPECT_EQ(3, YIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 10));
+ EXPECT_EQ(3, YIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 11));
+
+ EXPECT_EQ(0, YIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 0));
+ EXPECT_EQ(0, YIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 1));
+ EXPECT_EQ(1, YIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 2));
+ EXPECT_EQ(2, YIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 3));
+ EXPECT_EQ(3, YIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 4));
+ EXPECT_EQ(4, YIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 5));
+ EXPECT_EQ(5, YIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 6));
+ EXPECT_EQ(6, YIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 7));
+ EXPECT_EQ(7, YIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 8));
+ EXPECT_EQ(7, YIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 9));
+ EXPECT_EQ(7, YIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 10));
+ EXPECT_EQ(7, YIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 11));
+
+ EXPECT_EQ(0, YIndex(gfx::Size(1, 1), gfx::Size(1, 1), false, 0));
+ EXPECT_EQ(0, YIndex(gfx::Size(2, 2), gfx::Size(2, 2), false, 0));
+ EXPECT_EQ(0, YIndex(gfx::Size(2, 2), gfx::Size(2, 2), false, 1));
+ EXPECT_EQ(0, YIndex(gfx::Size(3, 3), gfx::Size(3, 3), false, 0));
+ EXPECT_EQ(0, YIndex(gfx::Size(3, 3), gfx::Size(3, 3), false, 1));
+ EXPECT_EQ(0, YIndex(gfx::Size(3, 3), gfx::Size(3, 3), false, 2));
+
+ EXPECT_EQ(0, YIndex(gfx::Size(3, 3), gfx::Size(3, 4), false, 0));
+ EXPECT_EQ(0, YIndex(gfx::Size(3, 3), gfx::Size(3, 4), false, 1));
+ EXPECT_EQ(0, YIndex(gfx::Size(3, 3), gfx::Size(3, 4), false, 2));
+ EXPECT_EQ(1, YIndex(gfx::Size(3, 3), gfx::Size(3, 4), false, 3));
+
+ EXPECT_EQ(0, YIndex(gfx::Size(1, 1), gfx::Size(1, 1), true, 0));
+ EXPECT_EQ(0, YIndex(gfx::Size(2, 2), gfx::Size(2, 2), true, 0));
+ EXPECT_EQ(0, YIndex(gfx::Size(2, 2), gfx::Size(2, 2), true, 1));
+ EXPECT_EQ(0, YIndex(gfx::Size(3, 3), gfx::Size(3, 3), true, 0));
+ EXPECT_EQ(0, YIndex(gfx::Size(3, 3), gfx::Size(3, 3), true, 1));
+ EXPECT_EQ(0, YIndex(gfx::Size(3, 3), gfx::Size(3, 3), true, 2));
+
+ EXPECT_EQ(0, YIndex(gfx::Size(3, 3), gfx::Size(3, 4), true, 0));
+ EXPECT_EQ(0, YIndex(gfx::Size(3, 3), gfx::Size(3, 4), true, 1));
+ EXPECT_EQ(1, YIndex(gfx::Size(3, 3), gfx::Size(3, 4), true, 2));
+ EXPECT_EQ(1, YIndex(gfx::Size(3, 3), gfx::Size(3, 4), true, 3));
+}
+
+TEST(TilingDataTest, FirstBorderTileYIndexFromSrcCoord) {
+ EXPECT_EQ(0, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 0));
+ EXPECT_EQ(0, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 1));
+ EXPECT_EQ(0, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 2));
+ EXPECT_EQ(1, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 3));
+ EXPECT_EQ(1, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 4));
+ EXPECT_EQ(1, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 5));
+ EXPECT_EQ(2, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 6));
+ EXPECT_EQ(2, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 7));
+ EXPECT_EQ(2, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 8));
+ EXPECT_EQ(3, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 9));
+ EXPECT_EQ(3, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 10));
+ EXPECT_EQ(3, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 11));
+
+ EXPECT_EQ(0, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 0));
+ EXPECT_EQ(0, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 1));
+ EXPECT_EQ(0, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 2));
+ EXPECT_EQ(1, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 3));
+ EXPECT_EQ(2, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 4));
+ EXPECT_EQ(3, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 5));
+ EXPECT_EQ(4, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 6));
+ EXPECT_EQ(5, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 7));
+ EXPECT_EQ(6, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 8));
+ EXPECT_EQ(7, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 9));
+ EXPECT_EQ(7, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 10));
+ EXPECT_EQ(7, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 11));
+
+ EXPECT_EQ(0, MinBorderYIndex(gfx::Size(1, 1), gfx::Size(1, 1), false, 0));
+ EXPECT_EQ(0, MinBorderYIndex(gfx::Size(2, 2), gfx::Size(2, 2), false, 0));
+ EXPECT_EQ(0, MinBorderYIndex(gfx::Size(2, 2), gfx::Size(2, 2), false, 1));
+ EXPECT_EQ(0, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 3), false, 0));
+ EXPECT_EQ(0, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 3), false, 1));
+ EXPECT_EQ(0, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 3), false, 2));
+
+ EXPECT_EQ(0, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 4), false, 0));
+ EXPECT_EQ(0, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 4), false, 1));
+ EXPECT_EQ(0, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 4), false, 2));
+ EXPECT_EQ(1, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 4), false, 3));
+
+ EXPECT_EQ(0, MinBorderYIndex(gfx::Size(1, 1), gfx::Size(1, 1), true, 0));
+ EXPECT_EQ(0, MinBorderYIndex(gfx::Size(2, 2), gfx::Size(2, 2), true, 0));
+ EXPECT_EQ(0, MinBorderYIndex(gfx::Size(2, 2), gfx::Size(2, 2), true, 1));
+ EXPECT_EQ(0, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 3), true, 0));
+ EXPECT_EQ(0, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 3), true, 1));
+ EXPECT_EQ(0, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 3), true, 2));
+
+ EXPECT_EQ(0, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 4), true, 0));
+ EXPECT_EQ(0, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 4), true, 1));
+ EXPECT_EQ(0, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 4), true, 2));
+ EXPECT_EQ(1, MinBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 4), true, 3));
+}
+
+TEST(TilingDataTest, LastBorderTileYIndexFromSrcCoord) {
+ EXPECT_EQ(0, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 0));
+ EXPECT_EQ(0, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 1));
+ EXPECT_EQ(0, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 2));
+ EXPECT_EQ(1, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 3));
+ EXPECT_EQ(1, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 4));
+ EXPECT_EQ(1, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 5));
+ EXPECT_EQ(2, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 6));
+ EXPECT_EQ(2, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 7));
+ EXPECT_EQ(2, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 8));
+ EXPECT_EQ(3, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 9));
+ EXPECT_EQ(3, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 10));
+ EXPECT_EQ(3, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), false, 11));
+
+ EXPECT_EQ(0, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 0));
+ EXPECT_EQ(1, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 1));
+ EXPECT_EQ(2, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 2));
+ EXPECT_EQ(3, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 3));
+ EXPECT_EQ(4, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 4));
+ EXPECT_EQ(5, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 5));
+ EXPECT_EQ(6, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 6));
+ EXPECT_EQ(7, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 7));
+ EXPECT_EQ(7, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 8));
+ EXPECT_EQ(7, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 9));
+ EXPECT_EQ(7, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 10));
+ EXPECT_EQ(7, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(10, 10), true, 11));
+
+ EXPECT_EQ(0, MaxBorderYIndex(gfx::Size(1, 1), gfx::Size(1, 1), false, 0));
+ EXPECT_EQ(0, MaxBorderYIndex(gfx::Size(2, 2), gfx::Size(2, 2), false, 0));
+ EXPECT_EQ(0, MaxBorderYIndex(gfx::Size(2, 2), gfx::Size(2, 2), false, 1));
+ EXPECT_EQ(0, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 3), false, 0));
+ EXPECT_EQ(0, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 3), false, 1));
+ EXPECT_EQ(0, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 3), false, 2));
+
+ EXPECT_EQ(0, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 4), false, 0));
+ EXPECT_EQ(0, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 4), false, 1));
+ EXPECT_EQ(0, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 4), false, 2));
+ EXPECT_EQ(1, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 4), false, 3));
+
+ EXPECT_EQ(0, MaxBorderYIndex(gfx::Size(1, 1), gfx::Size(1, 1), true, 0));
+ EXPECT_EQ(0, MaxBorderYIndex(gfx::Size(2, 2), gfx::Size(2, 2), true, 0));
+ EXPECT_EQ(0, MaxBorderYIndex(gfx::Size(2, 2), gfx::Size(2, 2), true, 1));
+ EXPECT_EQ(0, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 3), true, 0));
+ EXPECT_EQ(0, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 3), true, 1));
+ EXPECT_EQ(0, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 3), true, 2));
+
+ EXPECT_EQ(0, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 4), true, 0));
+ EXPECT_EQ(1, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 4), true, 1));
+ EXPECT_EQ(1, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 4), true, 2));
+ EXPECT_EQ(1, MaxBorderYIndex(gfx::Size(3, 3), gfx::Size(3, 4), true, 3));
+}
+
+TEST(TilingDataTest, TileSizeX) {
+ EXPECT_EQ(5, SizeX(gfx::Size(5, 5), gfx::Size(5, 5), false, 0));
+ EXPECT_EQ(5, SizeX(gfx::Size(5, 5), gfx::Size(5, 5), true, 0));
+
+ EXPECT_EQ(5, SizeX(gfx::Size(5, 5), gfx::Size(6, 6), false, 0));
+ EXPECT_EQ(1, SizeX(gfx::Size(5, 5), gfx::Size(6, 6), false, 1));
+ EXPECT_EQ(4, SizeX(gfx::Size(5, 5), gfx::Size(6, 6), true, 0));
+ EXPECT_EQ(2, SizeX(gfx::Size(5, 5), gfx::Size(6, 6), true, 1));
+
+ EXPECT_EQ(5, SizeX(gfx::Size(5, 5), gfx::Size(8, 8), false, 0));
+ EXPECT_EQ(3, SizeX(gfx::Size(5, 5), gfx::Size(8, 8), false, 1));
+ EXPECT_EQ(4, SizeX(gfx::Size(5, 5), gfx::Size(8, 8), true, 0));
+ EXPECT_EQ(4, SizeX(gfx::Size(5, 5), gfx::Size(8, 8), true, 1));
+
+ EXPECT_EQ(5, SizeX(gfx::Size(5, 5), gfx::Size(10, 10), false, 0));
+ EXPECT_EQ(5, SizeX(gfx::Size(5, 5), gfx::Size(10, 10), false, 1));
+ EXPECT_EQ(4, SizeX(gfx::Size(5, 5), gfx::Size(10, 10), true, 0));
+ EXPECT_EQ(3, SizeX(gfx::Size(5, 5), gfx::Size(10, 10), true, 1));
+ EXPECT_EQ(3, SizeX(gfx::Size(5, 5), gfx::Size(10, 10), true, 2));
+
+ EXPECT_EQ(4, SizeX(gfx::Size(5, 5), gfx::Size(11, 11), true, 2));
+ EXPECT_EQ(3, SizeX(gfx::Size(5, 5), gfx::Size(12, 12), true, 2));
+
+ EXPECT_EQ(3, SizeX(gfx::Size(5, 9), gfx::Size(12, 17), true, 2));
+}
+
+TEST(TilingDataTest, TileSizeY) {
+ EXPECT_EQ(5, SizeY(gfx::Size(5, 5), gfx::Size(5, 5), false, 0));
+ EXPECT_EQ(5, SizeY(gfx::Size(5, 5), gfx::Size(5, 5), true, 0));
+
+ EXPECT_EQ(5, SizeY(gfx::Size(5, 5), gfx::Size(6, 6), false, 0));
+ EXPECT_EQ(1, SizeY(gfx::Size(5, 5), gfx::Size(6, 6), false, 1));
+ EXPECT_EQ(4, SizeY(gfx::Size(5, 5), gfx::Size(6, 6), true, 0));
+ EXPECT_EQ(2, SizeY(gfx::Size(5, 5), gfx::Size(6, 6), true, 1));
+
+ EXPECT_EQ(5, SizeY(gfx::Size(5, 5), gfx::Size(8, 8), false, 0));
+ EXPECT_EQ(3, SizeY(gfx::Size(5, 5), gfx::Size(8, 8), false, 1));
+ EXPECT_EQ(4, SizeY(gfx::Size(5, 5), gfx::Size(8, 8), true, 0));
+ EXPECT_EQ(4, SizeY(gfx::Size(5, 5), gfx::Size(8, 8), true, 1));
+
+ EXPECT_EQ(5, SizeY(gfx::Size(5, 5), gfx::Size(10, 10), false, 0));
+ EXPECT_EQ(5, SizeY(gfx::Size(5, 5), gfx::Size(10, 10), false, 1));
+ EXPECT_EQ(4, SizeY(gfx::Size(5, 5), gfx::Size(10, 10), true, 0));
+ EXPECT_EQ(3, SizeY(gfx::Size(5, 5), gfx::Size(10, 10), true, 1));
+ EXPECT_EQ(3, SizeY(gfx::Size(5, 5), gfx::Size(10, 10), true, 2));
+
+ EXPECT_EQ(4, SizeY(gfx::Size(5, 5), gfx::Size(11, 11), true, 2));
+ EXPECT_EQ(3, SizeY(gfx::Size(5, 5), gfx::Size(12, 12), true, 2));
+
+ EXPECT_EQ(3, SizeY(gfx::Size(9, 5), gfx::Size(17, 12), true, 2));
+}
+
+TEST(TilingDataTest, TileSizeX_and_TilePositionX) {
+ // Single tile cases:
+ EXPECT_EQ(1, SizeX(gfx::Size(3, 3), gfx::Size(1, 1), false, 0));
+ EXPECT_EQ(0, PosX(gfx::Size(3, 3), gfx::Size(1, 1), false, 0));
+ EXPECT_EQ(1, SizeX(gfx::Size(3, 3), gfx::Size(1, 100), false, 0));
+ EXPECT_EQ(0, PosX(gfx::Size(3, 3), gfx::Size(1, 100), false, 0));
+ EXPECT_EQ(3, SizeX(gfx::Size(3, 3), gfx::Size(3, 1), false, 0));
+ EXPECT_EQ(0, PosX(gfx::Size(3, 3), gfx::Size(3, 1), false, 0));
+ EXPECT_EQ(3, SizeX(gfx::Size(3, 3), gfx::Size(3, 100), false, 0));
+ EXPECT_EQ(0, PosX(gfx::Size(3, 3), gfx::Size(3, 100), false, 0));
+ EXPECT_EQ(1, SizeX(gfx::Size(3, 3), gfx::Size(1, 1), true, 0));
+ EXPECT_EQ(0, PosX(gfx::Size(3, 3), gfx::Size(1, 1), true, 0));
+ EXPECT_EQ(1, SizeX(gfx::Size(3, 3), gfx::Size(1, 100), true, 0));
+ EXPECT_EQ(0, PosX(gfx::Size(3, 3), gfx::Size(1, 100), true, 0));
+ EXPECT_EQ(3, SizeX(gfx::Size(3, 3), gfx::Size(3, 1), true, 0));
+ EXPECT_EQ(0, PosX(gfx::Size(3, 3), gfx::Size(3, 1), true, 0));
+ EXPECT_EQ(3, SizeX(gfx::Size(3, 3), gfx::Size(3, 100), true, 0));
+ EXPECT_EQ(0, PosX(gfx::Size(3, 3), gfx::Size(3, 100), true, 0));
+
+ // Multiple tiles:
+ // no border
+ // positions 0, 3
+ EXPECT_EQ(2, NumTiles(gfx::Size(3, 3), gfx::Size(6, 1), false));
+ EXPECT_EQ(3, SizeX(gfx::Size(3, 3), gfx::Size(6, 1), false, 0));
+ EXPECT_EQ(3, SizeX(gfx::Size(3, 3), gfx::Size(6, 1), false, 1));
+ EXPECT_EQ(0, PosX(gfx::Size(3, 3), gfx::Size(6, 1), false, 0));
+ EXPECT_EQ(3, PosX(gfx::Size(3, 3), gfx::Size(6, 1), false, 1));
+ EXPECT_EQ(3, SizeX(gfx::Size(3, 3), gfx::Size(6, 100), false, 0));
+ EXPECT_EQ(3, SizeX(gfx::Size(3, 3), gfx::Size(6, 100), false, 1));
+ EXPECT_EQ(0, PosX(gfx::Size(3, 3), gfx::Size(6, 100), false, 0));
+ EXPECT_EQ(3, PosX(gfx::Size(3, 3), gfx::Size(6, 100), false, 1));
+
+ // Multiple tiles:
+ // with border
+ // positions 0, 2, 3, 4
+ EXPECT_EQ(4, NumTiles(gfx::Size(3, 3), gfx::Size(6, 1), true));
+ EXPECT_EQ(2, SizeX(gfx::Size(3, 3), gfx::Size(6, 1), true, 0));
+ EXPECT_EQ(1, SizeX(gfx::Size(3, 3), gfx::Size(6, 1), true, 1));
+ EXPECT_EQ(1, SizeX(gfx::Size(3, 3), gfx::Size(6, 1), true, 2));
+ EXPECT_EQ(2, SizeX(gfx::Size(3, 3), gfx::Size(6, 1), true, 3));
+ EXPECT_EQ(0, PosX(gfx::Size(3, 3), gfx::Size(6, 1), true, 0));
+ EXPECT_EQ(2, PosX(gfx::Size(3, 3), gfx::Size(6, 1), true, 1));
+ EXPECT_EQ(3, PosX(gfx::Size(3, 3), gfx::Size(6, 1), true, 2));
+ EXPECT_EQ(4, PosX(gfx::Size(3, 3), gfx::Size(6, 1), true, 3));
+ EXPECT_EQ(2, SizeX(gfx::Size(3, 7), gfx::Size(6, 100), true, 0));
+ EXPECT_EQ(1, SizeX(gfx::Size(3, 7), gfx::Size(6, 100), true, 1));
+ EXPECT_EQ(1, SizeX(gfx::Size(3, 7), gfx::Size(6, 100), true, 2));
+ EXPECT_EQ(2, SizeX(gfx::Size(3, 7), gfx::Size(6, 100), true, 3));
+ EXPECT_EQ(0, PosX(gfx::Size(3, 7), gfx::Size(6, 100), true, 0));
+ EXPECT_EQ(2, PosX(gfx::Size(3, 7), gfx::Size(6, 100), true, 1));
+ EXPECT_EQ(3, PosX(gfx::Size(3, 7), gfx::Size(6, 100), true, 2));
+ EXPECT_EQ(4, PosX(gfx::Size(3, 7), gfx::Size(6, 100), true, 3));
+}
+
+TEST(TilingDataTest, TileSizeY_and_TilePositionY) {
+ // Single tile cases:
+ EXPECT_EQ(1, SizeY(gfx::Size(3, 3), gfx::Size(1, 1), false, 0));
+ EXPECT_EQ(0, PosY(gfx::Size(3, 3), gfx::Size(1, 1), false, 0));
+ EXPECT_EQ(1, SizeY(gfx::Size(3, 3), gfx::Size(100, 1), false, 0));
+ EXPECT_EQ(0, PosY(gfx::Size(3, 3), gfx::Size(100, 1), false, 0));
+ EXPECT_EQ(3, SizeY(gfx::Size(3, 3), gfx::Size(1, 3), false, 0));
+ EXPECT_EQ(0, PosY(gfx::Size(3, 3), gfx::Size(1, 3), false, 0));
+ EXPECT_EQ(3, SizeY(gfx::Size(3, 3), gfx::Size(100, 3), false, 0));
+ EXPECT_EQ(0, PosY(gfx::Size(3, 3), gfx::Size(100, 3), false, 0));
+ EXPECT_EQ(1, SizeY(gfx::Size(3, 3), gfx::Size(1, 1), true, 0));
+ EXPECT_EQ(0, PosY(gfx::Size(3, 3), gfx::Size(1, 1), true, 0));
+ EXPECT_EQ(1, SizeY(gfx::Size(3, 3), gfx::Size(100, 1), true, 0));
+ EXPECT_EQ(0, PosY(gfx::Size(3, 3), gfx::Size(100, 1), true, 0));
+ EXPECT_EQ(3, SizeY(gfx::Size(3, 3), gfx::Size(1, 3), true, 0));
+ EXPECT_EQ(0, PosY(gfx::Size(3, 3), gfx::Size(1, 3), true, 0));
+ EXPECT_EQ(3, SizeY(gfx::Size(3, 3), gfx::Size(100, 3), true, 0));
+ EXPECT_EQ(0, PosY(gfx::Size(3, 3), gfx::Size(100, 3), true, 0));
+
+ // Multiple tiles:
+ // no border
+ // positions 0, 3
+ EXPECT_EQ(2, NumTiles(gfx::Size(3, 3), gfx::Size(1, 6), false));
+ EXPECT_EQ(3, SizeY(gfx::Size(3, 3), gfx::Size(1, 6), false, 0));
+ EXPECT_EQ(3, SizeY(gfx::Size(3, 3), gfx::Size(1, 6), false, 1));
+ EXPECT_EQ(0, PosY(gfx::Size(3, 3), gfx::Size(1, 6), false, 0));
+ EXPECT_EQ(3, PosY(gfx::Size(3, 3), gfx::Size(1, 6), false, 1));
+ EXPECT_EQ(3, SizeY(gfx::Size(3, 3), gfx::Size(100, 6), false, 0));
+ EXPECT_EQ(3, SizeY(gfx::Size(3, 3), gfx::Size(100, 6), false, 1));
+ EXPECT_EQ(0, PosY(gfx::Size(3, 3), gfx::Size(100, 6), false, 0));
+ EXPECT_EQ(3, PosY(gfx::Size(3, 3), gfx::Size(100, 6), false, 1));
+
+ // Multiple tiles:
+ // with border
+ // positions 0, 2, 3, 4
+ EXPECT_EQ(4, NumTiles(gfx::Size(3, 3), gfx::Size(1, 6), true));
+ EXPECT_EQ(2, SizeY(gfx::Size(3, 3), gfx::Size(1, 6), true, 0));
+ EXPECT_EQ(1, SizeY(gfx::Size(3, 3), gfx::Size(1, 6), true, 1));
+ EXPECT_EQ(1, SizeY(gfx::Size(3, 3), gfx::Size(1, 6), true, 2));
+ EXPECT_EQ(2, SizeY(gfx::Size(3, 3), gfx::Size(1, 6), true, 3));
+ EXPECT_EQ(0, PosY(gfx::Size(3, 3), gfx::Size(1, 6), true, 0));
+ EXPECT_EQ(2, PosY(gfx::Size(3, 3), gfx::Size(1, 6), true, 1));
+ EXPECT_EQ(3, PosY(gfx::Size(3, 3), gfx::Size(1, 6), true, 2));
+ EXPECT_EQ(4, PosY(gfx::Size(3, 3), gfx::Size(1, 6), true, 3));
+ EXPECT_EQ(2, SizeY(gfx::Size(7, 3), gfx::Size(100, 6), true, 0));
+ EXPECT_EQ(1, SizeY(gfx::Size(7, 3), gfx::Size(100, 6), true, 1));
+ EXPECT_EQ(1, SizeY(gfx::Size(7, 3), gfx::Size(100, 6), true, 2));
+ EXPECT_EQ(2, SizeY(gfx::Size(7, 3), gfx::Size(100, 6), true, 3));
+ EXPECT_EQ(0, PosY(gfx::Size(7, 3), gfx::Size(100, 6), true, 0));
+ EXPECT_EQ(2, PosY(gfx::Size(7, 3), gfx::Size(100, 6), true, 1));
+ EXPECT_EQ(3, PosY(gfx::Size(7, 3), gfx::Size(100, 6), true, 2));
+ EXPECT_EQ(4, PosY(gfx::Size(7, 3), gfx::Size(100, 6), true, 3));
+}
+
+TEST(TilingDataTest, SetTotalSize) {
+ TilingData data(gfx::Size(5, 5), gfx::Size(5, 5), false);
+ EXPECT_EQ(5, data.total_size().width());
+ EXPECT_EQ(5, data.total_size().height());
+ EXPECT_EQ(1, data.num_tiles_x());
+ EXPECT_EQ(5, data.TileSizeX(0));
+ EXPECT_EQ(1, data.num_tiles_y());
+ EXPECT_EQ(5, data.TileSizeY(0));
+
+ data.SetTotalSize(gfx::Size(6, 5));
+ EXPECT_EQ(6, data.total_size().width());
+ EXPECT_EQ(5, data.total_size().height());
+ EXPECT_EQ(2, data.num_tiles_x());
+ EXPECT_EQ(5, data.TileSizeX(0));
+ EXPECT_EQ(1, data.TileSizeX(1));
+ EXPECT_EQ(1, data.num_tiles_y());
+ EXPECT_EQ(5, data.TileSizeY(0));
+
+ data.SetTotalSize(gfx::Size(5, 12));
+ EXPECT_EQ(5, data.total_size().width());
+ EXPECT_EQ(12, data.total_size().height());
+ EXPECT_EQ(1, data.num_tiles_x());
+ EXPECT_EQ(5, data.TileSizeX(0));
+ EXPECT_EQ(3, data.num_tiles_y());
+ EXPECT_EQ(5, data.TileSizeY(0));
+ EXPECT_EQ(5, data.TileSizeY(1));
+ EXPECT_EQ(2, data.TileSizeY(2));
+}
+
+TEST(TilingDataTest, SetMaxTextureSizeNoBorders) {
+ TilingData data(gfx::Size(8, 8), gfx::Size(16, 32), false);
+ EXPECT_EQ(2, data.num_tiles_x());
+ EXPECT_EQ(4, data.num_tiles_y());
+
+ data.SetMaxTextureSize(gfx::Size(32, 32));
+ EXPECT_EQ(gfx::Size(32, 32), data.max_texture_size());
+ EXPECT_EQ(1, data.num_tiles_x());
+ EXPECT_EQ(1, data.num_tiles_y());
+
+ data.SetMaxTextureSize(gfx::Size(2, 2));
+ EXPECT_EQ(gfx::Size(2, 2), data.max_texture_size());
+ EXPECT_EQ(8, data.num_tiles_x());
+ EXPECT_EQ(16, data.num_tiles_y());
+
+ data.SetMaxTextureSize(gfx::Size(5, 5));
+ EXPECT_EQ(gfx::Size(5, 5), data.max_texture_size());
+ EXPECT_EQ(4, data.num_tiles_x());
+ EXPECT_EQ(7, data.num_tiles_y());
+
+ data.SetMaxTextureSize(gfx::Size(8, 5));
+ EXPECT_EQ(gfx::Size(8, 5), data.max_texture_size());
+ EXPECT_EQ(2, data.num_tiles_x());
+ EXPECT_EQ(7, data.num_tiles_y());
+}
+
+TEST(TilingDataTest, SetMaxTextureSizeBorders) {
+ TilingData data(gfx::Size(8, 8), gfx::Size(16, 32), true);
+ EXPECT_EQ(3, data.num_tiles_x());
+ EXPECT_EQ(5, data.num_tiles_y());
+
+ data.SetMaxTextureSize(gfx::Size(32, 32));
+ EXPECT_EQ(gfx::Size(32, 32), data.max_texture_size());
+ EXPECT_EQ(1, data.num_tiles_x());
+ EXPECT_EQ(1, data.num_tiles_y());
+
+ data.SetMaxTextureSize(gfx::Size(2, 2));
+ EXPECT_EQ(gfx::Size(2, 2), data.max_texture_size());
+ EXPECT_EQ(0, data.num_tiles_x());
+ EXPECT_EQ(0, data.num_tiles_y());
+
+ data.SetMaxTextureSize(gfx::Size(5, 5));
+ EXPECT_EQ(gfx::Size(5, 5), data.max_texture_size());
+ EXPECT_EQ(5, data.num_tiles_x());
+ EXPECT_EQ(10, data.num_tiles_y());
+
+ data.SetMaxTextureSize(gfx::Size(8, 5));
+ EXPECT_EQ(gfx::Size(8, 5), data.max_texture_size());
+ EXPECT_EQ(3, data.num_tiles_x());
+ EXPECT_EQ(10, data.num_tiles_y());
+}
+
+TEST(TilingDataTest, Assignment) {
+ {
+ TilingData source(gfx::Size(8, 8), gfx::Size(16, 32), true);
+ TilingData dest = source;
+ EXPECT_EQ(source.border_texels(), dest.border_texels());
+ EXPECT_EQ(source.max_texture_size(), dest.max_texture_size());
+ EXPECT_EQ(source.num_tiles_x(), dest.num_tiles_x());
+ EXPECT_EQ(source.num_tiles_y(), dest.num_tiles_y());
+ EXPECT_EQ(source.total_size().width(), dest.total_size().width());
+ EXPECT_EQ(source.total_size().height(), dest.total_size().height());
+ }
+ {
+ TilingData source(gfx::Size(7, 3), gfx::Size(6, 100), false);
+ TilingData dest(source);
+ EXPECT_EQ(source.border_texels(), dest.border_texels());
+ EXPECT_EQ(source.max_texture_size(), dest.max_texture_size());
+ EXPECT_EQ(source.num_tiles_x(), dest.num_tiles_x());
+ EXPECT_EQ(source.num_tiles_y(), dest.num_tiles_y());
+ EXPECT_EQ(source.total_size().width(), dest.total_size().width());
+ EXPECT_EQ(source.total_size().height(), dest.total_size().height());
+ }
+}
+
+TEST(TilingDataTest, SetBorderTexels) {
+ TilingData data(gfx::Size(8, 8), gfx::Size(16, 32), false);
+ EXPECT_EQ(2, data.num_tiles_x());
+ EXPECT_EQ(4, data.num_tiles_y());
+
+ data.SetHasBorderTexels(true);
+ EXPECT_EQ(3, data.num_tiles_x());
+ EXPECT_EQ(5, data.num_tiles_y());
+
+ data.SetHasBorderTexels(true);
+ EXPECT_EQ(3, data.num_tiles_x());
+ EXPECT_EQ(5, data.num_tiles_y());
+
+ data.SetHasBorderTexels(false);
+ EXPECT_EQ(2, data.num_tiles_x());
+ EXPECT_EQ(4, data.num_tiles_y());
+}
+
+TEST(TilingDataTest, LargeBorders) {
+ TilingData data(gfx::Size(100, 80), gfx::Size(200, 145), 30);
+ EXPECT_EQ(30, data.border_texels());
+
+ EXPECT_EQ(70, data.TileSizeX(0));
+ EXPECT_EQ(40, data.TileSizeX(1));
+ EXPECT_EQ(40, data.TileSizeX(2));
+ EXPECT_EQ(50, data.TileSizeX(3));
+ EXPECT_EQ(4, data.num_tiles_x());
+
+ EXPECT_EQ(50, data.TileSizeY(0));
+ EXPECT_EQ(20, data.TileSizeY(1));
+ EXPECT_EQ(20, data.TileSizeY(2));
+ EXPECT_EQ(20, data.TileSizeY(3));
+ EXPECT_EQ(35, data.TileSizeY(4));
+ EXPECT_EQ(5, data.num_tiles_y());
+
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 70, 50), data.TileBounds(0, 0));
+ EXPECT_RECT_EQ(gfx::Rect(70, 50, 40, 20), data.TileBounds(1, 1));
+ EXPECT_RECT_EQ(gfx::Rect(110, 110, 40, 35), data.TileBounds(2, 4));
+ EXPECT_RECT_EQ(gfx::Rect(150, 70, 50, 20), data.TileBounds(3, 2));
+ EXPECT_RECT_EQ(gfx::Rect(150, 110, 50, 35), data.TileBounds(3, 4));
+
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 80), data.TileBoundsWithBorder(0, 0));
+ EXPECT_RECT_EQ(gfx::Rect(40, 20, 100, 80), data.TileBoundsWithBorder(1, 1));
+ EXPECT_RECT_EQ(gfx::Rect(80, 80, 100, 65), data.TileBoundsWithBorder(2, 4));
+ EXPECT_RECT_EQ(gfx::Rect(120, 40, 80, 80), data.TileBoundsWithBorder(3, 2));
+ EXPECT_RECT_EQ(gfx::Rect(120, 80, 80, 65), data.TileBoundsWithBorder(3, 4));
+
+ EXPECT_EQ(0, data.TileXIndexFromSrcCoord(0));
+ EXPECT_EQ(0, data.TileXIndexFromSrcCoord(69));
+ EXPECT_EQ(1, data.TileXIndexFromSrcCoord(70));
+ EXPECT_EQ(1, data.TileXIndexFromSrcCoord(109));
+ EXPECT_EQ(2, data.TileXIndexFromSrcCoord(110));
+ EXPECT_EQ(2, data.TileXIndexFromSrcCoord(149));
+ EXPECT_EQ(3, data.TileXIndexFromSrcCoord(150));
+ EXPECT_EQ(3, data.TileXIndexFromSrcCoord(199));
+
+ EXPECT_EQ(0, data.TileYIndexFromSrcCoord(0));
+ EXPECT_EQ(0, data.TileYIndexFromSrcCoord(49));
+ EXPECT_EQ(1, data.TileYIndexFromSrcCoord(50));
+ EXPECT_EQ(1, data.TileYIndexFromSrcCoord(69));
+ EXPECT_EQ(2, data.TileYIndexFromSrcCoord(70));
+ EXPECT_EQ(2, data.TileYIndexFromSrcCoord(89));
+ EXPECT_EQ(3, data.TileYIndexFromSrcCoord(90));
+ EXPECT_EQ(3, data.TileYIndexFromSrcCoord(109));
+ EXPECT_EQ(4, data.TileYIndexFromSrcCoord(110));
+ EXPECT_EQ(4, data.TileYIndexFromSrcCoord(144));
+
+ EXPECT_EQ(0, data.FirstBorderTileXIndexFromSrcCoord(0));
+ EXPECT_EQ(0, data.FirstBorderTileXIndexFromSrcCoord(99));
+ EXPECT_EQ(1, data.FirstBorderTileXIndexFromSrcCoord(100));
+ EXPECT_EQ(1, data.FirstBorderTileXIndexFromSrcCoord(139));
+ EXPECT_EQ(2, data.FirstBorderTileXIndexFromSrcCoord(140));
+ EXPECT_EQ(2, data.FirstBorderTileXIndexFromSrcCoord(179));
+ EXPECT_EQ(3, data.FirstBorderTileXIndexFromSrcCoord(180));
+ EXPECT_EQ(3, data.FirstBorderTileXIndexFromSrcCoord(199));
+
+ EXPECT_EQ(0, data.FirstBorderTileYIndexFromSrcCoord(0));
+ EXPECT_EQ(0, data.FirstBorderTileYIndexFromSrcCoord(79));
+ EXPECT_EQ(1, data.FirstBorderTileYIndexFromSrcCoord(80));
+ EXPECT_EQ(1, data.FirstBorderTileYIndexFromSrcCoord(99));
+ EXPECT_EQ(2, data.FirstBorderTileYIndexFromSrcCoord(100));
+ EXPECT_EQ(2, data.FirstBorderTileYIndexFromSrcCoord(119));
+ EXPECT_EQ(3, data.FirstBorderTileYIndexFromSrcCoord(120));
+ EXPECT_EQ(3, data.FirstBorderTileYIndexFromSrcCoord(139));
+ EXPECT_EQ(4, data.FirstBorderTileYIndexFromSrcCoord(140));
+ EXPECT_EQ(4, data.FirstBorderTileYIndexFromSrcCoord(144));
+
+ EXPECT_EQ(0, data.LastBorderTileXIndexFromSrcCoord(0));
+ EXPECT_EQ(0, data.LastBorderTileXIndexFromSrcCoord(39));
+ EXPECT_EQ(1, data.LastBorderTileXIndexFromSrcCoord(40));
+ EXPECT_EQ(1, data.LastBorderTileXIndexFromSrcCoord(79));
+ EXPECT_EQ(2, data.LastBorderTileXIndexFromSrcCoord(80));
+ EXPECT_EQ(2, data.LastBorderTileXIndexFromSrcCoord(119));
+ EXPECT_EQ(3, data.LastBorderTileXIndexFromSrcCoord(120));
+ EXPECT_EQ(3, data.LastBorderTileXIndexFromSrcCoord(199));
+
+ EXPECT_EQ(0, data.LastBorderTileYIndexFromSrcCoord(0));
+ EXPECT_EQ(0, data.LastBorderTileYIndexFromSrcCoord(19));
+ EXPECT_EQ(1, data.LastBorderTileYIndexFromSrcCoord(20));
+ EXPECT_EQ(1, data.LastBorderTileYIndexFromSrcCoord(39));
+ EXPECT_EQ(2, data.LastBorderTileYIndexFromSrcCoord(40));
+ EXPECT_EQ(2, data.LastBorderTileYIndexFromSrcCoord(59));
+ EXPECT_EQ(3, data.LastBorderTileYIndexFromSrcCoord(60));
+ EXPECT_EQ(3, data.LastBorderTileYIndexFromSrcCoord(79));
+ EXPECT_EQ(4, data.LastBorderTileYIndexFromSrcCoord(80));
+ EXPECT_EQ(4, data.LastBorderTileYIndexFromSrcCoord(144));
+}
+
+void TestIterate(
+ const TilingData& data,
+ gfx::Rect rect,
+ int expect_left,
+ int expect_top,
+ int expect_right,
+ int expect_bottom) {
+
+ EXPECT_GE(expect_left, 0);
+ EXPECT_GE(expect_top, 0);
+ EXPECT_LT(expect_right, data.num_tiles_x());
+ EXPECT_LT(expect_bottom, data.num_tiles_y());
+
+ std::vector<std::pair<int, int> > original_expected;
+ for (int x = 0; x < data.num_tiles_x(); ++x) {
+ for (int y = 0; y < data.num_tiles_y(); ++y) {
+ gfx::Rect bounds = data.TileBoundsWithBorder(x, y);
+ if (x >= expect_left && x <= expect_right &&
+ y >= expect_top && y <= expect_bottom) {
+ EXPECT_TRUE(bounds.Intersects(rect));
+ original_expected.push_back(std::make_pair(x, y));
+ } else {
+ EXPECT_FALSE(bounds.Intersects(rect));
+ }
+ }
+ }
+
+ // Verify with vanilla iterator.
+ {
+ std::vector<std::pair<int, int> > expected = original_expected;
+ for (TilingData::Iterator iter(&data, rect); iter; ++iter) {
+ bool found = false;
+ for (size_t i = 0; i < expected.size(); ++i) {
+ if (expected[i] == iter.index()) {
+ expected[i] = expected.back();
+ expected.pop_back();
+ found = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(found);
+ }
+ EXPECT_EQ(0u, expected.size());
+ }
+
+ // Make sure this also works with a difference iterator and an empty ignore.
+ {
+ std::vector<std::pair<int, int> > expected = original_expected;
+ for (TilingData::DifferenceIterator iter(&data, rect, gfx::Rect());
+ iter; ++iter) {
+ bool found = false;
+ for (size_t i = 0; i < expected.size(); ++i) {
+ if (expected[i] == iter.index()) {
+ expected[i] = expected.back();
+ expected.pop_back();
+ found = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(found);
+ }
+ EXPECT_EQ(0u, expected.size());
+ }
+}
+
+TEST(TilingDataTest, IteratorNoBorderTexels) {
+ TilingData data(gfx::Size(10, 10), gfx::Size(40, 25), false);
+ // X border index by src coord: [0-10), [10-20), [20, 30), [30, 40)
+ // Y border index by src coord: [0-10), [10-20), [20, 25)
+ TestIterate(data, gfx::Rect(0, 0, 40, 25), 0, 0, 3, 2);
+ TestIterate(data, gfx::Rect(15, 15, 8, 8), 1, 1, 2, 2);
+
+ // Oversized.
+ TestIterate(data, gfx::Rect(-100, -100, 1000, 1000), 0, 0, 3, 2);
+ TestIterate(data, gfx::Rect(-100, 20, 1000, 1), 0, 2, 3, 2);
+ TestIterate(data, gfx::Rect(29, -100, 31, 1000), 2, 0, 3, 2);
+ // Nonintersecting.
+ TestIterate(data, gfx::Rect(60, 80, 100, 100), 0, 0, -1, -1);
+}
+
+TEST(TilingDataTest, IteratorOneBorderTexel) {
+ TilingData data(gfx::Size(10, 20), gfx::Size(25, 45), true);
+ // X border index by src coord: [0-10), [8-18), [16-25)
+ // Y border index by src coord: [0-20), [18-38), [36-45)
+ TestIterate(data, gfx::Rect(0, 0, 25, 45), 0, 0, 2, 2);
+ TestIterate(data, gfx::Rect(18, 19, 3, 17), 2, 0, 2, 1);
+ TestIterate(data, gfx::Rect(10, 20, 6, 16), 1, 1, 1, 1);
+ TestIterate(data, gfx::Rect(9, 19, 8, 18), 0, 0, 2, 2);
+
+ // Oversized.
+ TestIterate(data, gfx::Rect(-100, -100, 1000, 1000), 0, 0, 2, 2);
+ TestIterate(data, gfx::Rect(-100, 20, 1000, 1), 0, 1, 2, 1);
+ TestIterate(data, gfx::Rect(18, -100, 6, 1000), 2, 0, 2, 2);
+ // Nonintersecting.
+ TestIterate(data, gfx::Rect(60, 80, 100, 100), 0, 0, -1, -1);
+}
+
+TEST(TilingDataTest, IteratorManyBorderTexels) {
+ TilingData data(gfx::Size(50, 60), gfx::Size(65, 110), 20);
+ // X border index by src coord: [0-50), [10-60), [20-65)
+ // Y border index by src coord: [0-60), [20-80), [40-100), [60-110)
+ TestIterate(data, gfx::Rect(0, 0, 65, 110), 0, 0, 2, 3);
+ TestIterate(data, gfx::Rect(50, 60, 15, 65), 1, 1, 2, 3);
+ TestIterate(data, gfx::Rect(60, 30, 2, 10), 2, 0, 2, 1);
+
+ // Oversized.
+ TestIterate(data, gfx::Rect(-100, -100, 1000, 1000), 0, 0, 2, 3);
+ TestIterate(data, gfx::Rect(-100, 10, 1000, 10), 0, 0, 2, 0);
+ TestIterate(data, gfx::Rect(10, -100, 10, 1000), 0, 0, 1, 3);
+ // Nonintersecting.
+ TestIterate(data, gfx::Rect(65, 110, 100, 100), 0, 0, -1, -1);
+}
+
+TEST(TilingDataTest, IteratorOneTile) {
+ TilingData no_border(gfx::Size(1000, 1000), gfx::Size(30, 40), false);
+ TestIterate(no_border, gfx::Rect(0, 0, 30, 40), 0, 0, 0, 0);
+ TestIterate(no_border, gfx::Rect(10, 10, 20, 20), 0, 0, 0, 0);
+ TestIterate(no_border, gfx::Rect(30, 40, 100, 100), 0, 0, -1, -1);
+
+ TilingData one_border(gfx::Size(1000, 1000), gfx::Size(30, 40), true);
+ TestIterate(one_border, gfx::Rect(0, 0, 30, 40), 0, 0, 0, 0);
+ TestIterate(one_border, gfx::Rect(10, 10, 20, 20), 0, 0, 0, 0);
+ TestIterate(one_border, gfx::Rect(30, 40, 100, 100), 0, 0, -1, -1);
+
+ TilingData big_border(gfx::Size(1000, 1000), gfx::Size(30, 40), 50);
+ TestIterate(big_border, gfx::Rect(0, 0, 30, 40), 0, 0, 0, 0);
+ TestIterate(big_border, gfx::Rect(10, 10, 20, 20), 0, 0, 0, 0);
+ TestIterate(big_border, gfx::Rect(30, 40, 100, 100), 0, 0, -1, -1);
+}
+
+TEST(TilingDataTest, IteratorNoTiles) {
+ TilingData data(gfx::Size(100, 100), gfx::Size(), false);
+ TestIterate(data, gfx::Rect(0, 0, 100, 100), 0, 0, -1, -1);
+}
+
+void TestDiff(
+ const TilingData& data,
+ gfx::Rect consider,
+ gfx::Rect ignore,
+ size_t num_tiles) {
+
+ std::vector<std::pair<int, int> > expected;
+ for (int y = 0; y < data.num_tiles_y(); ++y) {
+ for (int x = 0; x < data.num_tiles_x(); ++x) {
+ gfx::Rect bounds = data.TileBoundsWithBorder(x, y);
+ if (bounds.Intersects(consider) && !bounds.Intersects(ignore))
+ expected.push_back(std::make_pair(x, y));
+ }
+ }
+
+ // Sanity check the test.
+ EXPECT_EQ(num_tiles, expected.size());
+
+ for (TilingData::DifferenceIterator iter(&data, consider, ignore);
+ iter; ++iter) {
+ bool found = false;
+ for (size_t i = 0; i < expected.size(); ++i) {
+ if (expected[i] == iter.index()) {
+ expected[i] = expected.back();
+ expected.pop_back();
+ found = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(found);
+ }
+ EXPECT_EQ(0u, expected.size());
+}
+
+TEST(TilingDataTest, DifferenceIteratorIgnoreGeometry) {
+ // This test is checking that the iterator can handle different geometries of
+ // ignore rects relative to the consider rect. The consider rect indices
+ // themselves are mostly tested by the non-difference iterator tests, so the
+ // full rect is mostly used here for simplicity.
+
+ // X border index by src coord: [0-10), [10-20), [20, 30), [30, 40)
+ // Y border index by src coord: [0-10), [10-20), [20, 25)
+ TilingData data(gfx::Size(10, 10), gfx::Size(40, 25), false);
+
+ // Fully ignored
+ TestDiff(data, gfx::Rect(0, 0, 40, 25), gfx::Rect(0, 0, 40, 25), 0);
+ TestDiff(data, gfx::Rect(0, 0, 40, 25), gfx::Rect(-100, -100, 200, 200), 0);
+ TestDiff(data, gfx::Rect(0, 0, 40, 25), gfx::Rect(9, 9, 30, 15), 0);
+ TestDiff(data, gfx::Rect(15, 15, 8, 8), gfx::Rect(15, 15, 8, 8), 0);
+
+ // Fully un-ignored
+ TestDiff(data, gfx::Rect(0, 0, 40, 25), gfx::Rect(-30, -20, 8, 8), 12);
+ TestDiff(data, gfx::Rect(0, 0, 40, 25), gfx::Rect(), 12);
+
+ // Top left, remove 2x2 tiles
+ TestDiff(data, gfx::Rect(0, 0, 40, 25), gfx::Rect(0, 0, 20, 19), 8);
+ // Bottom right, remove 2x2 tiles
+ TestDiff(data, gfx::Rect(0, 0, 40, 25), gfx::Rect(20, 15, 20, 6), 8);
+ // Bottom left, remove 2x2 tiles
+ TestDiff(data, gfx::Rect(0, 0, 40, 25), gfx::Rect(0, 15, 20, 6), 8);
+ // Top right, remove 2x2 tiles
+ TestDiff(data, gfx::Rect(0, 0, 40, 25), gfx::Rect(20, 0, 20, 19), 8);
+ // Center, remove only one tile
+ TestDiff(data, gfx::Rect(0, 0, 40, 25), gfx::Rect(10, 10, 5, 5), 11);
+
+ // Left column, flush left, removing two columns
+ TestDiff(data, gfx::Rect(0, 0, 40, 25), gfx::Rect(0, 0, 11, 25), 6);
+ // Middle column, removing two columns
+ TestDiff(data, gfx::Rect(0, 0, 40, 25), gfx::Rect(11, 0, 11, 25), 6);
+ // Right column, flush right, removing one column
+ TestDiff(data, gfx::Rect(0, 0, 40, 25), gfx::Rect(30, 0, 2, 25), 9);
+
+ // Top row, flush top, removing one row
+ TestDiff(data, gfx::Rect(0, 0, 40, 25), gfx::Rect(0, 5, 40, 5), 8);
+ // Middle row, removing one row
+ TestDiff(data, gfx::Rect(0, 0, 40, 25), gfx::Rect(0, 13, 40, 5), 8);
+ // Bottom row, flush bottom, removing two rows
+ TestDiff(data, gfx::Rect(0, 0, 40, 25), gfx::Rect(0, 13, 40, 12), 4);
+
+ // Non-intersecting, but still touching two of the same tiles.
+ TestDiff(data, gfx::Rect(8, 0, 32, 25), gfx::Rect(0, 12, 5, 12), 10);
+
+ // Intersecting, but neither contains the other. 2x3 with one overlap.
+ TestDiff(data, gfx::Rect(5, 2, 20, 10), gfx::Rect(25, 15, 5, 10), 5);
+}
+
+TEST(TilingDataTest, DifferenceIteratorManyBorderTexels) {
+ // X border index by src coord: [0-50), [10-60), [20-65)
+ // Y border index by src coord: [0-60), [20-80), [40-100), [60-110)
+ TilingData data(gfx::Size(50, 60), gfx::Size(65, 110), 20);
+
+ // Ignore one column, three rows
+ TestDiff(data, gfx::Rect(0, 30, 55, 80), gfx::Rect(5, 30, 5, 15), 9);
+
+ // Knock out three columns, leaving only one.
+ TestDiff(data, gfx::Rect(10, 30, 55, 80), gfx::Rect(30, 59, 20, 1), 3);
+
+ // Overlap all tiles with ignore rect.
+ TestDiff(data, gfx::Rect(0, 0, 65, 110), gfx::Rect(30, 59, 1, 2), 0);
+}
+
+TEST(TilingDataTest, DifferenceIteratorOneTile) {
+ TilingData no_border(gfx::Size(1000, 1000), gfx::Size(30, 40), false);
+ TestDiff(no_border, gfx::Rect(0, 0, 30, 40), gfx::Rect(), 1);
+ TestDiff(no_border, gfx::Rect(5, 5, 100, 100), gfx::Rect(5, 5, 1, 1), 0);
+
+ TilingData one_border(gfx::Size(1000, 1000), gfx::Size(30, 40), true);
+ TestDiff(one_border, gfx::Rect(0, 0, 30, 40), gfx::Rect(), 1);
+ TestDiff(one_border, gfx::Rect(5, 5, 100, 100), gfx::Rect(5, 5, 1, 1), 0);
+
+ TilingData big_border(gfx::Size(1000, 1000), gfx::Size(30, 40), 50);
+ TestDiff(big_border, gfx::Rect(0, 0, 30, 40), gfx::Rect(), 1);
+ TestDiff(big_border, gfx::Rect(5, 5, 100, 100), gfx::Rect(5, 5, 1, 1), 0);
+}
+
+TEST(TilingDataTest, DifferenceIteratorNoTiles) {
+ TilingData data(gfx::Size(100, 100), gfx::Size(), false);
+ TestDiff(data, gfx::Rect(0, 0, 100, 100), gfx::Rect(0, 0, 5, 5), 0);
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/base/util.h b/chromium/cc/base/util.h
new file mode 100644
index 00000000000..1d716ae2a42
--- /dev/null
+++ b/chromium/cc/base/util.h
@@ -0,0 +1,29 @@
+// 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.
+
+#ifndef CC_BASE_UTIL_H_
+#define CC_BASE_UTIL_H_
+
+#include <limits>
+
+#include "base/basictypes.h"
+
+namespace cc {
+
+template <typename T> T RoundUp(T n, T mul) {
+ COMPILE_ASSERT(std::numeric_limits<T>::is_integer, type_must_be_integral);
+ return (n > 0) ? ((n + mul - 1) / mul) * mul
+ : (n / mul) * mul;
+}
+
+template <typename T> T RoundDown(T n, T mul) {
+ COMPILE_ASSERT(std::numeric_limits<T>::is_integer, type_must_be_integral);
+ return (n > 0) ? (n / mul) * mul
+ : (n == 0) ? 0
+ : ((n - mul + 1) / mul) * mul;
+}
+
+} // namespace cc
+
+#endif // CC_BASE_UTIL_H_
diff --git a/chromium/cc/base/util_unittest.cc b/chromium/cc/base/util_unittest.cc
new file mode 100644
index 00000000000..6665a6a458d
--- /dev/null
+++ b/chromium/cc/base/util_unittest.cc
@@ -0,0 +1,67 @@
+// Copyright 2013 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 "cc/base/util.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+TEST(UtilTest, RoundUp) {
+ for (int multiplier = 1; multiplier <= 10; ++multiplier) {
+ // Try attempts in descending order, so that we can
+ // determine the correct value before it's needed.
+ int correct;
+ for (int attempt = 5 * multiplier; attempt >= -5 * multiplier; --attempt) {
+ if ((attempt % multiplier) == 0)
+ correct = attempt;
+ EXPECT_EQ(correct, RoundUp(attempt, multiplier))
+ << "attempt=" << attempt << " multiplier=" << multiplier;
+ }
+ }
+
+ for (unsigned multiplier = 1; multiplier <= 10; ++multiplier) {
+ // Try attempts in descending order, so that we can
+ // determine the correct value before it's needed.
+ unsigned correct;
+ for (unsigned attempt = 5 * multiplier; attempt > 0; --attempt) {
+ if ((attempt % multiplier) == 0)
+ correct = attempt;
+ EXPECT_EQ(correct, RoundUp(attempt, multiplier))
+ << "attempt=" << attempt << " multiplier=" << multiplier;
+ }
+ EXPECT_EQ(0u, RoundUp(0u, multiplier))
+ << "attempt=0 multiplier=" << multiplier;
+ }
+}
+
+TEST(UtilTest, RoundDown) {
+ for (int multiplier = 1; multiplier <= 10; ++multiplier) {
+ // Try attempts in ascending order, so that we can
+ // determine the correct value before it's needed.
+ int correct;
+ for (int attempt = -5 * multiplier; attempt <= 5 * multiplier; ++attempt) {
+ if ((attempt % multiplier) == 0)
+ correct = attempt;
+ EXPECT_EQ(correct, RoundDown(attempt, multiplier))
+ << "attempt=" << attempt << " multiplier=" << multiplier;
+ }
+ }
+
+ for (unsigned multiplier = 1; multiplier <= 10; ++multiplier) {
+ // Try attempts in ascending order, so that we can
+ // determine the correct value before it's needed.
+ unsigned correct;
+ for (unsigned attempt = 0; attempt <= 5 * multiplier; ++attempt) {
+ if ((attempt % multiplier) == 0)
+ correct = attempt;
+ EXPECT_EQ(correct, RoundDown(attempt, multiplier))
+ << "attempt=" << attempt << " multiplier=" << multiplier;
+ }
+ }
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/cc.gyp b/chromium/cc/cc.gyp
new file mode 100644
index 00000000000..e6e03c49899
--- /dev/null
+++ b/chromium/cc/cc.gyp
@@ -0,0 +1,393 @@
+# Copyright (c) 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.
+
+{
+ 'variables': {
+ 'chromium_code': 1,
+ },
+ 'targets': [
+ {
+ 'target_name': 'cc',
+ 'type': '<(component)',
+ 'dependencies': [
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
+ '<(DEPTH)/gpu/gpu.gyp:gpu',
+ '<(DEPTH)/skia/skia.gyp:skia',
+ '<(DEPTH)/media/media.gyp:media',
+ '<(DEPTH)/ui/gl/gl.gyp:gl',
+ '<(DEPTH)/ui/ui.gyp:ui',
+ '<(DEPTH)/third_party/WebKit/public/blink.gyp:blink_minimal',
+ ],
+ 'defines': [
+ 'CC_IMPLEMENTATION=1',
+ ],
+ 'sources': [
+ 'animation/animation.cc',
+ 'animation/animation.h',
+ 'animation/animation_curve.cc',
+ 'animation/animation_curve.h',
+ 'animation/animation_delegate.h',
+ 'animation/animation_events.cc',
+ 'animation/animation_events.h',
+ 'animation/animation_id_provider.cc',
+ 'animation/animation_id_provider.h',
+ 'animation/animation_registrar.cc',
+ 'animation/animation_registrar.h',
+ 'animation/keyframed_animation_curve.cc',
+ 'animation/keyframed_animation_curve.h',
+ 'animation/layer_animation_controller.cc',
+ 'animation/layer_animation_controller.h',
+ 'animation/layer_animation_event_observer.h',
+ 'animation/layer_animation_value_observer.h',
+ 'animation/scrollbar_animation_controller.h',
+ 'animation/scrollbar_animation_controller_linear_fade.cc',
+ 'animation/scrollbar_animation_controller_linear_fade.h',
+ 'animation/timing_function.cc',
+ 'animation/timing_function.h',
+ 'animation/transform_operation.cc',
+ 'animation/transform_operation.h',
+ 'animation/transform_operations.cc',
+ 'animation/transform_operations.h',
+ 'base/completion_event.h',
+ 'base/invalidation_region.cc',
+ 'base/invalidation_region.h',
+ 'base/math_util.cc',
+ 'base/math_util.h',
+ 'base/region.cc',
+ 'base/region.h',
+ 'base/scoped_ptr_algorithm.h',
+ 'base/scoped_ptr_deque.h',
+ 'base/scoped_ptr_hash_map.h',
+ 'base/scoped_ptr_vector.h',
+ 'base/switches.cc',
+ 'base/switches.h',
+ 'base/tiling_data.cc',
+ 'base/tiling_data.h',
+ 'base/util.h',
+ 'debug/debug_colors.cc',
+ 'debug/debug_colors.h',
+ 'debug/debug_rect_history.cc',
+ 'debug/debug_rect_history.h',
+ 'debug/devtools_instrumentation.h',
+ 'debug/fake_web_graphics_context_3d.cc',
+ 'debug/fake_web_graphics_context_3d.h',
+ 'debug/frame_rate_counter.cc',
+ 'debug/frame_rate_counter.h',
+ 'debug/layer_tree_debug_state.cc',
+ 'debug/layer_tree_debug_state.h',
+ 'debug/overdraw_metrics.cc',
+ 'debug/overdraw_metrics.h',
+ 'debug/paint_time_counter.cc',
+ 'debug/paint_time_counter.h',
+ 'debug/rendering_stats.cc',
+ 'debug/rendering_stats.h',
+ 'debug/rendering_stats_instrumentation.cc',
+ 'debug/rendering_stats_instrumentation.h',
+ 'debug/ring_buffer.h',
+ 'debug/test_context_provider.cc',
+ 'debug/test_context_provider.h',
+ 'debug/test_web_graphics_context_3d.cc',
+ 'debug/test_web_graphics_context_3d.h',
+ 'debug/traced_picture.cc',
+ 'debug/traced_picture.h',
+ 'debug/traced_value.cc',
+ 'debug/traced_value.h',
+ 'input/input_handler.h',
+ 'input/page_scale_animation.cc',
+ 'input/page_scale_animation.h',
+ 'input/top_controls_manager.cc',
+ 'input/top_controls_manager.h',
+ 'input/top_controls_manager_client.h',
+ 'layers/append_quads_data.h',
+ 'layers/compositing_reasons.h',
+ 'layers/content_layer.cc',
+ 'layers/content_layer.h',
+ 'layers/content_layer_client.h',
+ 'layers/contents_scaling_layer.cc',
+ 'layers/contents_scaling_layer.h',
+ 'layers/delegated_renderer_layer.cc',
+ 'layers/delegated_renderer_layer.h',
+ 'layers/delegated_renderer_layer_client.h',
+ 'layers/delegated_renderer_layer_impl.cc',
+ 'layers/delegated_renderer_layer_impl.h',
+ 'layers/draw_properties.h',
+ 'layers/heads_up_display_layer.cc',
+ 'layers/heads_up_display_layer.h',
+ 'layers/heads_up_display_layer_impl.cc',
+ 'layers/heads_up_display_layer_impl.h',
+ 'layers/image_layer.cc',
+ 'layers/image_layer.h',
+ 'layers/io_surface_layer.cc',
+ 'layers/io_surface_layer.h',
+ 'layers/io_surface_layer_impl.cc',
+ 'layers/io_surface_layer_impl.h',
+ 'layers/layer.cc',
+ 'layers/layer.h',
+ 'layers/layer_impl.cc',
+ 'layers/layer_impl.h',
+ 'layers/layer_iterator.cc',
+ 'layers/layer_iterator.h',
+ 'layers/layer_lists.cc',
+ 'layers/layer_lists.h',
+ 'layers/layer_position_constraint.cc',
+ 'layers/layer_position_constraint.h',
+ 'layers/nine_patch_layer.cc',
+ 'layers/nine_patch_layer.h',
+ 'layers/nine_patch_layer_impl.cc',
+ 'layers/nine_patch_layer_impl.h',
+ 'layers/paint_properties.h',
+ 'layers/picture_image_layer.cc',
+ 'layers/picture_image_layer.h',
+ 'layers/picture_image_layer_impl.cc',
+ 'layers/picture_image_layer_impl.h',
+ 'layers/picture_layer.cc',
+ 'layers/picture_layer.h',
+ 'layers/picture_layer_impl.cc',
+ 'layers/picture_layer_impl.h',
+ 'layers/quad_sink.h',
+ 'layers/render_pass_sink.h',
+ 'layers/render_surface.cc',
+ 'layers/render_surface.h',
+ 'layers/render_surface_impl.cc',
+ 'layers/render_surface_impl.h',
+ 'layers/scrollbar_layer.cc',
+ 'layers/scrollbar_layer.h',
+ 'layers/scrollbar_layer_impl.cc',
+ 'layers/scrollbar_layer_impl.h',
+ 'layers/solid_color_layer.cc',
+ 'layers/solid_color_layer.h',
+ 'layers/solid_color_layer_impl.cc',
+ 'layers/solid_color_layer_impl.h',
+ 'layers/texture_layer.cc',
+ 'layers/texture_layer.h',
+ 'layers/texture_layer_client.h',
+ 'layers/texture_layer_impl.cc',
+ 'layers/texture_layer_impl.h',
+ 'layers/tiled_layer.cc',
+ 'layers/tiled_layer.h',
+ 'layers/tiled_layer_impl.cc',
+ 'layers/tiled_layer_impl.h',
+ 'layers/video_frame_provider.h',
+ 'layers/video_frame_provider_client_impl.cc',
+ 'layers/video_frame_provider_client_impl.h',
+ 'layers/video_layer.cc',
+ 'layers/video_layer.h',
+ 'layers/video_layer_impl.cc',
+ 'layers/video_layer_impl.h',
+ 'output/begin_frame_args.cc',
+ 'output/begin_frame_args.h',
+ 'output/compositor_frame.cc',
+ 'output/compositor_frame.h',
+ 'output/compositor_frame_ack.cc',
+ 'output/compositor_frame_ack.h',
+ 'output/compositor_frame_metadata.cc',
+ 'output/compositor_frame_metadata.h',
+ 'output/context_provider.h',
+ 'output/copy_output_request.cc',
+ 'output/copy_output_request.h',
+ 'output/copy_output_result.cc',
+ 'output/copy_output_result.h',
+ 'output/delegated_frame_data.h',
+ 'output/delegated_frame_data.cc',
+ 'output/delegating_renderer.cc',
+ 'output/delegating_renderer.h',
+ 'output/direct_renderer.cc',
+ 'output/direct_renderer.h',
+ 'output/filter_operation.cc',
+ 'output/filter_operation.h',
+ 'output/filter_operations.cc',
+ 'output/filter_operations.h',
+ 'output/geometry_binding.cc',
+ 'output/geometry_binding.h',
+ 'output/gl_frame_data.h',
+ 'output/gl_frame_data.cc',
+ 'output/gl_renderer.cc',
+ 'output/gl_renderer.h',
+ 'output/gl_renderer_draw_cache.cc',
+ 'output/gl_renderer_draw_cache.h',
+ 'output/managed_memory_policy.cc',
+ 'output/managed_memory_policy.h',
+ 'output/output_surface.cc',
+ 'output/output_surface.h',
+ 'output/output_surface_client.h',
+ 'output/program_binding.cc',
+ 'output/program_binding.h',
+ 'output/render_surface_filters.cc',
+ 'output/render_surface_filters.h',
+ 'output/renderer.cc',
+ 'output/renderer.h',
+ 'output/shader.cc',
+ 'output/shader.h',
+ 'output/software_frame_data.cc',
+ 'output/software_frame_data.h',
+ 'output/software_output_device.cc',
+ 'output/software_output_device.h',
+ 'output/software_renderer.cc',
+ 'output/software_renderer.h',
+ 'quads/checkerboard_draw_quad.cc',
+ 'quads/checkerboard_draw_quad.h',
+ 'quads/content_draw_quad_base.cc',
+ 'quads/content_draw_quad_base.h',
+ 'quads/debug_border_draw_quad.cc',
+ 'quads/debug_border_draw_quad.h',
+ 'quads/draw_quad.cc',
+ 'quads/draw_quad.h',
+ 'quads/io_surface_draw_quad.cc',
+ 'quads/io_surface_draw_quad.h',
+ 'quads/picture_draw_quad.cc',
+ 'quads/picture_draw_quad.h',
+ 'quads/render_pass.cc',
+ 'quads/render_pass.h',
+ 'quads/render_pass_draw_quad.cc',
+ 'quads/render_pass_draw_quad.h',
+ 'quads/shared_quad_state.cc',
+ 'quads/shared_quad_state.h',
+ 'quads/solid_color_draw_quad.cc',
+ 'quads/solid_color_draw_quad.h',
+ 'quads/stream_video_draw_quad.cc',
+ 'quads/stream_video_draw_quad.h',
+ 'quads/texture_draw_quad.cc',
+ 'quads/texture_draw_quad.h',
+ 'quads/tile_draw_quad.cc',
+ 'quads/tile_draw_quad.h',
+ 'quads/yuv_video_draw_quad.cc',
+ 'quads/yuv_video_draw_quad.h',
+ 'resources/bitmap_content_layer_updater.cc',
+ 'resources/bitmap_content_layer_updater.h',
+ 'resources/bitmap_skpicture_content_layer_updater.cc',
+ 'resources/bitmap_skpicture_content_layer_updater.h',
+ 'resources/caching_bitmap_content_layer_updater.cc',
+ 'resources/caching_bitmap_content_layer_updater.h',
+ 'resources/content_layer_updater.cc',
+ 'resources/content_layer_updater.h',
+ 'resources/image_layer_updater.cc',
+ 'resources/image_layer_updater.h',
+ 'resources/image_raster_worker_pool.cc',
+ 'resources/image_raster_worker_pool.h',
+ 'resources/layer_painter.h',
+ 'resources/layer_quad.cc',
+ 'resources/layer_quad.h',
+ 'resources/layer_tiling_data.cc',
+ 'resources/layer_tiling_data.h',
+ 'resources/layer_updater.cc',
+ 'resources/layer_updater.h',
+ 'resources/managed_tile_state.cc',
+ 'resources/managed_tile_state.h',
+ 'resources/memory_history.cc',
+ 'resources/memory_history.h',
+ 'resources/picture.cc',
+ 'resources/picture.h',
+ 'resources/picture_layer_tiling.cc',
+ 'resources/picture_layer_tiling.h',
+ 'resources/picture_layer_tiling_set.cc',
+ 'resources/picture_layer_tiling_set.h',
+ 'resources/picture_pile.cc',
+ 'resources/picture_pile.h',
+ 'resources/picture_pile_base.cc',
+ 'resources/picture_pile_base.h',
+ 'resources/picture_pile_impl.cc',
+ 'resources/picture_pile_impl.h',
+ 'resources/pixel_buffer_raster_worker_pool.cc',
+ 'resources/pixel_buffer_raster_worker_pool.h',
+ 'resources/platform_color.h',
+ 'resources/prioritized_resource.cc',
+ 'resources/prioritized_resource.h',
+ 'resources/prioritized_resource_manager.cc',
+ 'resources/prioritized_resource_manager.h',
+ 'resources/prioritized_tile_set.cc',
+ 'resources/prioritized_tile_set.h',
+ 'resources/priority_calculator.cc',
+ 'resources/priority_calculator.h',
+ 'resources/raster_mode.cc',
+ 'resources/raster_mode.h',
+ 'resources/raster_worker_pool.cc',
+ 'resources/raster_worker_pool.h',
+ 'resources/resource.cc',
+ 'resources/resource.h',
+ 'resources/resource_pool.cc',
+ 'resources/resource_pool.h',
+ 'resources/resource_provider.cc',
+ 'resources/resource_provider.h',
+ 'resources/resource_update.cc',
+ 'resources/resource_update.h',
+ 'resources/resource_update_controller.cc',
+ 'resources/resource_update_controller.h',
+ 'resources/resource_update_queue.cc',
+ 'resources/resource_update_queue.h',
+ 'resources/scoped_resource.cc',
+ 'resources/scoped_resource.h',
+ 'resources/scoped_ui_resource.cc',
+ 'resources/scoped_ui_resource.h',
+ 'resources/skpicture_content_layer_updater.cc',
+ 'resources/skpicture_content_layer_updater.h',
+ 'resources/sync_point_helper.cc',
+ 'resources/sync_point_helper.h',
+ 'resources/texture_mailbox.cc',
+ 'resources/texture_mailbox.h',
+ 'resources/tile.cc',
+ 'resources/tile.h',
+ 'resources/tile_manager.cc',
+ 'resources/tile_manager.h',
+ 'resources/tile_priority.cc',
+ 'resources/tile_priority.h',
+ 'resources/transferable_resource.cc',
+ 'resources/transferable_resource.h',
+ 'resources/ui_resource_bitmap.cc',
+ 'resources/ui_resource_bitmap.h',
+ 'resources/ui_resource_client.h',
+ 'resources/video_resource_updater.cc',
+ 'resources/video_resource_updater.h',
+ 'resources/worker_pool.cc',
+ 'resources/worker_pool.h',
+ 'scheduler/delay_based_time_source.cc',
+ 'scheduler/delay_based_time_source.h',
+ 'scheduler/frame_rate_controller.cc',
+ 'scheduler/frame_rate_controller.h',
+ 'scheduler/rate_limiter.cc',
+ 'scheduler/rate_limiter.h',
+ 'scheduler/rolling_time_delta_history.cc',
+ 'scheduler/rolling_time_delta_history.h',
+ 'scheduler/scheduler.cc',
+ 'scheduler/scheduler.h',
+ 'scheduler/scheduler_settings.cc',
+ 'scheduler/scheduler_settings.h',
+ 'scheduler/scheduler_state_machine.cc',
+ 'scheduler/scheduler_state_machine.h',
+ 'scheduler/texture_uploader.cc',
+ 'scheduler/texture_uploader.h',
+ 'scheduler/time_source.h',
+ 'trees/damage_tracker.cc',
+ 'trees/damage_tracker.h',
+ 'trees/layer_sorter.cc',
+ 'trees/layer_sorter.h',
+ 'trees/layer_tree_host.cc',
+ 'trees/layer_tree_host.h',
+ 'trees/layer_tree_host_client.h',
+ 'trees/layer_tree_host_common.cc',
+ 'trees/layer_tree_host_common.h',
+ 'trees/layer_tree_host_impl.cc',
+ 'trees/layer_tree_host_impl.h',
+ 'trees/layer_tree_impl.cc',
+ 'trees/layer_tree_impl.h',
+ 'trees/layer_tree_settings.cc',
+ 'trees/layer_tree_settings.h',
+ 'trees/occlusion_tracker.cc',
+ 'trees/occlusion_tracker.h',
+ 'trees/proxy.cc',
+ 'trees/proxy.h',
+ 'trees/quad_culler.cc',
+ 'trees/quad_culler.h',
+ 'trees/single_thread_proxy.cc',
+ 'trees/single_thread_proxy.h',
+ 'trees/thread_proxy.cc',
+ 'trees/thread_proxy.h',
+ 'trees/tree_synchronizer.cc',
+ 'trees/tree_synchronizer.h',
+ ],
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [ 4267, ],
+ },
+ ],
+}
diff --git a/chromium/cc/cc_perftests.isolate b/chromium/cc/cc_perftests.isolate
new file mode 100644
index 00000000000..0aa38c0eb60
--- /dev/null
+++ b/chromium/cc/cc_perftests.isolate
@@ -0,0 +1,14 @@
+# Copyright 2013 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.
+{
+ 'conditions': [
+ ['OS=="android"', {
+ 'variables': {
+ 'isolate_dependency_untracked': [
+ 'test/data/',
+ ],
+ },
+ }],
+ ],
+}
diff --git a/chromium/cc/cc_tests.gyp b/chromium/cc/cc_tests.gyp
new file mode 100644
index 00000000000..4e4f4382e2c
--- /dev/null
+++ b/chromium/cc/cc_tests.gyp
@@ -0,0 +1,350 @@
+# Copyright (c) 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.
+
+{
+ 'variables': {
+ 'chromium_code': 1,
+ 'cc_unit_tests_source_files': [
+ 'animation/animation_unittest.cc',
+ 'animation/keyframed_animation_curve_unittest.cc',
+ 'animation/layer_animation_controller_unittest.cc',
+ 'animation/scrollbar_animation_controller_linear_fade_unittest.cc',
+ 'animation/timing_function_unittest.cc',
+ 'animation/transform_operations_unittest.cc',
+ 'base/float_quad_unittest.cc',
+ 'base/math_util_unittest.cc',
+ 'base/region_unittest.cc',
+ 'base/scoped_ptr_vector_unittest.cc',
+ 'base/tiling_data_unittest.cc',
+ 'base/util_unittest.cc',
+ 'input/top_controls_manager_unittest.cc',
+ 'layers/content_layer_unittest.cc',
+ 'layers/contents_scaling_layer_unittest.cc',
+ 'layers/delegated_renderer_layer_impl_unittest.cc',
+ 'layers/heads_up_display_unittest.cc',
+ 'layers/layer_impl_unittest.cc',
+ 'layers/layer_iterator_unittest.cc',
+ 'layers/layer_position_constraint_unittest.cc',
+ 'layers/layer_unittest.cc',
+ 'layers/nine_patch_layer_impl_unittest.cc',
+ 'layers/nine_patch_layer_unittest.cc',
+ 'layers/picture_image_layer_impl_unittest.cc',
+ 'layers/picture_layer_impl_unittest.cc',
+ 'layers/render_surface_unittest.cc',
+ 'layers/scrollbar_layer_unittest.cc',
+ 'layers/solid_color_layer_impl_unittest.cc',
+ 'layers/texture_layer_unittest.cc',
+ 'layers/tiled_layer_impl_unittest.cc',
+ 'layers/tiled_layer_unittest.cc',
+ 'output/delegating_renderer_unittest.cc',
+ 'output/filter_operations_unittest.cc',
+ 'output/gl_renderer_unittest.cc',
+ 'output/output_surface_unittest.cc',
+ 'output/renderer_pixeltest.cc',
+ 'output/render_surface_filters_unittest.cc',
+ 'output/shader_unittest.cc',
+ 'output/software_renderer_unittest.cc',
+ 'quads/draw_quad_unittest.cc',
+ 'quads/render_pass_unittest.cc',
+ 'resources/layer_quad_unittest.cc',
+ 'resources/picture_layer_tiling_set_unittest.cc',
+ 'resources/picture_layer_tiling_unittest.cc',
+ 'resources/picture_pile_impl_unittest.cc',
+ 'resources/picture_pile_unittest.cc',
+ 'resources/picture_unittest.cc',
+ 'resources/prioritized_resource_unittest.cc',
+ 'resources/prioritized_tile_set_unittest.cc',
+ 'resources/raster_worker_pool_unittest.cc',
+ 'resources/resource_provider_unittest.cc',
+ 'resources/resource_update_controller_unittest.cc',
+ 'resources/scoped_resource_unittest.cc',
+ 'resources/tile_manager_unittest.cc',
+ 'resources/tile_priority_unittest.cc',
+ 'resources/video_resource_updater_unittest.cc',
+ 'resources/worker_pool_unittest.cc',
+ 'scheduler/delay_based_time_source_unittest.cc',
+ 'scheduler/frame_rate_controller_unittest.cc',
+ 'scheduler/rolling_time_delta_history_unittest.cc',
+ 'scheduler/scheduler_state_machine_unittest.cc',
+ 'scheduler/scheduler_unittest.cc',
+ 'scheduler/texture_uploader_unittest.cc',
+ 'test/fake_web_graphics_context_3d_unittest.cc',
+ 'trees/damage_tracker_unittest.cc',
+ 'trees/layer_sorter_unittest.cc',
+ 'trees/layer_tree_host_common_unittest.cc',
+ 'trees/layer_tree_host_impl_unittest.cc',
+ 'trees/layer_tree_host_pixeltest_filters.cc',
+ 'trees/layer_tree_host_pixeltest_masks.cc',
+ 'trees/layer_tree_host_pixeltest_on_demand_raster.cc',
+ 'trees/layer_tree_host_pixeltest_readback.cc',
+ 'trees/layer_tree_host_unittest_animation.cc',
+ 'trees/layer_tree_host_unittest.cc',
+ 'trees/layer_tree_host_unittest_context.cc',
+ 'trees/layer_tree_host_unittest_damage.cc',
+ 'trees/layer_tree_host_unittest_delegated.cc',
+ 'trees/layer_tree_host_unittest_occlusion.cc',
+ 'trees/layer_tree_host_unittest_picture.cc',
+ 'trees/layer_tree_host_unittest_scroll.cc',
+ 'trees/layer_tree_host_unittest_video.cc',
+ 'trees/occlusion_tracker_unittest.cc',
+ 'trees/quad_culler_unittest.cc',
+ 'trees/tree_synchronizer_unittest.cc',
+ ],
+ 'cc_tests_support_files': [
+ 'test/animation_test_common.cc',
+ 'test/animation_test_common.h',
+ 'test/fake_content_layer.cc',
+ 'test/fake_content_layer.h',
+ 'test/fake_content_layer_client.cc',
+ 'test/fake_content_layer_client.h',
+ 'test/fake_content_layer_impl.cc',
+ 'test/fake_content_layer_impl.h',
+ 'test/fake_delegated_renderer_layer.cc',
+ 'test/fake_delegated_renderer_layer.h',
+ 'test/fake_delegated_renderer_layer_impl.cc',
+ 'test/fake_delegated_renderer_layer_impl.h',
+ 'test/fake_impl_proxy.h',
+ 'test/fake_layer_tree_host_client.cc',
+ 'test/fake_layer_tree_host_client.h',
+ 'test/fake_layer_tree_host_impl.cc',
+ 'test/fake_layer_tree_host_impl_client.cc',
+ 'test/fake_layer_tree_host_impl_client.h',
+ 'test/fake_layer_tree_host_impl.h',
+ 'test/fake_picture_layer.cc',
+ 'test/fake_picture_layer.h',
+ 'test/fake_picture_layer_impl.cc',
+ 'test/fake_picture_layer_impl.h',
+ 'test/fake_picture_layer_tiling_client.cc',
+ 'test/fake_picture_layer_tiling_client.h',
+ 'test/fake_picture_pile_impl.cc',
+ 'test/fake_picture_pile_impl.h',
+ 'test/fake_proxy.cc',
+ 'test/fake_proxy.h',
+ 'test/fake_rendering_stats_instrumentation.h',
+ 'test/fake_scrollbar.cc',
+ 'test/fake_scrollbar.h',
+ 'test/fake_scrollbar_layer.cc',
+ 'test/fake_scrollbar_layer.h',
+ 'test/fake_tile_manager.cc',
+ 'test/fake_tile_manager.h',
+ 'test/fake_tile_manager_client.h',
+ 'test/fake_tile_manager_client.cc',
+ 'test/fake_output_surface.cc',
+ 'test/fake_output_surface.h',
+ 'test/fake_output_surface_client.cc',
+ 'test/fake_output_surface_client.h',
+ 'test/fake_scoped_ui_resource.cc',
+ 'test/fake_scoped_ui_resource.h',
+ 'test/fake_video_frame_provider.cc',
+ 'test/fake_video_frame_provider.h',
+ 'test/geometry_test_utils.cc',
+ 'test/geometry_test_utils.h',
+ 'test/impl_side_painting_settings.h',
+ 'test/layer_test_common.cc',
+ 'test/layer_test_common.h',
+ 'test/layer_tree_pixel_test.cc',
+ 'test/layer_tree_pixel_test.h',
+ 'test/layer_tree_test.cc',
+ 'test/layer_tree_test.h',
+ 'test/layer_tree_json_parser.cc',
+ 'test/layer_tree_json_parser.h',
+ 'test/mock_quad_culler.cc',
+ 'test/mock_quad_culler.h',
+ 'test/occlusion_tracker_test_common.h',
+ 'test/paths.cc',
+ 'test/paths.h',
+ 'test/pixel_test.cc',
+ 'test/pixel_test.h',
+ 'test/pixel_test_output_surface.cc',
+ 'test/pixel_test_output_surface.h',
+ 'test/pixel_test_software_output_device.cc',
+ 'test/pixel_test_software_output_device.h',
+ 'test/render_pass_test_common.cc',
+ 'test/render_pass_test_common.h',
+ 'test/render_pass_test_utils.cc',
+ 'test/render_pass_test_utils.h',
+ 'test/scheduler_test_common.cc',
+ 'test/scheduler_test_common.h',
+ 'test/solid_color_content_layer_client.h',
+ 'test/solid_color_content_layer_client.cc',
+ 'test/skia_common.cc',
+ 'test/skia_common.h',
+ 'test/test_tile_priorities.cc',
+ 'test/test_tile_priorities.h',
+ 'test/tiled_layer_test_common.cc',
+ 'test/tiled_layer_test_common.h',
+ ],
+ },
+ 'targets': [
+ {
+ 'target_name': 'cc_unittests',
+ 'type': '<(gtest_target_type)',
+ 'dependencies': [
+ '../base/base.gyp:test_support_base',
+ '../gpu/gpu.gyp:gpu',
+ '../media/media.gyp:media',
+ '../skia/skia.gyp:skia',
+ '../testing/gmock.gyp:gmock',
+ '../testing/gtest.gyp:gtest',
+ '../ui/ui.gyp:ui',
+ '../webkit/common/gpu/webkit_gpu.gyp:webkit_gpu',
+ 'cc.gyp:cc',
+ 'cc_test_support',
+ 'cc_test_utils',
+ ],
+ 'sources': [
+ 'test/run_all_unittests.cc',
+ 'test/cc_test_suite.cc',
+ '<@(cc_unit_tests_source_files)',
+ ],
+ 'include_dirs': [
+ 'test',
+ '.',
+ ],
+ 'conditions': [
+ ['OS == "android" and gtest_target_type == "shared_library"',
+ {
+ 'dependencies': [
+ '../testing/android/native_test.gyp:native_test_native_code',
+ ],
+ }
+ ],
+ [ 'os_posix == 1 and OS != "mac" and OS != "android" and OS != "ios"',
+ {
+ 'conditions': [
+ [ 'linux_use_tcmalloc==1',
+ {
+ 'dependencies': [
+ '../base/allocator/allocator.gyp:allocator',
+ ],
+ }
+ ],
+ ],
+ }
+ ],
+ ],
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [ 4267, ],
+ },
+ {
+ 'target_name': 'cc_perftests',
+ 'type': '<(gtest_target_type)',
+ 'dependencies': [
+ '../base/base.gyp:test_support_base',
+ '../media/media.gyp:media',
+ '../skia/skia.gyp:skia',
+ '../testing/gmock.gyp:gmock',
+ '../testing/gtest.gyp:gtest',
+ '../ui/ui.gyp:ui',
+ 'cc.gyp:cc',
+ 'cc_test_support',
+ ],
+ 'sources': [
+ 'resources/raster_worker_pool_perftest.cc',
+ 'resources/tile_manager_perftest.cc',
+ 'resources/worker_pool_perftest.cc',
+ 'test/cc_test_suite.cc',
+ 'test/run_all_unittests.cc',
+ 'trees/layer_tree_host_perftest.cc',
+ ],
+ 'include_dirs': [
+ 'test',
+ '.',
+ ],
+ 'conditions': [
+ ['OS == "android" and gtest_target_type == "shared_library"',
+ {
+ 'dependencies': [
+ '../testing/android/native_test.gyp:native_test_native_code',
+ ],
+ }
+ ],
+ # See http://crbug.com/162998#c4 for why this is needed.
+ ['OS=="linux" and linux_use_tcmalloc==1',
+ {
+ 'dependencies': [
+ '../base/allocator/allocator.gyp:allocator',
+ ],
+ }
+ ],
+ ],
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [ 4267, ],
+ },
+ {
+ 'target_name': 'cc_test_support',
+ 'type': 'static_library',
+ 'include_dirs': [
+ 'test',
+ '.',
+ '..',
+ ],
+ 'dependencies': [
+ '../skia/skia.gyp:skia',
+ '../testing/gmock.gyp:gmock',
+ '../testing/gtest.gyp:gtest',
+ '../third_party/WebKit/public/blink.gyp:blink_minimal',
+ '../third_party/mesa/mesa.gyp:osmesa',
+ '../ui/gl/gl.gyp:gl',
+ '../ui/ui.gyp:ui',
+ ],
+ 'sources': [
+ '<@(cc_tests_support_files)',
+ ],
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [ 4267, ],
+ },
+ {
+ 'target_name': 'cc_test_utils',
+ 'type': 'static_library',
+ 'include_dirs': [
+ '..'
+ ],
+ 'sources': [
+ 'test/pixel_comparator.cc',
+ 'test/pixel_comparator.h',
+ 'test/pixel_test_utils.cc',
+ 'test/pixel_test_utils.h',
+ ],
+ 'dependencies': [
+ '../skia/skia.gyp:skia',
+ '../ui/ui.gyp:ui', # for png_codec
+ ],
+ },
+ ],
+ 'conditions': [
+ # Special target to wrap a gtest_target_type==shared_library
+ # cc_unittests into an android apk for execution.
+ ['OS == "android" and gtest_target_type == "shared_library"',
+ {
+ 'targets': [
+ {
+ 'target_name': 'cc_unittests_apk',
+ 'type': 'none',
+ 'dependencies': [
+ 'cc_unittests',
+ ],
+ 'variables': {
+ 'test_suite_name': 'cc_unittests',
+ 'input_shlib_path': '<(SHARED_LIB_DIR)/<(SHARED_LIB_PREFIX)cc_unittests<(SHARED_LIB_SUFFIX)',
+ },
+ 'includes': [ '../build/apk_test.gypi' ],
+ },
+ {
+ 'target_name': 'cc_perftests_apk',
+ 'type': 'none',
+ 'dependencies': [
+ 'cc_perftests',
+ ],
+ 'variables': {
+ 'test_suite_name': 'cc_perftests',
+ 'input_shlib_path': '<(SHARED_LIB_DIR)/<(SHARED_LIB_PREFIX)cc_perftests<(SHARED_LIB_SUFFIX)',
+ },
+ 'includes': [ '../build/apk_test.gypi' ],
+ },
+ ],
+ }
+ ]
+ ],
+}
diff --git a/chromium/cc/debug/OWNERS b/chromium/cc/debug/OWNERS
new file mode 100644
index 00000000000..a46edeee25d
--- /dev/null
+++ b/chromium/cc/debug/OWNERS
@@ -0,0 +1,4 @@
+# Changes to this file may break telemetry benchmarks
+per-file benchmark_instrumentation.h=set noparent
+per-file benchmark_instrumentation.h=ernstm@chromium.org
+per-file benchmark_instrumentation.h=nduca@chromium.org
diff --git a/chromium/cc/debug/benchmark_instrumentation.h b/chromium/cc/debug/benchmark_instrumentation.h
new file mode 100644
index 00000000000..615fbe90622
--- /dev/null
+++ b/chromium/cc/debug/benchmark_instrumentation.h
@@ -0,0 +1,32 @@
+// Copyright 2013 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.
+
+#ifndef CC_DEBUG_BENCHMARK_INSTRUMENTATION_H_
+#define CC_DEBUG_BENCHMARK_INSTRUMENTATION_H_
+
+#include "base/debug/trace_event.h"
+
+namespace cc {
+namespace benchmark_instrumentation {
+// Please do not change the string constants in this file (or the TRACE_EVENT
+// calls that use them) without updating
+// tools/perf/measurements/rasterize_and_record_benchmark.py accordingly.
+// The benchmark searches for events and their arguments by name.
+const char kCategory[] = "cc,benchmark";
+const char kSourceFrameNumber[] = "source_frame_number";
+const char kData[] = "data";
+const char kWidth[] = "width";
+const char kHeight[] = "height";
+const char kNumPixelsRasterized[] = "num_pixels_rasterized";
+const char kLayerTreeHostUpdateLayers[] = "LayerTreeHost::UpdateLayers";
+const char kPictureLayerUpdate[] = "PictureLayer::Update";
+const char kRunRasterOnThread[] = "RasterWorkerPoolTaskImpl::RunRasterOnThread";
+const char kRecordLoop[] = "RecordLoop";
+const char kRasterLoop[] = "RasterLoop";
+const char kPictureRecord[] = "Picture::Record";
+const char kPictureRaster[] = "Picture::Raster";
+} // namespace benchmark_instrumentation
+} // namespace cc
+
+#endif // CC_DEBUG_BENCHMARK_INSTRUMENTATION_H_
diff --git a/chromium/cc/debug/debug_colors.cc b/chromium/cc/debug/debug_colors.cc
new file mode 100644
index 00000000000..e6f3e28a238
--- /dev/null
+++ b/chromium/cc/debug/debug_colors.cc
@@ -0,0 +1,290 @@
+// 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 "cc/debug/debug_colors.h"
+
+#include "cc/trees/layer_tree_impl.h"
+
+namespace cc {
+
+static float Scale(float width, const LayerTreeImpl* tree_impl) {
+ return width * (tree_impl ? tree_impl->device_scale_factor() : 1);
+}
+
+// ======= Layer border colors =======
+
+// Tiled content layers are orange.
+SkColor DebugColors::TiledContentLayerBorderColor() {
+ return SkColorSetARGB(128, 255, 128, 0);
+}
+int DebugColors::TiledContentLayerBorderWidth(const LayerTreeImpl* tree_impl) {
+ return Scale(2, tree_impl);
+}
+
+// Image layers are olive.
+SkColor DebugColors::ImageLayerBorderColor() {
+ return SkColorSetARGB(128, 128, 128, 0);
+}
+int DebugColors::ImageLayerBorderWidth(const LayerTreeImpl* tree_impl) {
+ return Scale(2, tree_impl);
+}
+
+// Non-tiled content layers area green.
+SkColor DebugColors::ContentLayerBorderColor() {
+ return SkColorSetARGB(128, 0, 128, 32);
+}
+int DebugColors::ContentLayerBorderWidth(const LayerTreeImpl* tree_impl) {
+ return Scale(2, tree_impl);
+}
+
+// Masking layers are pale blue and wide.
+SkColor DebugColors::MaskingLayerBorderColor() {
+ return SkColorSetARGB(48, 128, 255, 255);
+}
+int DebugColors::MaskingLayerBorderWidth(const LayerTreeImpl* tree_impl) {
+ return Scale(20, tree_impl);
+}
+
+// Other container layers are yellow.
+SkColor DebugColors::ContainerLayerBorderColor() {
+ return SkColorSetARGB(192, 255, 255, 0);
+}
+int DebugColors::ContainerLayerBorderWidth(const LayerTreeImpl* tree_impl) {
+ return Scale(2, tree_impl);
+}
+
+// Render surfaces are blue.
+SkColor DebugColors::SurfaceBorderColor() {
+ return SkColorSetARGB(100, 0, 0, 255);
+}
+int DebugColors::SurfaceBorderWidth(const LayerTreeImpl* tree_impl) {
+ return Scale(2, tree_impl);
+}
+
+// Replicas of render surfaces are purple.
+SkColor DebugColors::SurfaceReplicaBorderColor() {
+ return SkColorSetARGB(100, 160, 0, 255);
+}
+int DebugColors::SurfaceReplicaBorderWidth(const LayerTreeImpl* tree_impl) {
+ return Scale(2, tree_impl);
+}
+
+// ======= Tile colors =======
+
+// High-res tile borders are cyan.
+SkColor DebugColors::HighResTileBorderColor() {
+ return SkColorSetARGB(100, 80, 200, 200);
+}
+int DebugColors::HighResTileBorderWidth(const LayerTreeImpl* tree_impl) {
+ return Scale(1, tree_impl);
+}
+
+// Low-res tile borders are purple.
+SkColor DebugColors::LowResTileBorderColor() {
+ return SkColorSetARGB(100, 212, 83, 192);
+}
+int DebugColors::LowResTileBorderWidth(const LayerTreeImpl* tree_impl) {
+ return Scale(2, tree_impl);
+}
+
+// Other high-resolution tile borders are yellow.
+SkColor DebugColors::ExtraHighResTileBorderColor() {
+ return SkColorSetARGB(100, 239, 231, 20);
+}
+int DebugColors::ExtraHighResTileBorderWidth(const LayerTreeImpl* tree_impl) {
+ return Scale(2, tree_impl);
+}
+
+// Other low-resolution tile borders are green.
+SkColor DebugColors::ExtraLowResTileBorderColor() {
+ return SkColorSetARGB(100, 93, 186, 18);
+}
+int DebugColors::ExtraLowResTileBorderWidth(const LayerTreeImpl* tree_impl) {
+ return Scale(2, tree_impl);
+}
+
+// Missing tile borders are red.
+SkColor DebugColors::MissingTileBorderColor() {
+ return SkColorSetARGB(100, 255, 0, 0);
+}
+int DebugColors::MissingTileBorderWidth(const LayerTreeImpl* tree_impl) {
+ return Scale(1, tree_impl);
+}
+
+// Culled tile borders are brown.
+SkColor DebugColors::CulledTileBorderColor() {
+ return SkColorSetARGB(120, 160, 100, 0);
+}
+int DebugColors::CulledTileBorderWidth(const LayerTreeImpl* tree_impl) {
+ return Scale(1, tree_impl);
+}
+
+// Solid color tile borders are grey.
+SkColor DebugColors::SolidColorTileBorderColor() {
+ return SkColorSetARGB(128, 128, 128, 128);
+}
+int DebugColors::SolidColorTileBorderWidth(const LayerTreeImpl* tree_impl) {
+ return Scale(1, tree_impl);
+}
+
+// Picture tile borders are dark grey.
+SkColor DebugColors::PictureTileBorderColor() {
+ return SkColorSetARGB(64, 64, 64, 0);
+}
+int DebugColors::PictureTileBorderWidth(const LayerTreeImpl* tree_impl) {
+ return Scale(1, tree_impl);
+}
+
+// Direct picture borders are chartreuse.
+SkColor DebugColors::DirectPictureBorderColor() {
+ return SkColorSetARGB(255, 127, 255, 0);
+}
+int DebugColors::DirectPictureBorderWidth(const LayerTreeImpl* tree_impl) {
+ return Scale(1, tree_impl);
+}
+
+// ======= Checkerboard colors =======
+
+// Non-debug checkerboards are grey.
+SkColor DebugColors::DefaultCheckerboardColor() {
+ return SkColorSetRGB(241, 241, 241);
+}
+
+// Invalidated tiles get sky blue checkerboards.
+SkColor DebugColors::InvalidatedTileCheckerboardColor() {
+ return SkColorSetRGB(128, 200, 245);
+}
+
+// Evicted tiles get pale red checkerboards.
+SkColor DebugColors::EvictedTileCheckerboardColor() {
+ return SkColorSetRGB(255, 200, 200);
+}
+
+// ======= Debug rect colors =======
+
+// Paint rects in red.
+SkColor DebugColors::PaintRectBorderColor() {
+ return SkColorSetARGB(255, 255, 0, 0);
+}
+int DebugColors::PaintRectBorderWidth() { return 2; }
+SkColor DebugColors::PaintRectFillColor() {
+ return SkColorSetARGB(30, 255, 0, 0);
+}
+
+// Property-changed rects in blue.
+SkColor DebugColors::PropertyChangedRectBorderColor() {
+ return SkColorSetARGB(255, 0, 0, 255);
+}
+int DebugColors::PropertyChangedRectBorderWidth() { return 2; }
+SkColor DebugColors::PropertyChangedRectFillColor() {
+ return SkColorSetARGB(30, 0, 0, 255);
+}
+
+// Surface damage rects in yellow-orange.
+SkColor DebugColors::SurfaceDamageRectBorderColor() {
+ return SkColorSetARGB(255, 200, 100, 0);
+}
+int DebugColors::SurfaceDamageRectBorderWidth() { return 2; }
+SkColor DebugColors::SurfaceDamageRectFillColor() {
+ return SkColorSetARGB(30, 200, 100, 0);
+}
+
+// Surface replica screen space rects in green.
+SkColor DebugColors::ScreenSpaceLayerRectBorderColor() {
+ return SkColorSetARGB(255, 100, 200, 0);
+}
+int DebugColors::ScreenSpaceLayerRectBorderWidth() { return 2; }
+SkColor DebugColors::ScreenSpaceLayerRectFillColor() {
+ return SkColorSetARGB(30, 100, 200, 0);
+}
+
+// Layer screen space rects in purple.
+SkColor DebugColors::ScreenSpaceSurfaceReplicaRectBorderColor() {
+ return SkColorSetARGB(255, 100, 0, 200);
+}
+int DebugColors::ScreenSpaceSurfaceReplicaRectBorderWidth() { return 2; }
+SkColor DebugColors::ScreenSpaceSurfaceReplicaRectFillColor() {
+ return SkColorSetARGB(10, 100, 0, 200);
+}
+
+// Occluding rects in pink.
+SkColor DebugColors::OccludingRectBorderColor() {
+ return SkColorSetARGB(255, 245, 136, 255);
+}
+int DebugColors::OccludingRectBorderWidth() { return 2; }
+SkColor DebugColors::OccludingRectFillColor() {
+ return SkColorSetARGB(10, 245, 136, 255);
+}
+
+// Non-Occluding rects in a reddish color.
+SkColor DebugColors::NonOccludingRectBorderColor() {
+ return SkColorSetARGB(255, 200, 0, 100);
+}
+int DebugColors::NonOccludingRectBorderWidth() { return 2; }
+SkColor DebugColors::NonOccludingRectFillColor() {
+ return SkColorSetARGB(10, 200, 0, 100);
+}
+
+// Touch-event-handler rects in yellow.
+SkColor DebugColors::TouchEventHandlerRectBorderColor() {
+ return SkColorSetARGB(255, 239, 229, 60);
+}
+int DebugColors::TouchEventHandlerRectBorderWidth() { return 2; }
+SkColor DebugColors::TouchEventHandlerRectFillColor() {
+ return SkColorSetARGB(30, 239, 229, 60);
+}
+
+// Wheel-event-handler rects in green.
+SkColor DebugColors::WheelEventHandlerRectBorderColor() {
+ return SkColorSetARGB(255, 189, 209, 57);
+}
+int DebugColors::WheelEventHandlerRectBorderWidth() { return 2; }
+SkColor DebugColors::WheelEventHandlerRectFillColor() {
+ return SkColorSetARGB(30, 189, 209, 57);
+}
+
+// Non-fast-scrollable rects in orange.
+SkColor DebugColors::NonFastScrollableRectBorderColor() {
+ return SkColorSetARGB(255, 238, 163, 59);
+}
+int DebugColors::NonFastScrollableRectBorderWidth() { return 2; }
+SkColor DebugColors::NonFastScrollableRectFillColor() {
+ return SkColorSetARGB(30, 238, 163, 59);
+}
+
+// Non-Painted rects in cyan.
+SkColor DebugColors::NonPaintedFillColor() { return SK_ColorCYAN; }
+
+// Missing picture rects in magenta.
+SkColor DebugColors::MissingPictureFillColor() { return SK_ColorMAGENTA; }
+
+// Picture borders in transparent blue.
+SkColor DebugColors::PictureBorderColor() {
+ return SkColorSetARGB(100, 0, 0, 200);
+}
+
+// ======= HUD widget colors =======
+
+SkColor DebugColors::HUDBackgroundColor() {
+ return SkColorSetARGB(215, 17, 17, 17);
+}
+SkColor DebugColors::HUDSeparatorLineColor() {
+ return SkColorSetARGB(255, 130, 130, 130);
+}
+SkColor DebugColors::HUDIndicatorLineColor() {
+ return SkColorSetARGB(255, 80, 80, 80);
+}
+
+SkColor DebugColors::PlatformLayerTreeTextColor() { return SK_ColorRED; }
+SkColor DebugColors::FPSDisplayTextAndGraphColor() { return SK_ColorRED; }
+SkColor DebugColors::MemoryDisplayTextColor() {
+ return SkColorSetARGB(255, 220, 220, 220);
+}
+
+// Paint time display in green (similar to paint times in the WebInspector)
+SkColor DebugColors::PaintTimeDisplayTextAndGraphColor() {
+ return SkColorSetRGB(75, 155, 55);
+}
+
+} // namespace cc
diff --git a/chromium/cc/debug/debug_colors.h b/chromium/cc/debug/debug_colors.h
new file mode 100644
index 00000000000..a1335e37e96
--- /dev/null
+++ b/chromium/cc/debug/debug_colors.h
@@ -0,0 +1,128 @@
+// 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.
+
+#ifndef CC_DEBUG_DEBUG_COLORS_H_
+#define CC_DEBUG_DEBUG_COLORS_H_
+
+#include "base/basictypes.h"
+#include "third_party/skia/include/core/SkColor.h"
+
+namespace cc {
+
+class LayerTreeImpl;
+
+class DebugColors {
+ public:
+ static SkColor TiledContentLayerBorderColor();
+ static int TiledContentLayerBorderWidth(const LayerTreeImpl* tree_impl);
+
+ static SkColor ImageLayerBorderColor();
+ static int ImageLayerBorderWidth(const LayerTreeImpl* tree_impl);
+
+ static SkColor ContentLayerBorderColor();
+ static int ContentLayerBorderWidth(const LayerTreeImpl* tree_impl);
+
+ static SkColor MaskingLayerBorderColor();
+ static int MaskingLayerBorderWidth(const LayerTreeImpl* tree_impl);
+
+ static SkColor ContainerLayerBorderColor();
+ static int ContainerLayerBorderWidth(const LayerTreeImpl* tree_impl);
+
+ static SkColor SurfaceBorderColor();
+ static int SurfaceBorderWidth(const LayerTreeImpl* tree_impl);
+
+ static SkColor SurfaceReplicaBorderColor();
+ static int SurfaceReplicaBorderWidth(const LayerTreeImpl* tree_impl);
+
+ static SkColor HighResTileBorderColor();
+ static int HighResTileBorderWidth(const LayerTreeImpl* tree_impl);
+
+ static SkColor LowResTileBorderColor();
+ static int LowResTileBorderWidth(const LayerTreeImpl* tree_impl);
+
+ static SkColor ExtraHighResTileBorderColor();
+ static int ExtraHighResTileBorderWidth(const LayerTreeImpl* tree_impl);
+
+ static SkColor ExtraLowResTileBorderColor();
+ static int ExtraLowResTileBorderWidth(const LayerTreeImpl* tree_impl);
+
+ static SkColor MissingTileBorderColor();
+ static int MissingTileBorderWidth(const LayerTreeImpl* tree_impl);
+
+ static SkColor CulledTileBorderColor();
+ static int CulledTileBorderWidth(const LayerTreeImpl* tree_impl);
+
+ static SkColor SolidColorTileBorderColor();
+ static int SolidColorTileBorderWidth(const LayerTreeImpl* tree_impl);
+
+ static SkColor PictureTileBorderColor();
+ static int PictureTileBorderWidth(const LayerTreeImpl* tree_impl);
+
+ static SkColor DirectPictureBorderColor();
+ static int DirectPictureBorderWidth(const LayerTreeImpl* tree_impl);
+
+ static SkColor DefaultCheckerboardColor();
+ static SkColor EvictedTileCheckerboardColor();
+ static SkColor InvalidatedTileCheckerboardColor();
+
+ static SkColor PaintRectBorderColor();
+ static int PaintRectBorderWidth();
+ static SkColor PaintRectFillColor();
+
+ static SkColor PropertyChangedRectBorderColor();
+ static int PropertyChangedRectBorderWidth();
+ static SkColor PropertyChangedRectFillColor();
+
+ static SkColor SurfaceDamageRectBorderColor();
+ static int SurfaceDamageRectBorderWidth();
+ static SkColor SurfaceDamageRectFillColor();
+
+ static SkColor ScreenSpaceLayerRectBorderColor();
+ static int ScreenSpaceLayerRectBorderWidth();
+ static SkColor ScreenSpaceLayerRectFillColor();
+
+ static SkColor ScreenSpaceSurfaceReplicaRectBorderColor();
+ static int ScreenSpaceSurfaceReplicaRectBorderWidth();
+ static SkColor ScreenSpaceSurfaceReplicaRectFillColor();
+
+ static SkColor OccludingRectBorderColor();
+ static int OccludingRectBorderWidth();
+ static SkColor OccludingRectFillColor();
+
+ static SkColor NonOccludingRectBorderColor();
+ static int NonOccludingRectBorderWidth();
+ static SkColor NonOccludingRectFillColor();
+
+ static SkColor TouchEventHandlerRectBorderColor();
+ static int TouchEventHandlerRectBorderWidth();
+ static SkColor TouchEventHandlerRectFillColor();
+
+ static SkColor WheelEventHandlerRectBorderColor();
+ static int WheelEventHandlerRectBorderWidth();
+ static SkColor WheelEventHandlerRectFillColor();
+
+ static SkColor NonFastScrollableRectBorderColor();
+ static int NonFastScrollableRectBorderWidth();
+ static SkColor NonFastScrollableRectFillColor();
+
+ static SkColor NonPaintedFillColor();
+ static SkColor MissingPictureFillColor();
+ static SkColor PictureBorderColor();
+
+ static SkColor HUDBackgroundColor();
+ static SkColor HUDSeparatorLineColor();
+ static SkColor HUDIndicatorLineColor();
+
+ static SkColor PlatformLayerTreeTextColor();
+ static SkColor FPSDisplayTextAndGraphColor();
+ static SkColor MemoryDisplayTextColor();
+ static SkColor PaintTimeDisplayTextAndGraphColor();
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(DebugColors);
+};
+
+} // namespace cc
+
+#endif // CC_DEBUG_DEBUG_COLORS_H_
diff --git a/chromium/cc/debug/debug_rect_history.cc b/chromium/cc/debug/debug_rect_history.cc
new file mode 100644
index 00000000000..24ba86eae59
--- /dev/null
+++ b/chromium/cc/debug/debug_rect_history.cc
@@ -0,0 +1,236 @@
+// 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 "cc/debug/debug_rect_history.h"
+
+#include "cc/base/math_util.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/layers/render_surface_impl.h"
+#include "cc/trees/damage_tracker.h"
+#include "cc/trees/layer_tree_host.h"
+#include "cc/trees/layer_tree_host_common.h"
+
+namespace cc {
+
+// static
+scoped_ptr<DebugRectHistory> DebugRectHistory::Create() {
+ return make_scoped_ptr(new DebugRectHistory());
+}
+
+DebugRectHistory::DebugRectHistory() {}
+
+DebugRectHistory::~DebugRectHistory() {}
+
+void DebugRectHistory::SaveDebugRectsForCurrentFrame(
+ LayerImpl* root_layer,
+ const LayerImplList& render_surface_layer_list,
+ const std::vector<gfx::Rect>& occluding_screen_space_rects,
+ const std::vector<gfx::Rect>& non_occluding_screen_space_rects,
+ const LayerTreeDebugState& debug_state) {
+ // For now, clear all rects from previous frames. In the future we may want to
+ // store all debug rects for a history of many frames.
+ debug_rects_.clear();
+
+ if (debug_state.show_touch_event_handler_rects)
+ SaveTouchEventHandlerRects(root_layer);
+
+ if (debug_state.show_wheel_event_handler_rects)
+ SaveWheelEventHandlerRects(root_layer);
+
+ if (debug_state.show_non_fast_scrollable_rects)
+ SaveNonFastScrollableRects(root_layer);
+
+ if (debug_state.show_paint_rects)
+ SavePaintRects(root_layer);
+
+ if (debug_state.show_property_changed_rects)
+ SavePropertyChangedRects(render_surface_layer_list);
+
+ if (debug_state.show_surface_damage_rects)
+ SaveSurfaceDamageRects(render_surface_layer_list);
+
+ if (debug_state.show_screen_space_rects)
+ SaveScreenSpaceRects(render_surface_layer_list);
+
+ if (debug_state.show_occluding_rects)
+ SaveOccludingRects(occluding_screen_space_rects);
+
+ if (debug_state.show_non_occluding_rects)
+ SaveNonOccludingRects(non_occluding_screen_space_rects);
+}
+
+void DebugRectHistory::SavePaintRects(LayerImpl* layer) {
+ // We would like to visualize where any layer's paint rect (update rect) has
+ // changed, regardless of whether this layer is skipped for actual drawing or
+ // not. Therefore we traverse recursively over all layers, not just the render
+ // surface list.
+
+ if (!layer->update_rect().IsEmpty() && layer->DrawsContent()) {
+ float width_scale = layer->content_bounds().width() /
+ static_cast<float>(layer->bounds().width());
+ float height_scale = layer->content_bounds().height() /
+ static_cast<float>(layer->bounds().height());
+ gfx::RectF update_content_rect =
+ gfx::ScaleRect(layer->update_rect(), width_scale, height_scale);
+ debug_rects_.push_back(
+ DebugRect(PAINT_RECT_TYPE,
+ MathUtil::MapClippedRect(layer->screen_space_transform(),
+ update_content_rect)));
+ }
+
+ for (unsigned i = 0; i < layer->children().size(); ++i)
+ SavePaintRects(layer->children()[i]);
+}
+
+void DebugRectHistory::SavePropertyChangedRects(
+ const LayerImplList& render_surface_layer_list) {
+ for (int surface_index = render_surface_layer_list.size() - 1;
+ surface_index >= 0;
+ --surface_index) {
+ LayerImpl* render_surface_layer = render_surface_layer_list[surface_index];
+ RenderSurfaceImpl* render_surface = render_surface_layer->render_surface();
+ DCHECK(render_surface);
+
+ const LayerImplList& layer_list = render_surface->layer_list();
+ for (unsigned layer_index = 0;
+ layer_index < layer_list.size();
+ ++layer_index) {
+ LayerImpl* layer = layer_list[layer_index];
+
+ if (LayerTreeHostCommon::RenderSurfaceContributesToTarget<LayerImpl>(
+ layer, render_surface_layer->id()))
+ continue;
+
+ if (layer->LayerIsAlwaysDamaged())
+ continue;
+
+ if (layer->LayerPropertyChanged() ||
+ layer->LayerSurfacePropertyChanged()) {
+ debug_rects_.push_back(
+ DebugRect(PROPERTY_CHANGED_RECT_TYPE,
+ MathUtil::MapClippedRect(
+ layer->screen_space_transform(),
+ gfx::RectF(gfx::PointF(), layer->content_bounds()))));
+ }
+ }
+ }
+}
+
+void DebugRectHistory::SaveSurfaceDamageRects(
+ const LayerImplList& render_surface_layer_list) {
+ for (int surface_index = render_surface_layer_list.size() - 1;
+ surface_index >= 0;
+ --surface_index) {
+ LayerImpl* render_surface_layer = render_surface_layer_list[surface_index];
+ RenderSurfaceImpl* render_surface = render_surface_layer->render_surface();
+ DCHECK(render_surface);
+
+ debug_rects_.push_back(DebugRect(
+ SURFACE_DAMAGE_RECT_TYPE,
+ MathUtil::MapClippedRect(
+ render_surface->screen_space_transform(),
+ render_surface->damage_tracker()->current_damage_rect())));
+ }
+}
+
+void DebugRectHistory::SaveScreenSpaceRects(
+ const LayerImplList& render_surface_layer_list) {
+ for (int surface_index = render_surface_layer_list.size() - 1;
+ surface_index >= 0;
+ --surface_index) {
+ LayerImpl* render_surface_layer = render_surface_layer_list[surface_index];
+ RenderSurfaceImpl* render_surface = render_surface_layer->render_surface();
+ DCHECK(render_surface);
+
+ debug_rects_.push_back(DebugRect(
+ SCREEN_SPACE_RECT_TYPE,
+ MathUtil::MapClippedRect(render_surface->screen_space_transform(),
+ render_surface->content_rect())));
+
+ if (render_surface_layer->replica_layer()) {
+ debug_rects_.push_back(
+ DebugRect(REPLICA_SCREEN_SPACE_RECT_TYPE,
+ MathUtil::MapClippedRect(
+ render_surface->replica_screen_space_transform(),
+ render_surface->content_rect())));
+ }
+ }
+}
+
+void DebugRectHistory::SaveOccludingRects(
+ const std::vector<gfx::Rect>& occluding_rects) {
+ for (size_t i = 0; i < occluding_rects.size(); ++i)
+ debug_rects_.push_back(DebugRect(OCCLUDING_RECT_TYPE, occluding_rects[i]));
+}
+
+void DebugRectHistory::SaveNonOccludingRects(
+ const std::vector<gfx::Rect>& non_occluding_rects) {
+ for (size_t i = 0; i < non_occluding_rects.size(); ++i) {
+ debug_rects_.push_back(
+ DebugRect(NONOCCLUDING_RECT_TYPE, non_occluding_rects[i]));
+ }
+}
+
+void DebugRectHistory::SaveTouchEventHandlerRects(LayerImpl* layer) {
+ LayerTreeHostCommon::CallFunctionForSubtree<LayerImpl>(
+ layer,
+ base::Bind(&DebugRectHistory::SaveTouchEventHandlerRectsCallback,
+ base::Unretained(this)));
+}
+
+void DebugRectHistory::SaveTouchEventHandlerRectsCallback(LayerImpl* layer) {
+ for (Region::Iterator iter(layer->touch_event_handler_region());
+ iter.has_rect();
+ iter.next()) {
+ gfx::RectF touch_rect = gfx::ScaleRect(iter.rect(),
+ layer->contents_scale_x(),
+ layer->contents_scale_y());
+ debug_rects_.push_back(DebugRect(TOUCH_EVENT_HANDLER_RECT_TYPE,
+ MathUtil::MapClippedRect(
+ layer->screen_space_transform(),
+ touch_rect)));
+ }
+}
+
+void DebugRectHistory::SaveWheelEventHandlerRects(LayerImpl* layer) {
+ LayerTreeHostCommon::CallFunctionForSubtree<LayerImpl>(
+ layer,
+ base::Bind(&DebugRectHistory::SaveWheelEventHandlerRectsCallback,
+ base::Unretained(this)));
+}
+
+void DebugRectHistory::SaveWheelEventHandlerRectsCallback(LayerImpl* layer) {
+ if (!layer->have_wheel_event_handlers())
+ return;
+
+ gfx::RectF wheel_rect = gfx::RectF(layer->content_bounds());
+ wheel_rect.Scale(layer->contents_scale_x(), layer->contents_scale_y());
+ debug_rects_.push_back(DebugRect(WHEEL_EVENT_HANDLER_RECT_TYPE,
+ MathUtil::MapClippedRect(
+ layer->screen_space_transform(),
+ wheel_rect)));
+}
+
+void DebugRectHistory::SaveNonFastScrollableRects(LayerImpl* layer) {
+ LayerTreeHostCommon::CallFunctionForSubtree<LayerImpl>(
+ layer,
+ base::Bind(&DebugRectHistory::SaveNonFastScrollableRectsCallback,
+ base::Unretained(this)));
+}
+
+void DebugRectHistory::SaveNonFastScrollableRectsCallback(LayerImpl* layer) {
+ for (Region::Iterator iter(layer->non_fast_scrollable_region());
+ iter.has_rect();
+ iter.next()) {
+ gfx::RectF scroll_rect = gfx::ScaleRect(iter.rect(),
+ layer->contents_scale_x(),
+ layer->contents_scale_y());
+ debug_rects_.push_back(DebugRect(NON_FAST_SCROLLABLE_RECT_TYPE,
+ MathUtil::MapClippedRect(
+ layer->screen_space_transform(),
+ scroll_rect)));
+ }
+}
+
+} // namespace cc
diff --git a/chromium/cc/debug/debug_rect_history.h b/chromium/cc/debug/debug_rect_history.h
new file mode 100644
index 00000000000..346148a93fc
--- /dev/null
+++ b/chromium/cc/debug/debug_rect_history.h
@@ -0,0 +1,114 @@
+// 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.
+
+#ifndef CC_DEBUG_DEBUG_RECT_HISTORY_H_
+#define CC_DEBUG_DEBUG_RECT_HISTORY_H_
+
+#include <vector>
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/layers/layer_lists.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/rect_f.h"
+
+namespace cc {
+
+class LayerImpl;
+class LayerTreeDebugState;
+
+// There are currently six types of debug rects:
+//
+// - Paint rects (update rects): regions of a layer that needed to be
+// re-uploaded to the texture resource; in most cases implying that they had to
+// be repainted, too.
+//
+// - Property-changed rects: enclosing bounds of layers that cause changes to
+// the screen even if the layer did not change internally. (For example, if the
+// layer's opacity or position changes.)
+//
+// - Surface damage rects: the aggregate damage on a target surface that is
+// caused by all layers and surfaces that contribute to it. This includes (1)
+// paint rects, (2) property- changed rects, and (3) newly exposed areas.
+//
+// - Screen space rects: this is the region the contents occupy in screen space.
+//
+// - Replica screen space rects: this is the region the replica's contents
+// occupy in screen space.
+//
+// - Occluding rects: these are the regions that contribute to the occluded
+// region.
+//
+// - Non-Occluding rects: these are the regions of composited layers that do not
+// contribute to the occluded region.
+//
+enum DebugRectType {
+ PAINT_RECT_TYPE,
+ PROPERTY_CHANGED_RECT_TYPE,
+ SURFACE_DAMAGE_RECT_TYPE,
+ SCREEN_SPACE_RECT_TYPE,
+ REPLICA_SCREEN_SPACE_RECT_TYPE,
+ OCCLUDING_RECT_TYPE,
+ NONOCCLUDING_RECT_TYPE,
+ TOUCH_EVENT_HANDLER_RECT_TYPE,
+ WHEEL_EVENT_HANDLER_RECT_TYPE,
+ NON_FAST_SCROLLABLE_RECT_TYPE,
+};
+
+struct DebugRect {
+ DebugRect(DebugRectType new_type, gfx::RectF new_rect)
+ : type(new_type), rect(new_rect) {}
+
+ DebugRectType type;
+ gfx::RectF rect;
+};
+
+// This class maintains a history of rects of various types that can be used
+// for debugging purposes. The overhead of collecting rects is performed only if
+// the appropriate LayerTreeSettings are enabled.
+class DebugRectHistory {
+ public:
+ static scoped_ptr<DebugRectHistory> Create();
+
+ ~DebugRectHistory();
+
+ // Note: Saving debug rects must happen before layers' change tracking is
+ // reset.
+ void SaveDebugRectsForCurrentFrame(
+ LayerImpl* root_layer,
+ const LayerImplList& render_surface_layer_list,
+ const std::vector<gfx::Rect>& occluding_screen_space_rects,
+ const std::vector<gfx::Rect>& non_occluding_screen_space_rects,
+ const LayerTreeDebugState& debug_state);
+
+ const std::vector<DebugRect>& debug_rects() { return debug_rects_; }
+
+ private:
+ DebugRectHistory();
+
+ void SavePaintRects(LayerImpl* layer);
+ void SavePropertyChangedRects(
+ const LayerImplList& render_surface_layer_list);
+ void SaveSurfaceDamageRects(
+ const LayerImplList& render_surface_layer_list);
+ void SaveScreenSpaceRects(
+ const LayerImplList& render_surface_layer_list);
+ void SaveOccludingRects(
+ const std::vector<gfx::Rect>& occluding_screen_space_rects);
+ void SaveNonOccludingRects(
+ const std::vector<gfx::Rect>& non_occluding_screen_space_rects);
+ void SaveTouchEventHandlerRects(LayerImpl* layer);
+ void SaveTouchEventHandlerRectsCallback(LayerImpl* layer);
+ void SaveWheelEventHandlerRects(LayerImpl* layer);
+ void SaveWheelEventHandlerRectsCallback(LayerImpl* layer);
+ void SaveNonFastScrollableRects(LayerImpl* layer);
+ void SaveNonFastScrollableRectsCallback(LayerImpl* layer);
+
+ std::vector<DebugRect> debug_rects_;
+
+ DISALLOW_COPY_AND_ASSIGN(DebugRectHistory);
+};
+
+} // namespace cc
+
+#endif // CC_DEBUG_DEBUG_RECT_HISTORY_H_
diff --git a/chromium/cc/debug/devtools_instrumentation.h b/chromium/cc/debug/devtools_instrumentation.h
new file mode 100644
index 00000000000..7a77d404454
--- /dev/null
+++ b/chromium/cc/debug/devtools_instrumentation.h
@@ -0,0 +1,75 @@
+// Copyright 2013 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.
+
+#ifndef CC_DEBUG_DEVTOOLS_INSTRUMENTATION_H_
+#define CC_DEBUG_DEVTOOLS_INSTRUMENTATION_H_
+
+#include "base/debug/trace_event.h"
+
+namespace cc {
+namespace devtools_instrumentation {
+
+namespace internal {
+const char kCategory[] = "cc,devtools";
+const char kLayerId[] = "layerId";
+const char kLayerTreeId[] = "layerTreeId";
+}
+
+const char kPaintLayer[] = "PaintLayer";
+const char kRasterTask[] = "RasterTask";
+const char kImageDecodeTask[] = "ImageDecodeTask";
+const char kPaintSetup[] = "PaintSetup";
+const char kUpdateLayer[] = "UpdateLayer";
+
+class ScopedLayerTask {
+ public:
+ ScopedLayerTask(const char* event_name, int layer_id)
+ : event_name_(event_name) {
+ TRACE_EVENT_BEGIN1(internal::kCategory, event_name_,
+ internal::kLayerId, layer_id);
+ }
+ ~ScopedLayerTask() {
+ TRACE_EVENT_END0(internal::kCategory, event_name_);
+ }
+ private:
+ const char* event_name_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedLayerTask);
+};
+
+class ScopedLayerTreeTask {
+ public:
+ ScopedLayerTreeTask(const char* event_name,
+ int layer_id,
+ uint64 tree_id)
+ : event_name_(event_name) {
+ TRACE_EVENT_BEGIN2(internal::kCategory, event_name_,
+ internal::kLayerId, layer_id, internal::kLayerTreeId, tree_id);
+ }
+ ~ScopedLayerTreeTask() {
+ TRACE_EVENT_END0(internal::kCategory, event_name_);
+ }
+ private:
+ const char* event_name_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedLayerTreeTask);
+};
+
+struct ScopedLayerObjectTracker
+ : public base::debug::TraceScopedTrackableObject<int> {
+ explicit ScopedLayerObjectTracker(int layer_id)
+ : base::debug::TraceScopedTrackableObject<int>(
+ internal::kCategory,
+ internal::kLayerId,
+ layer_id) {
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScopedLayerObjectTracker);
+};
+
+} // namespace devtools_instrumentation
+} // namespace cc
+
+#endif // CC_DEBUG_DEVTOOLS_INSTRUMENTATION_H_
diff --git a/chromium/cc/debug/fake_web_graphics_context_3d.cc b/chromium/cc/debug/fake_web_graphics_context_3d.cc
new file mode 100644
index 00000000000..f493dc1d854
--- /dev/null
+++ b/chromium/cc/debug/fake_web_graphics_context_3d.cc
@@ -0,0 +1,347 @@
+// Copyright 2013 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 "cc/debug/fake_web_graphics_context_3d.h"
+
+#include "base/logging.h"
+#include "third_party/khronos/GLES2/gl2.h"
+
+using WebKit::WGC3Dboolean;
+using WebKit::WGC3Denum;
+using WebKit::WebGLId;
+using WebKit::WebGraphicsContext3D;
+
+namespace cc {
+
+FakeWebGraphicsContext3D::FakeWebGraphicsContext3D()
+ : WebKit::WebGraphicsContext3D() {
+}
+
+FakeWebGraphicsContext3D::~FakeWebGraphicsContext3D() {
+}
+
+bool FakeWebGraphicsContext3D::makeContextCurrent() {
+ return true;
+}
+
+int FakeWebGraphicsContext3D::width() {
+ return 1;
+}
+
+int FakeWebGraphicsContext3D::height() {
+ return 1;
+}
+
+void FakeWebGraphicsContext3D::reshape(int width, int height) {
+}
+
+bool FakeWebGraphicsContext3D::isGLES2Compliant() {
+ return false;
+}
+
+WebGLId FakeWebGraphicsContext3D::getPlatformTextureId() {
+ return 0;
+}
+
+bool FakeWebGraphicsContext3D::isContextLost() {
+ return false;
+}
+
+WGC3Denum FakeWebGraphicsContext3D::getGraphicsResetStatusARB() {
+ return GL_NO_ERROR;
+}
+
+void* FakeWebGraphicsContext3D::mapBufferSubDataCHROMIUM(
+ WGC3Denum target,
+ WebKit::WGC3Dintptr offset,
+ WebKit::WGC3Dsizeiptr size,
+ WGC3Denum access) {
+ return 0;
+}
+
+void* FakeWebGraphicsContext3D::mapTexSubImage2DCHROMIUM(
+ WGC3Denum target,
+ WebKit::WGC3Dint level,
+ WebKit::WGC3Dint xoffset,
+ WebKit::WGC3Dint yoffset,
+ WebKit::WGC3Dsizei width,
+ WebKit::WGC3Dsizei height,
+ WGC3Denum format,
+ WGC3Denum type,
+ WGC3Denum access) {
+ return 0;
+}
+
+WebKit::WebString FakeWebGraphicsContext3D::getRequestableExtensionsCHROMIUM() {
+ return WebKit::WebString();
+}
+
+WGC3Denum FakeWebGraphicsContext3D::checkFramebufferStatus(
+ WGC3Denum target) {
+ return GL_FRAMEBUFFER_COMPLETE;
+}
+
+bool FakeWebGraphicsContext3D::getActiveAttrib(
+ WebGLId program,
+ WebKit::WGC3Duint index,
+ ActiveInfo&) {
+ return false;
+}
+
+bool FakeWebGraphicsContext3D::getActiveUniform(
+ WebGLId program,
+ WebKit::WGC3Duint index,
+ ActiveInfo&) {
+ return false;
+}
+
+WebKit::WGC3Dint FakeWebGraphicsContext3D::getAttribLocation(
+ WebGLId program,
+ const WebKit::WGC3Dchar* name) {
+ return 0;
+}
+
+WebGraphicsContext3D::Attributes
+ FakeWebGraphicsContext3D::getContextAttributes() {
+ return WebGraphicsContext3D::Attributes();
+}
+
+WGC3Denum FakeWebGraphicsContext3D::getError() {
+ return 0;
+}
+
+void FakeWebGraphicsContext3D::getIntegerv(
+ WGC3Denum pname,
+ WebKit::WGC3Dint* value) {
+ if (pname == GL_MAX_TEXTURE_SIZE)
+ *value = 1024;
+ else if (pname == GL_ACTIVE_TEXTURE)
+ *value = GL_TEXTURE0;
+}
+
+void FakeWebGraphicsContext3D::getProgramiv(
+ WebGLId program,
+ WGC3Denum pname,
+ WebKit::WGC3Dint* value) {
+ if (pname == GL_LINK_STATUS)
+ *value = 1;
+}
+
+WebKit::WebString FakeWebGraphicsContext3D::getProgramInfoLog(
+ WebGLId program) {
+ return WebKit::WebString();
+}
+
+void FakeWebGraphicsContext3D::getShaderiv(
+ WebGLId shader,
+ WGC3Denum pname,
+ WebKit::WGC3Dint* value) {
+ if (pname == GL_COMPILE_STATUS)
+ *value = 1;
+}
+
+WebKit::WebString FakeWebGraphicsContext3D::getShaderInfoLog(
+ WebGLId shader) {
+ return WebKit::WebString();
+}
+
+void FakeWebGraphicsContext3D::getShaderPrecisionFormat(
+ WebKit::WGC3Denum shadertype,
+ WebKit::WGC3Denum precisiontype,
+ WebKit::WGC3Dint* range,
+ WebKit::WGC3Dint* precision) {
+ // Return the minimum precision requirements of the GLES specificatin.
+ switch (precisiontype) {
+ case GL_LOW_INT:
+ range[0] = 8;
+ range[1] = 8;
+ *precision = 0;
+ break;
+ case GL_MEDIUM_INT:
+ range[0] = 10;
+ range[1] = 10;
+ *precision = 0;
+ break;
+ case GL_HIGH_INT:
+ range[0] = 16;
+ range[1] = 16;
+ *precision = 0;
+ break;
+ case GL_LOW_FLOAT:
+ range[0] = 8;
+ range[1] = 8;
+ *precision = 8;
+ break;
+ case GL_MEDIUM_FLOAT:
+ range[0] = 14;
+ range[1] = 14;
+ *precision = 10;
+ break;
+ case GL_HIGH_FLOAT:
+ range[0] = 62;
+ range[1] = 62;
+ *precision = 16;
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+}
+
+WebKit::WebString FakeWebGraphicsContext3D::getShaderSource(
+ WebGLId shader) {
+ return WebKit::WebString();
+}
+
+WebKit::WebString FakeWebGraphicsContext3D::getString(WGC3Denum name) {
+ return WebKit::WebString();
+}
+
+WebKit::WGC3Dint FakeWebGraphicsContext3D::getUniformLocation(
+ WebGLId program,
+ const WebKit::WGC3Dchar* name) {
+ return 0;
+}
+
+WebKit::WGC3Dsizeiptr FakeWebGraphicsContext3D::getVertexAttribOffset(
+ WebKit::WGC3Duint index,
+ WGC3Denum pname) {
+ return 0;
+}
+
+WGC3Dboolean FakeWebGraphicsContext3D::isBuffer(
+ WebGLId buffer) {
+ return false;
+}
+
+WGC3Dboolean FakeWebGraphicsContext3D::isEnabled(
+ WGC3Denum cap) {
+ return false;
+}
+
+WGC3Dboolean FakeWebGraphicsContext3D::isFramebuffer(
+ WebGLId framebuffer) {
+ return false;
+}
+
+WGC3Dboolean FakeWebGraphicsContext3D::isProgram(
+ WebGLId program) {
+ return false;
+}
+
+WGC3Dboolean FakeWebGraphicsContext3D::isRenderbuffer(
+ WebGLId renderbuffer) {
+ return false;
+}
+
+WGC3Dboolean FakeWebGraphicsContext3D::isShader(
+ WebGLId shader) {
+ return false;
+}
+
+WGC3Dboolean FakeWebGraphicsContext3D::isTexture(
+ WebGLId texture) {
+ return false;
+}
+
+WebGLId FakeWebGraphicsContext3D::createBuffer() {
+ return 1;
+}
+
+void FakeWebGraphicsContext3D::deleteBuffer(WebKit::WebGLId id) {
+}
+
+WebGLId FakeWebGraphicsContext3D::createFramebuffer() {
+ return 1;
+}
+
+void FakeWebGraphicsContext3D::deleteFramebuffer(WebKit::WebGLId id) {
+}
+
+WebGLId FakeWebGraphicsContext3D::createProgram() {
+ return 1;
+}
+
+void FakeWebGraphicsContext3D::deleteProgram(WebKit::WebGLId id) {
+}
+
+WebGLId FakeWebGraphicsContext3D::createRenderbuffer() {
+ return 1;
+}
+
+void FakeWebGraphicsContext3D::deleteRenderbuffer(WebKit::WebGLId id) {
+}
+
+WebGLId FakeWebGraphicsContext3D::createShader(WGC3Denum) {
+ return 1;
+}
+
+void FakeWebGraphicsContext3D::deleteShader(WebKit::WebGLId id) {
+}
+
+WebGLId FakeWebGraphicsContext3D::createTexture() {
+ return 1;
+}
+
+void FakeWebGraphicsContext3D::deleteTexture(WebGLId texture_id) {
+}
+
+void FakeWebGraphicsContext3D::attachShader(WebGLId program, WebGLId shader) {
+}
+
+void FakeWebGraphicsContext3D::useProgram(WebGLId program) {
+}
+
+void FakeWebGraphicsContext3D::bindBuffer(WGC3Denum target, WebGLId buffer) {
+}
+
+void FakeWebGraphicsContext3D::bindFramebuffer(
+ WGC3Denum target, WebGLId framebuffer) {
+}
+
+void FakeWebGraphicsContext3D::bindRenderbuffer(
+ WGC3Denum target, WebGLId renderbuffer) {
+}
+
+void FakeWebGraphicsContext3D::bindTexture(
+ WGC3Denum target, WebGLId texture_id) {
+}
+
+WebGLId FakeWebGraphicsContext3D::createQueryEXT() {
+ return 1;
+}
+
+WGC3Dboolean FakeWebGraphicsContext3D::isQueryEXT(WebGLId query) {
+ return true;
+}
+
+void FakeWebGraphicsContext3D::endQueryEXT(WebKit::WGC3Denum target) {
+}
+
+void FakeWebGraphicsContext3D::getQueryObjectuivEXT(
+ WebKit::WebGLId query,
+ WebKit::WGC3Denum pname,
+ WebKit::WGC3Duint* params) {
+}
+
+void FakeWebGraphicsContext3D::setContextLostCallback(
+ WebGraphicsContextLostCallback* callback) {
+}
+
+void FakeWebGraphicsContext3D::loseContextCHROMIUM(WGC3Denum current,
+ WGC3Denum other) {
+}
+
+WebKit::WGC3Duint FakeWebGraphicsContext3D::createImageCHROMIUM(
+ WebKit::WGC3Dsizei width, WebKit::WGC3Dsizei height,
+ WebKit::WGC3Denum internalformat) {
+ return 0;
+}
+
+void* FakeWebGraphicsContext3D::mapImageCHROMIUM(WebKit::WGC3Duint image_id,
+ WebKit::WGC3Denum access) {
+ return 0;
+}
+
+} // namespace cc
diff --git a/chromium/cc/debug/fake_web_graphics_context_3d.h b/chromium/cc/debug/fake_web_graphics_context_3d.h
new file mode 100644
index 00000000000..3c8a6f53b76
--- /dev/null
+++ b/chromium/cc/debug/fake_web_graphics_context_3d.h
@@ -0,0 +1,611 @@
+// Copyright 2013 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.
+
+#ifndef CC_DEBUG_FAKE_WEB_GRAPHICS_CONTEXT_3D_H_
+#define CC_DEBUG_FAKE_WEB_GRAPHICS_CONTEXT_3D_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "cc/base/cc_export.h"
+#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h"
+
+namespace cc {
+
+// WebGraphicsContext3D base class for use in unit tests.
+// All operations are no-ops (returning 0 if necessary).
+class CC_EXPORT FakeWebGraphicsContext3D
+ : public NON_EXPORTED_BASE(WebKit::WebGraphicsContext3D) {
+ public:
+ FakeWebGraphicsContext3D();
+ virtual ~FakeWebGraphicsContext3D();
+
+ virtual bool makeContextCurrent();
+
+ virtual int width();
+ virtual int height();
+
+ virtual void reshape(int width, int height);
+
+ virtual bool isGLES2Compliant();
+
+ virtual WebKit::WebGLId getPlatformTextureId();
+
+ virtual void prepareTexture() {}
+
+ virtual void postSubBufferCHROMIUM(int x, int y, int width, int height) {}
+
+ virtual void synthesizeGLError(WebKit::WGC3Denum) {}
+
+ virtual bool isContextLost();
+ virtual WebKit::WGC3Denum getGraphicsResetStatusARB();
+
+ virtual void* mapBufferSubDataCHROMIUM(
+ WebKit::WGC3Denum target,
+ WebKit::WGC3Dintptr offset,
+ WebKit::WGC3Dsizeiptr size,
+ WebKit::WGC3Denum access);
+
+ virtual void unmapBufferSubDataCHROMIUM(const void*) {}
+ virtual void* mapTexSubImage2DCHROMIUM(
+ WebKit::WGC3Denum target,
+ WebKit::WGC3Dint level,
+ WebKit::WGC3Dint xoffset,
+ WebKit::WGC3Dint yoffset,
+ WebKit::WGC3Dsizei width,
+ WebKit::WGC3Dsizei height,
+ WebKit::WGC3Denum format,
+ WebKit::WGC3Denum type,
+ WebKit::WGC3Denum access);
+ virtual void unmapTexSubImage2DCHROMIUM(const void*) {}
+
+ virtual void setVisibilityCHROMIUM(bool visible) {}
+
+ virtual void discardFramebufferEXT(
+ WebKit::WGC3Denum target,
+ WebKit::WGC3Dsizei num_attachments,
+ const WebKit::WGC3Denum* attachments) {}
+
+ virtual void setMemoryAllocationChangedCallbackCHROMIUM(
+ WebGraphicsMemoryAllocationChangedCallbackCHROMIUM* callback) {}
+
+ virtual WebKit::WebString getRequestableExtensionsCHROMIUM();
+ virtual void requestExtensionCHROMIUM(const char*) {}
+
+ virtual void blitFramebufferCHROMIUM(
+ WebKit::WGC3Dint src_x0,
+ WebKit::WGC3Dint src_y0,
+ WebKit::WGC3Dint src_x1,
+ WebKit::WGC3Dint src_y1,
+ WebKit::WGC3Dint dst_x0,
+ WebKit::WGC3Dint dst_y0,
+ WebKit::WGC3Dint dst_x1,
+ WebKit::WGC3Dint dst_y1,
+ WebKit::WGC3Dbitfield mask,
+ WebKit::WGC3Denum filter) {}
+ virtual void renderbufferStorageMultisampleCHROMIUM(
+ WebKit::WGC3Denum target,
+ WebKit::WGC3Dsizei samples,
+ WebKit::WGC3Denum internalformat,
+ WebKit::WGC3Dsizei width,
+ WebKit::WGC3Dsizei height) {}
+
+ virtual void activeTexture(WebKit::WGC3Denum texture) {}
+ virtual void attachShader(WebKit::WebGLId program, WebKit::WebGLId shader);
+ virtual void bindAttribLocation(
+ WebKit::WebGLId program,
+ WebKit::WGC3Duint index,
+ const WebKit::WGC3Dchar* name) {}
+ virtual void bindBuffer(WebKit::WGC3Denum target, WebKit::WebGLId buffer);
+ virtual void bindFramebuffer(
+ WebKit::WGC3Denum target, WebKit::WebGLId framebuffer);
+ virtual void bindRenderbuffer(
+ WebKit::WGC3Denum target, WebKit::WebGLId renderbuffer);
+ virtual void bindTexture(
+ WebKit::WGC3Denum target,
+ WebKit::WebGLId texture_id);
+ virtual void blendColor(
+ WebKit::WGC3Dclampf red,
+ WebKit::WGC3Dclampf green,
+ WebKit::WGC3Dclampf blue,
+ WebKit::WGC3Dclampf alpha) {}
+ virtual void blendEquation(WebKit::WGC3Denum mode) {}
+ virtual void blendEquationSeparate(
+ WebKit::WGC3Denum mode_rgb,
+ WebKit::WGC3Denum mode_alpha) {}
+ virtual void blendFunc(
+ WebKit::WGC3Denum sfactor,
+ WebKit::WGC3Denum dfactor) {}
+ virtual void blendFuncSeparate(
+ WebKit::WGC3Denum src_rgb,
+ WebKit::WGC3Denum dst_rgb,
+ WebKit::WGC3Denum src_alpha,
+ WebKit::WGC3Denum dst_alpha) {}
+
+ virtual void bufferData(
+ WebKit::WGC3Denum target,
+ WebKit::WGC3Dsizeiptr size,
+ const void* data,
+ WebKit::WGC3Denum usage) {}
+ virtual void bufferSubData(
+ WebKit::WGC3Denum target,
+ WebKit::WGC3Dintptr offset,
+ WebKit::WGC3Dsizeiptr size,
+ const void* data) {}
+
+ virtual WebKit::WGC3Denum checkFramebufferStatus(WebKit::WGC3Denum target);
+
+ virtual void clear(WebKit::WGC3Dbitfield mask) {}
+ virtual void clearColor(
+ WebKit::WGC3Dclampf red,
+ WebKit::WGC3Dclampf green,
+ WebKit::WGC3Dclampf blue,
+ WebKit::WGC3Dclampf alpha) {}
+ virtual void clearDepth(WebKit::WGC3Dclampf depth) {}
+ virtual void clearStencil(WebKit::WGC3Dint s) {}
+ virtual void colorMask(
+ WebKit::WGC3Dboolean red,
+ WebKit::WGC3Dboolean green,
+ WebKit::WGC3Dboolean blue,
+ WebKit::WGC3Dboolean alpha) {}
+ virtual void compileShader(WebKit::WebGLId shader) {}
+
+ virtual void compressedTexImage2D(
+ WebKit::WGC3Denum target,
+ WebKit::WGC3Dint level,
+ WebKit::WGC3Denum internal_format,
+ WebKit::WGC3Dsizei width,
+ WebKit::WGC3Dsizei height,
+ WebKit::WGC3Dint border,
+ WebKit::WGC3Dsizei image_size,
+ const void* data) {}
+ virtual void compressedTexSubImage2D(
+ WebKit::WGC3Denum target,
+ WebKit::WGC3Dint level,
+ WebKit::WGC3Dint xoffset,
+ WebKit::WGC3Dint yoffset,
+ WebKit::WGC3Dsizei width,
+ WebKit::WGC3Dsizei height,
+ WebKit::WGC3Denum format,
+ WebKit::WGC3Dsizei image_size,
+ const void* data) {}
+ virtual void copyTexImage2D(
+ WebKit::WGC3Denum target,
+ WebKit::WGC3Dint level,
+ WebKit::WGC3Denum internalformat,
+ WebKit::WGC3Dint x,
+ WebKit::WGC3Dint y,
+ WebKit::WGC3Dsizei width,
+ WebKit::WGC3Dsizei height,
+ WebKit::WGC3Dint border) {}
+ virtual void copyTexSubImage2D(
+ WebKit::WGC3Denum target,
+ WebKit::WGC3Dint level,
+ WebKit::WGC3Dint xoffset,
+ WebKit::WGC3Dint yoffset,
+ WebKit::WGC3Dint x,
+ WebKit::WGC3Dint y,
+ WebKit::WGC3Dsizei width,
+ WebKit::WGC3Dsizei height) {}
+ virtual void cullFace(WebKit::WGC3Denum mode) {}
+ virtual void depthFunc(WebKit::WGC3Denum func) {}
+ virtual void depthMask(WebKit::WGC3Dboolean flag) {}
+ virtual void depthRange(
+ WebKit::WGC3Dclampf z_near,
+ WebKit::WGC3Dclampf z_far) {}
+ virtual void detachShader(WebKit::WebGLId program, WebKit::WebGLId shader) {}
+ virtual void disable(WebKit::WGC3Denum cap) {}
+ virtual void disableVertexAttribArray(WebKit::WGC3Duint index) {}
+ virtual void drawArrays(
+ WebKit::WGC3Denum mode,
+ WebKit::WGC3Dint first,
+ WebKit::WGC3Dsizei count) {}
+ virtual void drawElements(
+ WebKit::WGC3Denum mode,
+ WebKit::WGC3Dsizei count,
+ WebKit::WGC3Denum type,
+ WebKit::WGC3Dintptr offset) {}
+
+ virtual void enable(WebKit::WGC3Denum cap) {}
+ virtual void enableVertexAttribArray(WebKit::WGC3Duint index) {}
+ virtual void finish() {}
+ virtual void flush() {}
+ virtual void framebufferRenderbuffer(
+ WebKit::WGC3Denum target,
+ WebKit::WGC3Denum attachment,
+ WebKit::WGC3Denum renderbuffertarget,
+ WebKit::WebGLId renderbuffer) {}
+ virtual void framebufferTexture2D(
+ WebKit::WGC3Denum target,
+ WebKit::WGC3Denum attachment,
+ WebKit::WGC3Denum textarget,
+ WebKit::WebGLId texture,
+ WebKit::WGC3Dint level) {}
+ virtual void frontFace(WebKit::WGC3Denum mode) {}
+ virtual void generateMipmap(WebKit::WGC3Denum target) {}
+
+ virtual bool getActiveAttrib(
+ WebKit::WebGLId program,
+ WebKit::WGC3Duint index, ActiveInfo&);
+ virtual bool getActiveUniform(
+ WebKit::WebGLId program,
+ WebKit::WGC3Duint index,
+ ActiveInfo&);
+ virtual void getAttachedShaders(
+ WebKit::WebGLId program,
+ WebKit::WGC3Dsizei max_count,
+ WebKit::WGC3Dsizei* count,
+ WebKit::WebGLId* shaders) {}
+ virtual WebKit::WGC3Dint getAttribLocation(
+ WebKit::WebGLId program,
+ const WebKit::WGC3Dchar* name);
+ virtual void getBooleanv(
+ WebKit::WGC3Denum pname,
+ WebKit::WGC3Dboolean* value) {}
+ virtual void getBufferParameteriv(
+ WebKit::WGC3Denum target,
+ WebKit::WGC3Denum pname,
+ WebKit::WGC3Dint* value) {}
+ virtual Attributes getContextAttributes();
+ virtual WebKit::WGC3Denum getError();
+ virtual void getFloatv(
+ WebKit::WGC3Denum pname,
+ WebKit::WGC3Dfloat* value) {}
+ virtual void getFramebufferAttachmentParameteriv(
+ WebKit::WGC3Denum target,
+ WebKit::WGC3Denum attachment,
+ WebKit::WGC3Denum pname,
+ WebKit::WGC3Dint* value) {}
+
+ virtual void getIntegerv(
+ WebKit::WGC3Denum pname,
+ WebKit::WGC3Dint* value);
+
+ virtual void getProgramiv(
+ WebKit::WebGLId program,
+ WebKit::WGC3Denum pname,
+ WebKit::WGC3Dint* value);
+
+ virtual WebKit::WebString getProgramInfoLog(WebKit::WebGLId program);
+ virtual void getRenderbufferParameteriv(
+ WebKit::WGC3Denum target,
+ WebKit::WGC3Denum pname,
+ WebKit::WGC3Dint* value) {}
+
+ virtual void getShaderiv(
+ WebKit::WebGLId shader,
+ WebKit::WGC3Denum pname,
+ WebKit::WGC3Dint* value);
+
+ virtual WebKit::WebString getShaderInfoLog(WebKit::WebGLId shader);
+ virtual void getShaderPrecisionFormat(
+ WebKit::WGC3Denum shadertype,
+ WebKit::WGC3Denum precisiontype,
+ WebKit::WGC3Dint* range,
+ WebKit::WGC3Dint* precision);
+ virtual WebKit::WebString getShaderSource(WebKit::WebGLId shader);
+ virtual WebKit::WebString getString(WebKit::WGC3Denum name);
+ virtual void getTexParameterfv(
+ WebKit::WGC3Denum target,
+ WebKit::WGC3Denum pname,
+ WebKit::WGC3Dfloat* value) {}
+ virtual void getTexParameteriv(
+ WebKit::WGC3Denum target,
+ WebKit::WGC3Denum pname,
+ WebKit::WGC3Dint* value) {}
+ virtual void getUniformfv(
+ WebKit::WebGLId program,
+ WebKit::WGC3Dint location,
+ WebKit::WGC3Dfloat* value) {}
+ virtual void getUniformiv(
+ WebKit::WebGLId program,
+ WebKit::WGC3Dint location,
+ WebKit::WGC3Dint* value) {}
+ virtual WebKit::WGC3Dint getUniformLocation(
+ WebKit::WebGLId program,
+ const WebKit::WGC3Dchar* name);
+ virtual void getVertexAttribfv(
+ WebKit::WGC3Duint index,
+ WebKit::WGC3Denum pname,
+ WebKit::WGC3Dfloat* value) {}
+ virtual void getVertexAttribiv(
+ WebKit::WGC3Duint index,
+ WebKit::WGC3Denum pname,
+ WebKit::WGC3Dint* value) {}
+ virtual WebKit::WGC3Dsizeiptr getVertexAttribOffset(
+ WebKit::WGC3Duint index,
+ WebKit::WGC3Denum pname);
+
+ virtual void hint(WebKit::WGC3Denum target, WebKit::WGC3Denum mode) {}
+ virtual WebKit::WGC3Dboolean isBuffer(WebKit::WebGLId buffer);
+ virtual WebKit::WGC3Dboolean isEnabled(WebKit::WGC3Denum cap);
+ virtual WebKit::WGC3Dboolean isFramebuffer(WebKit::WebGLId framebuffer);
+ virtual WebKit::WGC3Dboolean isProgram(WebKit::WebGLId program);
+ virtual WebKit::WGC3Dboolean isRenderbuffer(WebKit::WebGLId renderbuffer);
+ virtual WebKit::WGC3Dboolean isShader(WebKit::WebGLId shader);
+ virtual WebKit::WGC3Dboolean isTexture(WebKit::WebGLId texture);
+ virtual void lineWidth(WebKit::WGC3Dfloat) {}
+ virtual void linkProgram(WebKit::WebGLId program) {}
+ virtual void pixelStorei(WebKit::WGC3Denum pname, WebKit::WGC3Dint param) {}
+ virtual void polygonOffset(
+ WebKit::WGC3Dfloat factor,
+ WebKit::WGC3Dfloat units) {}
+
+ virtual void readPixels(
+ WebKit::WGC3Dint x,
+ WebKit::WGC3Dint y,
+ WebKit::WGC3Dsizei width,
+ WebKit::WGC3Dsizei height,
+ WebKit::WGC3Denum format,
+ WebKit::WGC3Denum type,
+ void* pixels) {}
+
+ virtual void releaseShaderCompiler() {}
+
+ virtual void renderbufferStorage(
+ WebKit::WGC3Denum target,
+ WebKit::WGC3Denum internalformat,
+ WebKit::WGC3Dsizei width,
+ WebKit::WGC3Dsizei height) {}
+ virtual void sampleCoverage(
+ WebKit::WGC3Dclampf value,
+ WebKit::WGC3Dboolean invert) {}
+ virtual void scissor(
+ WebKit::WGC3Dint x,
+ WebKit::WGC3Dint y,
+ WebKit::WGC3Dsizei width,
+ WebKit::WGC3Dsizei height) {}
+ virtual void shaderSource(
+ WebKit::WebGLId shader,
+ const WebKit::WGC3Dchar* string) {}
+ virtual void stencilFunc(
+ WebKit::WGC3Denum func,
+ WebKit::WGC3Dint ref,
+ WebKit::WGC3Duint mask) {}
+ virtual void stencilFuncSeparate(
+ WebKit::WGC3Denum face,
+ WebKit::WGC3Denum func,
+ WebKit::WGC3Dint ref,
+ WebKit::WGC3Duint mask) {}
+ virtual void stencilMask(WebKit::WGC3Duint mask) {}
+ virtual void stencilMaskSeparate(
+ WebKit::WGC3Denum face,
+ WebKit::WGC3Duint mask) {}
+ virtual void stencilOp(
+ WebKit::WGC3Denum fail,
+ WebKit::WGC3Denum zfail,
+ WebKit::WGC3Denum zpass) {}
+ virtual void stencilOpSeparate(
+ WebKit::WGC3Denum face,
+ WebKit::WGC3Denum fail,
+ WebKit::WGC3Denum zfail,
+ WebKit::WGC3Denum zpass) {}
+
+ virtual void texImage2D(
+ WebKit::WGC3Denum target,
+ WebKit::WGC3Dint level,
+ WebKit::WGC3Denum internalformat,
+ WebKit::WGC3Dsizei width,
+ WebKit::WGC3Dsizei height,
+ WebKit::WGC3Dint border,
+ WebKit::WGC3Denum format,
+ WebKit::WGC3Denum type,
+ const void* pixels) {}
+
+ virtual void texParameterf(
+ WebKit::WGC3Denum target,
+ WebKit::WGC3Denum pname,
+ WebKit::WGC3Dfloat param) {}
+ virtual void texParameteri(
+ WebKit::WGC3Denum target,
+ WebKit::WGC3Denum pname,
+ WebKit::WGC3Dint param) {}
+
+ virtual void texSubImage2D(
+ WebKit::WGC3Denum target,
+ WebKit::WGC3Dint level,
+ WebKit::WGC3Dint xoffset,
+ WebKit::WGC3Dint yoffset,
+ WebKit::WGC3Dsizei width,
+ WebKit::WGC3Dsizei height,
+ WebKit::WGC3Denum format,
+ WebKit::WGC3Denum type,
+ const void* pixels) {}
+
+ virtual void uniform1f(WebKit::WGC3Dint location, WebKit::WGC3Dfloat x) {}
+ virtual void uniform1fv(
+ WebKit::WGC3Dint location,
+ WebKit::WGC3Dsizei count,
+ const WebKit::WGC3Dfloat* v) {}
+ virtual void uniform1i(WebKit::WGC3Dint location, WebKit::WGC3Dint x) {}
+ virtual void uniform1iv(
+ WebKit::WGC3Dint location,
+ WebKit::WGC3Dsizei count,
+ const WebKit::WGC3Dint* v) {}
+ virtual void uniform2f(
+ WebKit::WGC3Dint location,
+ WebKit::WGC3Dfloat x,
+ WebKit::WGC3Dfloat y) {}
+ virtual void uniform2fv(
+ WebKit::WGC3Dint location,
+ WebKit::WGC3Dsizei count,
+ const WebKit::WGC3Dfloat* v) {}
+ virtual void uniform2i(
+ WebKit::WGC3Dint location,
+ WebKit::WGC3Dint x,
+ WebKit::WGC3Dint y) {}
+ virtual void uniform2iv(
+ WebKit::WGC3Dint location,
+ WebKit::WGC3Dsizei count,
+ const WebKit::WGC3Dint* v) {}
+ virtual void uniform3f(
+ WebKit::WGC3Dint location,
+ WebKit::WGC3Dfloat x,
+ WebKit::WGC3Dfloat y,
+ WebKit::WGC3Dfloat z) {}
+ virtual void uniform3fv(
+ WebKit::WGC3Dint location,
+ WebKit::WGC3Dsizei count,
+ const WebKit::WGC3Dfloat* v) {}
+ virtual void uniform3i(
+ WebKit::WGC3Dint location,
+ WebKit::WGC3Dint x,
+ WebKit::WGC3Dint y,
+ WebKit::WGC3Dint z) {}
+ virtual void uniform3iv(
+ WebKit::WGC3Dint location,
+ WebKit::WGC3Dsizei count,
+ const WebKit::WGC3Dint* v) {}
+ virtual void uniform4f(
+ WebKit::WGC3Dint location,
+ WebKit::WGC3Dfloat x,
+ WebKit::WGC3Dfloat y,
+ WebKit::WGC3Dfloat z,
+ WebKit::WGC3Dfloat w) {}
+ virtual void uniform4fv(
+ WebKit::WGC3Dint location,
+ WebKit::WGC3Dsizei count,
+ const WebKit::WGC3Dfloat* v) {}
+ virtual void uniform4i(
+ WebKit::WGC3Dint location,
+ WebKit::WGC3Dint x,
+ WebKit::WGC3Dint y,
+ WebKit::WGC3Dint z,
+ WebKit::WGC3Dint w) {}
+ virtual void uniform4iv(
+ WebKit::WGC3Dint location,
+ WebKit::WGC3Dsizei count,
+ const WebKit::WGC3Dint* v) {}
+ virtual void uniformMatrix2fv(
+ WebKit::WGC3Dint location,
+ WebKit::WGC3Dsizei count,
+ WebKit::WGC3Dboolean transpose,
+ const WebKit::WGC3Dfloat* value) {}
+ virtual void uniformMatrix3fv(
+ WebKit::WGC3Dint location,
+ WebKit::WGC3Dsizei count,
+ WebKit::WGC3Dboolean transpose,
+ const WebKit::WGC3Dfloat* value) {}
+ virtual void uniformMatrix4fv(
+ WebKit::WGC3Dint location,
+ WebKit::WGC3Dsizei count,
+ WebKit::WGC3Dboolean transpose,
+ const WebKit::WGC3Dfloat* value) {}
+
+ virtual void useProgram(WebKit::WebGLId program);
+ virtual void validateProgram(WebKit::WebGLId program) {}
+
+ virtual void vertexAttrib1f(WebKit::WGC3Duint index, WebKit::WGC3Dfloat x) {}
+ virtual void vertexAttrib1fv(
+ WebKit::WGC3Duint index,
+ const WebKit::WGC3Dfloat* values) {}
+ virtual void vertexAttrib2f(
+ WebKit::WGC3Duint index,
+ WebKit::WGC3Dfloat x,
+ WebKit::WGC3Dfloat y) {}
+ virtual void vertexAttrib2fv(
+ WebKit::WGC3Duint index,
+ const WebKit::WGC3Dfloat* values) {}
+ virtual void vertexAttrib3f(
+ WebKit::WGC3Duint index,
+ WebKit::WGC3Dfloat x,
+ WebKit::WGC3Dfloat y,
+ WebKit::WGC3Dfloat z) {}
+ virtual void vertexAttrib3fv(
+ WebKit::WGC3Duint index,
+ const WebKit::WGC3Dfloat* values) {}
+ virtual void vertexAttrib4f(
+ WebKit::WGC3Duint index,
+ WebKit::WGC3Dfloat x,
+ WebKit::WGC3Dfloat y,
+ WebKit::WGC3Dfloat z,
+ WebKit::WGC3Dfloat w) {}
+ virtual void vertexAttrib4fv(
+ WebKit::WGC3Duint index,
+ const WebKit::WGC3Dfloat* values) {}
+ virtual void vertexAttribPointer(
+ WebKit::WGC3Duint index,
+ WebKit::WGC3Dint size,
+ WebKit::WGC3Denum type,
+ WebKit::WGC3Dboolean normalized,
+ WebKit::WGC3Dsizei stride,
+ WebKit::WGC3Dintptr offset) {}
+
+ virtual void viewport(
+ WebKit::WGC3Dint x,
+ WebKit::WGC3Dint y,
+ WebKit::WGC3Dsizei width,
+ WebKit::WGC3Dsizei height) {}
+
+ virtual WebKit::WebGLId createBuffer();
+ virtual WebKit::WebGLId createFramebuffer();
+ virtual WebKit::WebGLId createProgram();
+ virtual WebKit::WebGLId createRenderbuffer();
+ virtual WebKit::WebGLId createShader(WebKit::WGC3Denum);
+ virtual WebKit::WebGLId createTexture();
+
+ virtual void deleteBuffer(WebKit::WebGLId id);
+ virtual void deleteFramebuffer(WebKit::WebGLId id);
+ virtual void deleteProgram(WebKit::WebGLId id);
+ virtual void deleteRenderbuffer(WebKit::WebGLId id);
+ virtual void deleteShader(WebKit::WebGLId id);
+ virtual void deleteTexture(WebKit::WebGLId texture_id);
+
+ virtual void texStorage2DEXT(
+ WebKit::WGC3Denum target,
+ WebKit::WGC3Dint levels,
+ WebKit::WGC3Duint internalformat,
+ WebKit::WGC3Dint width,
+ WebKit::WGC3Dint height) {}
+
+ virtual WebKit::WebGLId createQueryEXT();
+ virtual void deleteQueryEXT(WebKit::WebGLId query) {}
+ virtual WebKit::WGC3Dboolean isQueryEXT(WebKit::WebGLId query);
+ virtual void beginQueryEXT(
+ WebKit::WGC3Denum target,
+ WebKit::WebGLId query) {}
+ virtual void endQueryEXT(WebKit::WGC3Denum target);
+ virtual void getQueryivEXT(
+ WebKit::WGC3Denum target,
+ WebKit::WGC3Denum pname,
+ WebKit::WGC3Dint* params) {}
+ virtual void getQueryObjectuivEXT(
+ WebKit::WebGLId query,
+ WebKit::WGC3Denum pname,
+ WebKit::WGC3Duint* params);
+
+ virtual void setContextLostCallback(
+ WebGraphicsContextLostCallback* callback);
+
+ virtual void loseContextCHROMIUM(WebKit::WGC3Denum current,
+ WebKit::WGC3Denum other);
+
+ virtual void drawBuffersEXT(WebKit::WGC3Dsizei m,
+ const WebKit::WGC3Denum* bufs) {}
+
+ virtual void bindTexImage2DCHROMIUM(WebKit::WGC3Denum target,
+ WebKit::WGC3Dint image_id) {}
+
+ // GL_CHROMIUM_gpu_memory_buffer
+ virtual WebKit::WGC3Duint createImageCHROMIUM(
+ WebKit::WGC3Dsizei width,
+ WebKit::WGC3Dsizei height,
+ WebKit::WGC3Denum internalformat);
+ virtual void destroyImageCHROMIUM(WebKit::WGC3Duint image_id) {}
+ virtual void getImageParameterivCHROMIUM(
+ WebKit::WGC3Duint image_id,
+ WebKit::WGC3Denum pname,
+ WebKit::WGC3Dint* params) {}
+ virtual void* mapImageCHROMIUM(
+ WebKit::WGC3Duint image_id,
+ WebKit::WGC3Denum access);
+ virtual void unmapImageCHROMIUM(WebKit::WGC3Duint image_id) {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FakeWebGraphicsContext3D);
+};
+
+} // namespace cc
+
+#endif // CC_DEBUG_FAKE_WEB_GRAPHICS_CONTEXT_3D_H_
diff --git a/chromium/cc/debug/frame_rate_counter.cc b/chromium/cc/debug/frame_rate_counter.cc
new file mode 100644
index 00000000000..8a829ff711a
--- /dev/null
+++ b/chromium/cc/debug/frame_rate_counter.cc
@@ -0,0 +1,138 @@
+// 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 "cc/debug/frame_rate_counter.h"
+
+#include <algorithm>
+#include <limits>
+
+#include "base/metrics/histogram.h"
+#include "cc/trees/proxy.h"
+
+namespace cc {
+
+// The following constants are measured in seconds.
+
+// Two thresholds (measured in seconds) that describe what is considered to be a
+// "no-op frame" that should not be counted.
+// - if the frame is too fast, then given our compositor implementation, the
+// frame probably was a no-op and did not draw.
+// - if the frame is too slow, then there is probably not animating content, so
+// we should not pollute the average.
+static const double kFrameTooFast = 1.0 / 70.0;
+static const double kFrameTooSlow = 1.0 / 4.0;
+
+// If a frame takes longer than this threshold (measured in seconds) then we
+// (naively) assume that it missed a screen refresh; that is, we dropped a
+// frame.
+// TODO(brianderson): Determine this threshold based on monitor refresh rate,
+// crbug.com/138642.
+static const double kDroppedFrameTime = 1.0 / 50.0;
+
+// static
+scoped_ptr<FrameRateCounter> FrameRateCounter::Create(bool has_impl_thread) {
+ return make_scoped_ptr(new FrameRateCounter(has_impl_thread));
+}
+
+base::TimeDelta FrameRateCounter::RecentFrameInterval(size_t n) const {
+ DCHECK_GT(n, 0u);
+ DCHECK_LT(n, ring_buffer_.BufferSize());
+ return ring_buffer_.ReadBuffer(n) - ring_buffer_.ReadBuffer(n - 1);
+}
+
+FrameRateCounter::FrameRateCounter(bool has_impl_thread)
+ : has_impl_thread_(has_impl_thread), dropped_frame_count_(0) {}
+
+void FrameRateCounter::SaveTimeStamp(base::TimeTicks timestamp) {
+ ring_buffer_.SaveToBuffer(timestamp);
+
+ // Check if frame interval can be computed.
+ if (ring_buffer_.CurrentIndex() < 2)
+ return;
+
+ base::TimeDelta frame_interval_seconds =
+ RecentFrameInterval(ring_buffer_.BufferSize() - 1);
+
+ if (has_impl_thread_ && ring_buffer_.CurrentIndex() > 0) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Renderer4.CompositorThreadImplDrawDelay",
+ frame_interval_seconds.InMilliseconds(),
+ 1,
+ 120,
+ 60);
+ }
+
+ if (!IsBadFrameInterval(frame_interval_seconds) &&
+ frame_interval_seconds.InSecondsF() > kDroppedFrameTime)
+ ++dropped_frame_count_;
+}
+
+bool FrameRateCounter::IsBadFrameInterval(
+ base::TimeDelta interval_between_consecutive_frames) const {
+ double delta = interval_between_consecutive_frames.InSecondsF();
+ bool scheduler_allows_double_frames = !has_impl_thread_;
+ bool interval_too_fast =
+ scheduler_allows_double_frames ? delta < kFrameTooFast : delta <= 0.0;
+ bool interval_too_slow = delta > kFrameTooSlow;
+ return interval_too_fast || interval_too_slow;
+}
+
+void FrameRateCounter::GetMinAndMaxFPS(double* min_fps, double* max_fps) const {
+ *min_fps = std::numeric_limits<double>::max();
+ *max_fps = 0.0;
+
+ for (RingBufferType::Iterator it = --ring_buffer_.End(); it; --it) {
+ base::TimeDelta delta = RecentFrameInterval(it.index() + 1);
+
+ if (IsBadFrameInterval(delta))
+ continue;
+
+ DCHECK_GT(delta.InSecondsF(), 0.f);
+ double fps = 1.0 / delta.InSecondsF();
+
+ *min_fps = std::min(fps, *min_fps);
+ *max_fps = std::max(fps, *max_fps);
+ }
+
+ if (*min_fps > *max_fps)
+ *min_fps = *max_fps;
+}
+
+double FrameRateCounter::GetAverageFPS() const {
+ int frame_count = 0;
+ double frame_times_total = 0.0;
+ double average_fps = 0.0;
+
+ // Walk backwards through the samples looking for a run of good frame
+ // timings from which to compute the mean.
+ //
+ // Slow frames occur just because the user is inactive, and should be
+ // ignored. Fast frames are ignored if the scheduler is in single-thread
+ // mode in order to represent the true frame rate in spite of the fact that
+ // the first few swapbuffers happen instantly which skews the statistics
+ // too much for short lived animations.
+ //
+ // IsBadFrameInterval encapsulates the frame too slow/frame too fast logic.
+
+ for (RingBufferType::Iterator it = --ring_buffer_.End();
+ it && frame_times_total < 1.0;
+ --it) {
+ base::TimeDelta delta = RecentFrameInterval(it.index() + 1);
+
+ if (!IsBadFrameInterval(delta)) {
+ frame_count++;
+ frame_times_total += delta.InSecondsF();
+ } else if (frame_count) {
+ break;
+ }
+ }
+
+ if (frame_count) {
+ DCHECK_GT(frame_times_total, 0.0);
+ average_fps = frame_count / frame_times_total;
+ }
+
+ return average_fps;
+}
+
+} // namespace cc
diff --git a/chromium/cc/debug/frame_rate_counter.h b/chromium/cc/debug/frame_rate_counter.h
new file mode 100644
index 00000000000..32b4c2c24f1
--- /dev/null
+++ b/chromium/cc/debug/frame_rate_counter.h
@@ -0,0 +1,57 @@
+// 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.
+
+#ifndef CC_DEBUG_FRAME_RATE_COUNTER_H_
+#define CC_DEBUG_FRAME_RATE_COUNTER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "cc/debug/ring_buffer.h"
+
+namespace cc {
+
+// This class maintains a history of timestamps, and provides functionality to
+// intelligently compute average frames per second.
+class FrameRateCounter {
+ public:
+ static scoped_ptr<FrameRateCounter> Create(bool has_impl_thread);
+
+ int current_frame_number() const { return ring_buffer_.CurrentIndex(); }
+ int dropped_frame_count() const { return dropped_frame_count_; }
+ size_t time_stamp_history_size() const { return ring_buffer_.BufferSize(); }
+
+ void SaveTimeStamp(base::TimeTicks timestamp);
+
+ // n = 0 returns the oldest frame interval retained in the history, while n =
+ // time_stamp_history_size() - 1 returns the most recent frame interval.
+ base::TimeDelta RecentFrameInterval(size_t n) const;
+
+ // This is a heuristic that can be used to ignore frames in a reasonable way.
+ // Returns true if the given frame interval is too fast or too slow, based on
+ // constant thresholds.
+ bool IsBadFrameInterval(
+ base::TimeDelta interval_between_consecutive_frames) const;
+
+ void GetMinAndMaxFPS(double* min_fps, double* max_fps) const;
+ double GetAverageFPS() const;
+
+ typedef RingBuffer<base::TimeTicks, 136> RingBufferType;
+ RingBufferType::Iterator begin() const { return ring_buffer_.Begin(); }
+ RingBufferType::Iterator end() const { return ring_buffer_.End(); }
+
+ private:
+ explicit FrameRateCounter(bool has_impl_thread);
+
+ RingBufferType ring_buffer_;
+
+ bool has_impl_thread_;
+ int dropped_frame_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(FrameRateCounter);
+};
+
+} // namespace cc
+
+#endif // CC_DEBUG_FRAME_RATE_COUNTER_H_
diff --git a/chromium/cc/debug/layer_tree_debug_state.cc b/chromium/cc/debug/layer_tree_debug_state.cc
new file mode 100644
index 00000000000..72c3a9ff18c
--- /dev/null
+++ b/chromium/cc/debug/layer_tree_debug_state.cc
@@ -0,0 +1,113 @@
+// Copyright 2011 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 "cc/debug/layer_tree_debug_state.h"
+
+#include "base/logging.h"
+
+namespace cc {
+
+// IMPORTANT: new fields must be added to Equal() and Unite()
+LayerTreeDebugState::LayerTreeDebugState()
+ : show_fps_counter(false),
+ show_debug_borders(false),
+ continuous_painting(false),
+ show_paint_rects(false),
+ show_property_changed_rects(false),
+ show_surface_damage_rects(false),
+ show_screen_space_rects(false),
+ show_replica_screen_space_rects(false),
+ show_occluding_rects(false),
+ show_non_occluding_rects(false),
+ show_touch_event_handler_rects(false),
+ show_wheel_event_handler_rects(false),
+ show_non_fast_scrollable_rects(false),
+ slow_down_raster_scale_factor(0),
+ rasterize_only_visible_content(false),
+ show_picture_borders(false),
+ record_rendering_stats_(false) {}
+
+LayerTreeDebugState::~LayerTreeDebugState() {}
+
+void LayerTreeDebugState::SetRecordRenderingStats(bool enabled) {
+ record_rendering_stats_ = enabled;
+}
+
+bool LayerTreeDebugState::RecordRenderingStats() const {
+ return record_rendering_stats_ || continuous_painting;
+}
+
+bool LayerTreeDebugState::ShowHudInfo() const {
+ return show_fps_counter || continuous_painting ||
+ ShowHudRects();
+}
+
+bool LayerTreeDebugState::ShowHudRects() const {
+ return show_paint_rects || show_property_changed_rects ||
+ show_surface_damage_rects || show_screen_space_rects ||
+ show_replica_screen_space_rects || show_occluding_rects ||
+ show_non_occluding_rects || show_touch_event_handler_rects ||
+ show_wheel_event_handler_rects || show_non_fast_scrollable_rects;
+}
+
+bool LayerTreeDebugState::ShowMemoryStats() const {
+ return show_fps_counter || continuous_painting;
+}
+
+bool LayerTreeDebugState::Equal(const LayerTreeDebugState& a,
+ const LayerTreeDebugState& b) {
+ return (a.show_fps_counter == b.show_fps_counter &&
+ a.show_debug_borders == b.show_debug_borders &&
+ a.continuous_painting == b.continuous_painting &&
+ a.show_paint_rects == b.show_paint_rects &&
+ a.show_property_changed_rects == b.show_property_changed_rects &&
+ a.show_surface_damage_rects == b.show_surface_damage_rects &&
+ a.show_screen_space_rects == b.show_screen_space_rects &&
+ a.show_replica_screen_space_rects ==
+ b.show_replica_screen_space_rects &&
+ a.show_occluding_rects == b.show_occluding_rects &&
+ a.show_non_occluding_rects == b.show_non_occluding_rects &&
+ a.show_touch_event_handler_rects ==
+ b.show_touch_event_handler_rects &&
+ a.show_wheel_event_handler_rects ==
+ b.show_wheel_event_handler_rects &&
+ a.show_non_fast_scrollable_rects ==
+ b.show_non_fast_scrollable_rects &&
+ a.slow_down_raster_scale_factor == b.slow_down_raster_scale_factor &&
+ a.rasterize_only_visible_content ==
+ b.rasterize_only_visible_content &&
+ a.show_picture_borders == b.show_picture_borders &&
+ a.record_rendering_stats_ == b.record_rendering_stats_);
+}
+
+LayerTreeDebugState LayerTreeDebugState::Unite(const LayerTreeDebugState& a,
+ const LayerTreeDebugState& b) {
+ LayerTreeDebugState r(a);
+
+ r.show_fps_counter |= b.show_fps_counter;
+ r.show_debug_borders |= b.show_debug_borders;
+ r.continuous_painting |= b.continuous_painting;
+
+ r.show_paint_rects |= b.show_paint_rects;
+ r.show_property_changed_rects |= b.show_property_changed_rects;
+ r.show_surface_damage_rects |= b.show_surface_damage_rects;
+ r.show_screen_space_rects |= b.show_screen_space_rects;
+ r.show_replica_screen_space_rects |= b.show_replica_screen_space_rects;
+ r.show_occluding_rects |= b.show_occluding_rects;
+ r.show_non_occluding_rects |= b.show_non_occluding_rects;
+ r.show_touch_event_handler_rects |= b.show_touch_event_handler_rects;
+ r.show_wheel_event_handler_rects |= b.show_wheel_event_handler_rects;
+ r.show_non_fast_scrollable_rects |= b.show_non_fast_scrollable_rects;
+
+ if (b.slow_down_raster_scale_factor)
+ r.slow_down_raster_scale_factor = b.slow_down_raster_scale_factor;
+ r.rasterize_only_visible_content |= b.rasterize_only_visible_content;
+ r.show_picture_borders |= b.show_picture_borders;
+
+ r.record_rendering_stats_ |= b.record_rendering_stats_;
+
+ return r;
+}
+
+} // namespace cc
diff --git a/chromium/cc/debug/layer_tree_debug_state.h b/chromium/cc/debug/layer_tree_debug_state.h
new file mode 100644
index 00000000000..1233a4e36f0
--- /dev/null
+++ b/chromium/cc/debug/layer_tree_debug_state.h
@@ -0,0 +1,54 @@
+// Copyright 2011 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.
+
+#ifndef CC_DEBUG_LAYER_TREE_DEBUG_STATE_H_
+#define CC_DEBUG_LAYER_TREE_DEBUG_STATE_H_
+
+#include "base/basictypes.h"
+#include "cc/base/cc_export.h"
+
+namespace cc {
+
+class CC_EXPORT LayerTreeDebugState {
+ public:
+ LayerTreeDebugState();
+ ~LayerTreeDebugState();
+
+ bool show_fps_counter;
+ bool show_debug_borders;
+ bool continuous_painting;
+
+ bool show_paint_rects;
+ bool show_property_changed_rects;
+ bool show_surface_damage_rects;
+ bool show_screen_space_rects;
+ bool show_replica_screen_space_rects;
+ bool show_occluding_rects;
+ bool show_non_occluding_rects;
+ bool show_touch_event_handler_rects;
+ bool show_wheel_event_handler_rects;
+ bool show_non_fast_scrollable_rects;
+
+ int slow_down_raster_scale_factor;
+ bool rasterize_only_visible_content;
+ bool show_picture_borders;
+
+ void SetRecordRenderingStats(bool enabled);
+ bool RecordRenderingStats() const;
+
+ bool ShowHudInfo() const;
+ bool ShowHudRects() const;
+ bool ShowMemoryStats() const;
+
+ static bool Equal(const LayerTreeDebugState& a, const LayerTreeDebugState& b);
+ static LayerTreeDebugState Unite(const LayerTreeDebugState& a,
+ const LayerTreeDebugState& b);
+
+ private:
+ bool record_rendering_stats_;
+};
+
+} // namespace cc
+
+#endif // CC_DEBUG_LAYER_TREE_DEBUG_STATE_H_
diff --git a/chromium/cc/debug/overdraw_metrics.cc b/chromium/cc/debug/overdraw_metrics.cc
new file mode 100644
index 00000000000..5b19370fe2c
--- /dev/null
+++ b/chromium/cc/debug/overdraw_metrics.cc
@@ -0,0 +1,268 @@
+// 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 "cc/debug/overdraw_metrics.h"
+
+#include "base/debug/trace_event.h"
+#include "base/metrics/histogram.h"
+#include "cc/base/math_util.h"
+#include "cc/trees/layer_tree_host.h"
+#include "cc/trees/layer_tree_host_impl.h"
+#include "ui/gfx/quad_f.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+
+OverdrawMetrics::OverdrawMetrics(bool record_metrics_for_frame)
+ : record_metrics_for_frame_(record_metrics_for_frame),
+ pixels_painted_(0),
+ pixels_uploaded_opaque_(0),
+ pixels_uploaded_translucent_(0),
+ tiles_culled_for_upload_(0),
+ contents_texture_use_bytes_(0),
+ render_surface_texture_use_bytes_(0),
+ pixels_drawn_opaque_(0),
+ pixels_drawn_translucent_(0),
+ pixels_culled_for_drawing_(0) {}
+
+static inline float WedgeProduct(gfx::PointF p1, gfx::PointF p2) {
+ return p1.x() * p2.y() - p1.y() * p2.x();
+}
+
+// Calculates area of an arbitrary convex polygon with up to 8 points.
+static inline float PolygonArea(gfx::PointF points[8], int num_points) {
+ if (num_points < 3)
+ return 0;
+
+ float area = 0;
+ for (int i = 0; i < num_points; ++i)
+ area += WedgeProduct(points[i], points[(i+1)%num_points]);
+ return std::abs(0.5f * area);
+}
+
+// Takes a given quad, maps it by the given transformation, and gives the area
+// of the resulting polygon.
+static inline float AreaOfMappedQuad(const gfx::Transform& transform,
+ const gfx::QuadF& quad) {
+ gfx::PointF clipped_quad[8];
+ int num_vertices_in_clipped_quad = 0;
+ MathUtil::MapClippedQuad(transform,
+ quad,
+ clipped_quad,
+ &num_vertices_in_clipped_quad);
+ return PolygonArea(clipped_quad, num_vertices_in_clipped_quad);
+}
+
+void OverdrawMetrics::DidPaint(gfx::Rect painted_rect) {
+ if (!record_metrics_for_frame_)
+ return;
+
+ pixels_painted_ +=
+ static_cast<float>(painted_rect.width()) * painted_rect.height();
+}
+
+void OverdrawMetrics::DidCullTilesForUpload(int count) {
+ if (record_metrics_for_frame_)
+ tiles_culled_for_upload_ += count;
+}
+
+void OverdrawMetrics::DidUpload(const gfx::Transform& transform_to_target,
+ gfx::Rect upload_rect,
+ gfx::Rect opaque_rect) {
+ if (!record_metrics_for_frame_)
+ return;
+
+ float upload_area =
+ AreaOfMappedQuad(transform_to_target, gfx::QuadF(upload_rect));
+ float upload_opaque_area =
+ AreaOfMappedQuad(transform_to_target,
+ gfx::QuadF(gfx::IntersectRects(opaque_rect,
+ upload_rect)));
+
+ pixels_uploaded_opaque_ += upload_opaque_area;
+ pixels_uploaded_translucent_ += upload_area - upload_opaque_area;
+}
+
+void OverdrawMetrics::DidUseContentsTextureMemoryBytes(
+ size_t contents_texture_use_bytes) {
+ if (!record_metrics_for_frame_)
+ return;
+
+ contents_texture_use_bytes_ += contents_texture_use_bytes;
+}
+
+void OverdrawMetrics::DidUseRenderSurfaceTextureMemoryBytes(
+ size_t render_surface_use_bytes) {
+ if (!record_metrics_for_frame_)
+ return;
+
+ render_surface_texture_use_bytes_ += render_surface_use_bytes;
+}
+
+void OverdrawMetrics::DidCullForDrawing(
+ const gfx::Transform& transform_to_target,
+ gfx::Rect before_cull_rect,
+ gfx::Rect after_cull_rect) {
+ if (!record_metrics_for_frame_)
+ return;
+
+ float before_cull_area =
+ AreaOfMappedQuad(transform_to_target, gfx::QuadF(before_cull_rect));
+ float after_cull_area =
+ AreaOfMappedQuad(transform_to_target, gfx::QuadF(after_cull_rect));
+
+ pixels_culled_for_drawing_ += before_cull_area - after_cull_area;
+}
+
+void OverdrawMetrics::DidDraw(const gfx::Transform& transform_to_target,
+ gfx::Rect after_cull_rect,
+ gfx::Rect opaque_rect) {
+ if (!record_metrics_for_frame_)
+ return;
+
+ float after_cull_area =
+ AreaOfMappedQuad(transform_to_target, gfx::QuadF(after_cull_rect));
+ float after_cull_opaque_area =
+ AreaOfMappedQuad(transform_to_target,
+ gfx::QuadF(gfx::IntersectRects(opaque_rect,
+ after_cull_rect)));
+
+ pixels_drawn_opaque_ += after_cull_opaque_area;
+ pixels_drawn_translucent_ += after_cull_area - after_cull_opaque_area;
+}
+
+void OverdrawMetrics::RecordMetrics(
+ const LayerTreeHost* layer_tree_host) const {
+ if (record_metrics_for_frame_)
+ RecordMetricsInternal<LayerTreeHost>(UpdateAndCommit, layer_tree_host);
+}
+
+void OverdrawMetrics::RecordMetrics(
+ const LayerTreeHostImpl* layer_tree_host_impl) const {
+ if (record_metrics_for_frame_) {
+ RecordMetricsInternal<LayerTreeHostImpl>(DrawingToScreen,
+ layer_tree_host_impl);
+ }
+}
+
+static gfx::Size DeviceViewportSize(const LayerTreeHost* host) {
+ return host->device_viewport_size();
+}
+static gfx::Size DeviceViewportSize(const LayerTreeHostImpl* host_impl) {
+ return host_impl->device_viewport_size();
+}
+
+template <typename LayerTreeHostType>
+void OverdrawMetrics::RecordMetricsInternal(
+ MetricsType metrics_type,
+ const LayerTreeHostType* layer_tree_host) const {
+ // This gives approximately 10x the percentage of pixels to fill the viewport
+ // once.
+ float normalization = 1000.f / (DeviceViewportSize(layer_tree_host).width() *
+ DeviceViewportSize(layer_tree_host).height());
+ // This gives approximately 100x the percentage of tiles to fill the viewport
+ // once, if all tiles were 256x256.
+ float tile_normalization =
+ 10000.f / (DeviceViewportSize(layer_tree_host).width() / 256.f *
+ DeviceViewportSize(layer_tree_host).height() / 256.f);
+ // This gives approximately 10x the percentage of bytes to fill the viewport
+ // once, assuming 4 bytes per pixel.
+ float byte_normalization = normalization / 4;
+
+ switch (metrics_type) {
+ case DrawingToScreen: {
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Renderer4.pixelCountOpaque_Draw",
+ static_cast<int>(normalization * pixels_drawn_opaque_),
+ 100, 1000000, 50);
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Renderer4.pixelCountTranslucent_Draw",
+ static_cast<int>(normalization * pixels_drawn_translucent_),
+ 100, 1000000, 50);
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Renderer4.pixelCountCulled_Draw",
+ static_cast<int>(normalization * pixels_culled_for_drawing_),
+ 100, 1000000, 50);
+
+ TRACE_COUNTER_ID1("cc",
+ "DrawPixelsCulled",
+ layer_tree_host,
+ pixels_culled_for_drawing_);
+ TRACE_EVENT2("cc",
+ "OverdrawMetrics",
+ "PixelsDrawnOpaque",
+ pixels_drawn_opaque_,
+ "PixelsDrawnTranslucent",
+ pixels_drawn_translucent_);
+ break;
+ }
+ case UpdateAndCommit: {
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Renderer4.pixelCountPainted",
+ static_cast<int>(normalization * pixels_painted_),
+ 100, 1000000, 50);
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Renderer4.pixelCountOpaque_Upload",
+ static_cast<int>(normalization * pixels_uploaded_opaque_),
+ 100, 1000000, 50);
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Renderer4.pixelCountTranslucent_Upload",
+ static_cast<int>(normalization * pixels_uploaded_translucent_),
+ 100, 1000000, 50);
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Renderer4.tileCountCulled_Upload",
+ static_cast<int>(tile_normalization * tiles_culled_for_upload_),
+ 100, 10000000, 50);
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Renderer4.renderSurfaceTextureBytes_ViewportScaled",
+ static_cast<int>(
+ byte_normalization * render_surface_texture_use_bytes_),
+ 10, 1000000, 50);
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Renderer4.renderSurfaceTextureBytes_Unscaled",
+ static_cast<int>(render_surface_texture_use_bytes_ / 1000),
+ 1000, 100000000, 50);
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Renderer4.contentsTextureBytes_ViewportScaled",
+ static_cast<int>(byte_normalization * contents_texture_use_bytes_),
+ 10, 1000000, 50);
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Renderer4.contentsTextureBytes_Unscaled",
+ static_cast<int>(contents_texture_use_bytes_ / 1000),
+ 1000, 100000000, 50);
+ {
+ TRACE_COUNTER_ID1("cc",
+ "UploadTilesCulled",
+ layer_tree_host,
+ tiles_culled_for_upload_);
+ TRACE_EVENT2("cc",
+ "OverdrawMetrics",
+ "PixelsUploadedOpaque",
+ pixels_uploaded_opaque_,
+ "PixelsUploadedTranslucent",
+ pixels_uploaded_translucent_);
+ }
+ {
+ // This must be in a different scope than the TRACE_EVENT2 above.
+ TRACE_EVENT1("cc",
+ "OverdrawPaintMetrics",
+ "PixelsPainted",
+ pixels_painted_);
+ }
+ {
+ // This must be in a different scope than the TRACE_EVENTs above.
+ TRACE_EVENT2("cc",
+ "OverdrawPaintMetrics",
+ "ContentsTextureBytes",
+ contents_texture_use_bytes_,
+ "RenderSurfaceTextureBytes",
+ render_surface_texture_use_bytes_);
+ }
+ break;
+ }
+ }
+}
+
+} // namespace cc
diff --git a/chromium/cc/debug/overdraw_metrics.h b/chromium/cc/debug/overdraw_metrics.h
new file mode 100644
index 00000000000..43bd19baa73
--- /dev/null
+++ b/chromium/cc/debug/overdraw_metrics.h
@@ -0,0 +1,115 @@
+// 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.
+
+#ifndef CC_DEBUG_OVERDRAW_METRICS_H_
+#define CC_DEBUG_OVERDRAW_METRICS_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace gfx {
+class Rect;
+class Transform;
+}
+
+namespace cc {
+class LayerTreeHost;
+class LayerTreeHostImpl;
+
+class OverdrawMetrics {
+ public:
+ static scoped_ptr<OverdrawMetrics> Create(bool record_metrics_for_frame) {
+ return make_scoped_ptr(new OverdrawMetrics(record_metrics_for_frame));
+ }
+
+ // These methods are used for saving metrics during update/commit.
+
+ // Record pixels painted by WebKit into the texture updater, but does not mean
+ // the pixels were rasterized in main memory.
+ void DidPaint(gfx::Rect painted_rect);
+ // Records that an invalid tile was culled and did not need to be
+ // painted/uploaded, and did not contribute to other tiles needing to be
+ // painted.
+ void DidCullTilesForUpload(int count);
+ // Records pixels that were uploaded to texture memory.
+ void DidUpload(const gfx::Transform& transform_to_target,
+ gfx::Rect upload_rect,
+ gfx::Rect opaque_rect);
+ // Record contents texture(s) behind present using the given number of bytes.
+ void DidUseContentsTextureMemoryBytes(size_t contents_texture_use_bytes);
+ // Record RenderSurfaceImpl texture(s) being present using the given number of
+ // bytes.
+ void DidUseRenderSurfaceTextureMemoryBytes(size_t render_surface_use_bytes);
+
+ // These methods are used for saving metrics during draw.
+
+ // Record pixels that were not drawn to screen.
+ void DidCullForDrawing(const gfx::Transform& transform_to_target,
+ gfx::Rect before_cull_rect,
+ gfx::Rect after_cull_rect);
+ // Record pixels that were drawn to screen.
+ void DidDraw(const gfx::Transform& transform_to_target,
+ gfx::Rect after_cull_rect,
+ gfx::Rect opaque_rect);
+
+ void RecordMetrics(const LayerTreeHost* layer_tree_host) const;
+ void RecordMetrics(const LayerTreeHostImpl* layer_tree_host_impl) const;
+
+ // Accessors for tests.
+ float pixels_drawn_opaque() const { return pixels_drawn_opaque_; }
+ float pixels_drawn_translucent() const { return pixels_drawn_translucent_; }
+ float pixels_culled_for_drawing() const { return pixels_culled_for_drawing_; }
+ float pixels_painted() const { return pixels_painted_; }
+ float pixels_uploaded_opaque() const { return pixels_uploaded_opaque_; }
+ float pixels_uploaded_translucent() const {
+ return pixels_uploaded_translucent_;
+ }
+ int tiles_culled_for_upload() const { return tiles_culled_for_upload_; }
+
+ private:
+ enum MetricsType {
+ UpdateAndCommit,
+ DrawingToScreen
+ };
+
+ explicit OverdrawMetrics(bool record_metrics_for_frame);
+
+ template <typename LayerTreeHostType>
+ void RecordMetricsInternal(MetricsType metrics_type,
+ const LayerTreeHostType* layer_tree_host) const;
+
+ // When false this class is a giant no-op.
+ bool record_metrics_for_frame_;
+
+ // These values are used for saving metrics during update/commit.
+
+ // Count of pixels that were painted due to invalidation.
+ float pixels_painted_;
+ // Count of pixels uploaded to textures and known to be opaque.
+ float pixels_uploaded_opaque_;
+ // Count of pixels uploaded to textures and not known to be opaque.
+ float pixels_uploaded_translucent_;
+ // Count of tiles that were invalidated but not uploaded.
+ int tiles_culled_for_upload_;
+ // Count the number of bytes in contents textures.
+ uint64 contents_texture_use_bytes_;
+ // Count the number of bytes in RenderSurfaceImpl textures.
+ uint64 render_surface_texture_use_bytes_;
+
+ // These values are used for saving metrics during draw.
+
+ // Count of pixels that are opaque (and thus occlude). Ideally this is no more
+ // than wiewport width x height.
+ float pixels_drawn_opaque_;
+ // Count of pixels that are possibly translucent, and cannot occlude.
+ float pixels_drawn_translucent_;
+ // Count of pixels not drawn as they are occluded by somthing opaque.
+ float pixels_culled_for_drawing_;
+
+ DISALLOW_COPY_AND_ASSIGN(OverdrawMetrics);
+};
+
+} // namespace cc
+
+#endif // CC_DEBUG_OVERDRAW_METRICS_H_
diff --git a/chromium/cc/debug/paint_time_counter.cc b/chromium/cc/debug/paint_time_counter.cc
new file mode 100644
index 00000000000..7d5415427ff
--- /dev/null
+++ b/chromium/cc/debug/paint_time_counter.cc
@@ -0,0 +1,51 @@
+// 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 "cc/debug/paint_time_counter.h"
+
+namespace cc {
+
+// static
+scoped_ptr<PaintTimeCounter> PaintTimeCounter::Create() {
+ return make_scoped_ptr(new PaintTimeCounter());
+}
+
+PaintTimeCounter::PaintTimeCounter()
+ : can_save_paint_time_delta_(false) {
+}
+
+void PaintTimeCounter::SavePaintTime(const base::TimeDelta& total_paint_time) {
+ if (can_save_paint_time_delta_) {
+ base::TimeDelta paint_time = total_paint_time - last_total_paint_time_;
+ ring_buffer_.SaveToBuffer(paint_time);
+ }
+
+ last_total_paint_time_ = total_paint_time;
+ can_save_paint_time_delta_ = true;
+}
+
+void PaintTimeCounter::GetMinAndMaxPaintTime(base::TimeDelta* min,
+ base::TimeDelta* max) const {
+ *min = base::TimeDelta::FromDays(1);
+ *max = base::TimeDelta();
+
+ for (RingBufferType::Iterator it = ring_buffer_.Begin(); it; ++it) {
+ const base::TimeDelta paint_time = **it;
+
+ if (paint_time < *min)
+ *min = paint_time;
+ if (paint_time > *max)
+ *max = paint_time;
+ }
+
+ if (*min > *max)
+ *min = *max;
+}
+
+void PaintTimeCounter::ClearHistory() {
+ ring_buffer_.Clear();
+ can_save_paint_time_delta_ = false;
+}
+
+} // namespace cc
diff --git a/chromium/cc/debug/paint_time_counter.h b/chromium/cc/debug/paint_time_counter.h
new file mode 100644
index 00000000000..8d109d03f73
--- /dev/null
+++ b/chromium/cc/debug/paint_time_counter.h
@@ -0,0 +1,47 @@
+// Copyright 2013 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.
+
+#ifndef CC_DEBUG_PAINT_TIME_COUNTER_H_
+#define CC_DEBUG_PAINT_TIME_COUNTER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "cc/debug/ring_buffer.h"
+
+namespace cc {
+
+// Maintains a history of paint times for each frame
+class PaintTimeCounter {
+ public:
+ static scoped_ptr<PaintTimeCounter> Create();
+
+ size_t HistorySize() const { return ring_buffer_.BufferSize(); }
+
+ // n = 0 returns the oldest and
+ // n = PaintTimeHistorySize() - 1 the most recent paint time.
+ base::TimeDelta GetPaintTimeOfRecentFrame(const size_t& n) const;
+
+ void SavePaintTime(const base::TimeDelta& total_paint_time);
+ void GetMinAndMaxPaintTime(base::TimeDelta* min, base::TimeDelta* max) const;
+
+ void ClearHistory();
+
+ typedef RingBuffer<base::TimeDelta, 200> RingBufferType;
+ RingBufferType::Iterator Begin() const { return ring_buffer_.Begin(); }
+ RingBufferType::Iterator End() const { return ring_buffer_.End(); }
+
+ private:
+ PaintTimeCounter();
+
+ RingBufferType ring_buffer_;
+ base::TimeDelta last_total_paint_time_;
+ bool can_save_paint_time_delta_;
+
+ DISALLOW_COPY_AND_ASSIGN(PaintTimeCounter);
+};
+
+} // namespace cc
+
+#endif // CC_DEBUG_PAINT_TIME_COUNTER_H_
diff --git a/chromium/cc/debug/rendering_stats.cc b/chromium/cc/debug/rendering_stats.cc
new file mode 100644
index 00000000000..12c54530c71
--- /dev/null
+++ b/chromium/cc/debug/rendering_stats.cc
@@ -0,0 +1,99 @@
+// 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 "cc/debug/rendering_stats.h"
+
+namespace cc {
+
+RenderingStats::RenderingStats()
+ : animation_frame_count(0),
+ screen_frame_count(0),
+ dropped_frame_count(0),
+ total_commit_count(0),
+ total_pixels_painted(0),
+ total_pixels_recorded(0),
+ total_pixels_rasterized(0),
+ num_impl_thread_scrolls(0),
+ num_main_thread_scrolls(0),
+ num_layers_drawn(0),
+ num_missing_tiles(0),
+ total_deferred_image_decode_count(0),
+ total_deferred_image_cache_hit_count(0),
+ total_image_gathering_count(0),
+ total_tiles_analyzed(0),
+ solid_color_tiles_analyzed(0) {}
+
+void RenderingStats::EnumerateFields(Enumerator* enumerator) const {
+ enumerator->AddInt64("numAnimationFrames", animation_frame_count);
+ enumerator->AddInt64("numFramesSentToScreen", screen_frame_count);
+ enumerator->AddInt64("droppedFrameCount", dropped_frame_count);
+ enumerator->AddDouble("totalPaintTimeInSeconds",
+ total_paint_time.InSecondsF());
+ enumerator->AddDouble("totalRecordTimeInSeconds",
+ total_record_time.InSecondsF());
+ enumerator->AddDouble("totalRasterizeTimeInSeconds",
+ total_rasterize_time.InSecondsF());
+ enumerator->AddDouble(
+ "totalRasterizeTimeForNowBinsOnPendingTree",
+ total_rasterize_time_for_now_bins_on_pending_tree.InSecondsF());
+ enumerator->AddDouble("totalCommitTimeInSeconds",
+ total_commit_time.InSecondsF());
+ enumerator->AddDouble("bestRasterizeTimeInSeconds",
+ best_rasterize_time.InSecondsF());
+ enumerator->AddInt64("totalCommitCount", total_commit_count);
+ enumerator->AddInt64("totalPixelsPainted", total_pixels_painted);
+ enumerator->AddInt64("totalPixelsRecorded", total_pixels_recorded);
+ enumerator->AddInt64("totalPixelsRasterized", total_pixels_rasterized);
+ enumerator->AddInt64("numImplThreadScrolls", num_impl_thread_scrolls);
+ enumerator->AddInt64("numMainThreadScrolls", num_main_thread_scrolls);
+ enumerator->AddInt64("numLayersDrawn", num_layers_drawn);
+ enumerator->AddInt64("numMissingTiles", num_missing_tiles);
+ enumerator->AddInt64("totalDeferredImageDecodeCount",
+ total_deferred_image_decode_count);
+ enumerator->AddInt64("totalTilesAnalyzed", total_tiles_analyzed);
+ enumerator->AddInt64("solidColorTilesAnalyzed",
+ solid_color_tiles_analyzed);
+ enumerator->AddInt64("totalDeferredImageCacheHitCount",
+ total_deferred_image_cache_hit_count);
+ enumerator->AddInt64("totalImageGatheringCount",
+ total_image_gathering_count);
+ enumerator->AddDouble("totalDeferredImageDecodeTimeInSeconds",
+ total_deferred_image_decode_time.InSecondsF());
+ enumerator->AddDouble("totalImageGatheringTimeInSeconds",
+ total_image_gathering_time.InSecondsF());
+ enumerator->AddDouble("totalTileAnalysisTimeInSeconds",
+ total_tile_analysis_time.InSecondsF());
+}
+
+void RenderingStats::Add(const RenderingStats& other) {
+ animation_frame_count += other.animation_frame_count;
+ screen_frame_count += other.screen_frame_count;
+ dropped_frame_count += other.dropped_frame_count;
+ total_paint_time += other.total_paint_time;
+ total_record_time += other.total_record_time;
+ total_rasterize_time += other.total_rasterize_time;
+ total_rasterize_time_for_now_bins_on_pending_tree +=
+ other.total_rasterize_time_for_now_bins_on_pending_tree;
+ total_commit_time += other.total_commit_time;
+ best_rasterize_time += other.best_rasterize_time;
+ total_commit_count += other.total_commit_count;
+ total_pixels_painted += other.total_pixels_painted;
+ total_pixels_recorded += other.total_pixels_recorded;
+ total_pixels_rasterized += other.total_pixels_rasterized;
+ num_impl_thread_scrolls += other.num_impl_thread_scrolls;
+ num_main_thread_scrolls += other.num_main_thread_scrolls;
+ num_layers_drawn += other.num_layers_drawn;
+ num_missing_tiles += other.num_missing_tiles;
+ total_deferred_image_decode_count += other.total_deferred_image_decode_count;
+ total_deferred_image_cache_hit_count +=
+ other.total_deferred_image_cache_hit_count;
+ total_image_gathering_count += other.total_image_gathering_count;
+ total_deferred_image_decode_time += other.total_deferred_image_decode_time;
+ total_image_gathering_time += other.total_image_gathering_time;
+ total_tiles_analyzed += other.total_tiles_analyzed;
+ solid_color_tiles_analyzed += other.solid_color_tiles_analyzed;
+ total_tile_analysis_time += other.total_tile_analysis_time;
+}
+
+} // namespace cc
diff --git a/chromium/cc/debug/rendering_stats.h b/chromium/cc/debug/rendering_stats.h
new file mode 100644
index 00000000000..eb01a571b11
--- /dev/null
+++ b/chromium/cc/debug/rendering_stats.h
@@ -0,0 +1,70 @@
+// 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.
+
+#ifndef CC_DEBUG_RENDERING_STATS_H_
+#define CC_DEBUG_RENDERING_STATS_H_
+
+#include "base/basictypes.h"
+#include "base/time/time.h"
+#include "cc/base/cc_export.h"
+
+namespace cc {
+
+struct CC_EXPORT RenderingStats {
+ int64 animation_frame_count;
+ int64 screen_frame_count;
+ int64 dropped_frame_count;
+ base::TimeDelta total_paint_time;
+ base::TimeDelta total_record_time;
+ base::TimeDelta total_rasterize_time;
+ base::TimeDelta total_rasterize_time_for_now_bins_on_pending_tree;
+ base::TimeDelta total_commit_time;
+ base::TimeDelta best_rasterize_time;
+ int64 total_commit_count;
+ int64 total_pixels_painted;
+ int64 total_pixels_recorded;
+ int64 total_pixels_rasterized;
+ int64 num_impl_thread_scrolls;
+ int64 num_main_thread_scrolls;
+ int64 num_layers_drawn;
+ int64 num_missing_tiles;
+ int64 total_deferred_image_decode_count;
+ int64 total_deferred_image_cache_hit_count;
+ int64 total_image_gathering_count;
+ int64 total_tiles_analyzed;
+ int64 solid_color_tiles_analyzed;
+ base::TimeDelta total_deferred_image_decode_time;
+ base::TimeDelta total_image_gathering_time;
+ base::TimeDelta total_tile_analysis_time;
+ // Note: when adding new members, please remember to update EnumerateFields
+ // and Add in rendering_stats.cc.
+
+ RenderingStats();
+
+ // In conjunction with EnumerateFields, this allows the embedder to
+ // enumerate the values in this structure without
+ // having to embed references to its specific member variables. This
+ // simplifies the addition of new fields to this type.
+ class Enumerator {
+ public:
+ virtual void AddInt64(const char* name, int64 value) = 0;
+ virtual void AddDouble(const char* name, double value) = 0;
+ virtual void AddInt(const char* name, int value) = 0;
+ virtual void AddTimeDeltaInSecondsF(const char* name,
+ const base::TimeDelta& value) = 0;
+
+ protected:
+ virtual ~Enumerator() {}
+ };
+
+ // Outputs the fields in this structure to the provided enumerator.
+ void EnumerateFields(Enumerator* enumerator) const;
+
+ // Add fields of |other| to the fields in this structure.
+ void Add(const RenderingStats& other);
+};
+
+} // namespace cc
+
+#endif // CC_DEBUG_RENDERING_STATS_H_
diff --git a/chromium/cc/debug/rendering_stats_instrumentation.cc b/chromium/cc/debug/rendering_stats_instrumentation.cc
new file mode 100644
index 00000000000..b3886af51c0
--- /dev/null
+++ b/chromium/cc/debug/rendering_stats_instrumentation.cc
@@ -0,0 +1,183 @@
+// Copyright 2013 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 "cc/debug/rendering_stats_instrumentation.h"
+
+namespace cc {
+
+// static
+scoped_ptr<RenderingStatsInstrumentation>
+ RenderingStatsInstrumentation::Create() {
+ return make_scoped_ptr(new RenderingStatsInstrumentation());
+}
+
+RenderingStatsInstrumentation::RenderingStatsInstrumentation()
+ : record_rendering_stats_(false) {
+}
+
+RenderingStatsInstrumentation::~RenderingStatsInstrumentation() {}
+
+RenderingStats RenderingStatsInstrumentation::GetRenderingStats() {
+ base::AutoLock scoped_lock(lock_);
+ return rendering_stats_;
+}
+
+base::TimeTicks RenderingStatsInstrumentation::StartRecording() const {
+ if (record_rendering_stats_)
+ return base::TimeTicks::HighResNow();
+ return base::TimeTicks();
+}
+
+base::TimeDelta RenderingStatsInstrumentation::EndRecording(
+ base::TimeTicks start_time) const {
+ if (!start_time.is_null())
+ return base::TimeTicks::HighResNow() - start_time;
+ return base::TimeDelta();
+}
+
+void RenderingStatsInstrumentation::IncrementAnimationFrameCount() {
+ if (!record_rendering_stats_)
+ return;
+
+ base::AutoLock scoped_lock(lock_);
+ rendering_stats_.animation_frame_count++;
+}
+
+void RenderingStatsInstrumentation::SetScreenFrameCount(int64 count) {
+ if (!record_rendering_stats_)
+ return;
+
+ base::AutoLock scoped_lock(lock_);
+ rendering_stats_.screen_frame_count = count;
+}
+
+void RenderingStatsInstrumentation::SetDroppedFrameCount(int64 count) {
+ if (!record_rendering_stats_)
+ return;
+
+ base::AutoLock scoped_lock(lock_);
+ rendering_stats_.dropped_frame_count = count;
+}
+
+void RenderingStatsInstrumentation::AddCommit(base::TimeDelta duration) {
+ if (!record_rendering_stats_)
+ return;
+
+ base::AutoLock scoped_lock(lock_);
+ rendering_stats_.total_commit_time += duration;
+ rendering_stats_.total_commit_count++;
+}
+
+void RenderingStatsInstrumentation::AddPaint(base::TimeDelta duration,
+ int64 pixels) {
+ if (!record_rendering_stats_)
+ return;
+
+ base::AutoLock scoped_lock(lock_);
+ rendering_stats_.total_paint_time += duration;
+ rendering_stats_.total_pixels_painted += pixels;
+}
+
+void RenderingStatsInstrumentation::AddRecord(base::TimeDelta duration,
+ int64 pixels) {
+ if (!record_rendering_stats_)
+ return;
+
+ base::AutoLock scoped_lock(lock_);
+ rendering_stats_.total_record_time += duration;
+ rendering_stats_.total_pixels_recorded += pixels;
+}
+
+void RenderingStatsInstrumentation::AddRaster(base::TimeDelta total_duration,
+ base::TimeDelta best_duration,
+ int64 pixels,
+ bool is_in_pending_tree_now_bin) {
+ if (!record_rendering_stats_)
+ return;
+
+ base::AutoLock scoped_lock(lock_);
+ rendering_stats_.total_rasterize_time += total_duration;
+ rendering_stats_.best_rasterize_time += best_duration;
+ rendering_stats_.total_pixels_rasterized += pixels;
+
+ if (is_in_pending_tree_now_bin) {
+ rendering_stats_.total_rasterize_time_for_now_bins_on_pending_tree +=
+ total_duration;
+ }
+}
+
+void RenderingStatsInstrumentation::IncrementImplThreadScrolls() {
+ if (!record_rendering_stats_)
+ return;
+
+ base::AutoLock scoped_lock(lock_);
+ rendering_stats_.num_impl_thread_scrolls++;
+}
+
+void RenderingStatsInstrumentation::IncrementMainThreadScrolls() {
+ if (!record_rendering_stats_)
+ return;
+
+ base::AutoLock scoped_lock(lock_);
+ rendering_stats_.num_main_thread_scrolls++;
+}
+
+void RenderingStatsInstrumentation::AddLayersDrawn(int64 amount) {
+ if (!record_rendering_stats_)
+ return;
+
+ base::AutoLock scoped_lock(lock_);
+ rendering_stats_.num_layers_drawn += amount;
+}
+
+void RenderingStatsInstrumentation::AddMissingTiles(int64 amount) {
+ if (!record_rendering_stats_)
+ return;
+
+ base::AutoLock scoped_lock(lock_);
+ rendering_stats_.num_missing_tiles += amount;
+}
+
+void RenderingStatsInstrumentation::AddDeferredImageDecode(
+ base::TimeDelta duration) {
+ if (!record_rendering_stats_)
+ return;
+
+ base::AutoLock scoped_lock(lock_);
+ rendering_stats_.total_deferred_image_decode_time += duration;
+ rendering_stats_.total_deferred_image_decode_count++;
+}
+
+void RenderingStatsInstrumentation::AddImageGathering(
+ base::TimeDelta duration) {
+ if (!record_rendering_stats_)
+ return;
+
+ base::AutoLock scoped_lock(lock_);
+ rendering_stats_.total_image_gathering_time += duration;
+ rendering_stats_.total_image_gathering_count++;
+}
+
+void RenderingStatsInstrumentation::IncrementDeferredImageCacheHitCount() {
+ if (!record_rendering_stats_)
+ return;
+
+ base::AutoLock scoped_lock(lock_);
+ rendering_stats_.total_deferred_image_cache_hit_count++;
+}
+
+void RenderingStatsInstrumentation::AddAnalysisResult(
+ base::TimeDelta duration,
+ bool is_solid_color) {
+ if (!record_rendering_stats_)
+ return;
+
+ base::AutoLock scoped_lock(lock_);
+ rendering_stats_.total_tiles_analyzed++;
+ rendering_stats_.total_tile_analysis_time += duration;
+ if (is_solid_color)
+ rendering_stats_.solid_color_tiles_analyzed++;
+}
+
+} // namespace cc
diff --git a/chromium/cc/debug/rendering_stats_instrumentation.h b/chromium/cc/debug/rendering_stats_instrumentation.h
new file mode 100644
index 00000000000..6f7515a9418
--- /dev/null
+++ b/chromium/cc/debug/rendering_stats_instrumentation.h
@@ -0,0 +1,74 @@
+// Copyright 2013 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.
+
+#ifndef CC_DEBUG_RENDERING_STATS_INSTRUMENTATION_H_
+#define CC_DEBUG_RENDERING_STATS_INSTRUMENTATION_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/lock.h"
+#include "cc/debug/rendering_stats.h"
+
+namespace cc {
+
+// RenderingStatsInstrumentation is shared among threads and manages conditional
+// recording of rendering stats into a private RenderingStats instance.
+class CC_EXPORT RenderingStatsInstrumentation {
+ public:
+ static scoped_ptr<RenderingStatsInstrumentation> Create();
+ virtual ~RenderingStatsInstrumentation();
+
+ RenderingStats GetRenderingStats();
+
+ // Read and write access to the record_rendering_stats_ flag is not locked to
+ // improve performance. The flag is commonly turned off and hardly changes
+ // it's value during runtime.
+ bool record_rendering_stats() const { return record_rendering_stats_; }
+ void set_record_rendering_stats(bool record_rendering_stats) {
+ if (record_rendering_stats_ != record_rendering_stats)
+ record_rendering_stats_ = record_rendering_stats;
+ }
+
+ base::TimeTicks StartRecording() const;
+ base::TimeDelta EndRecording(base::TimeTicks start_time) const;
+
+ void IncrementAnimationFrameCount();
+ void SetScreenFrameCount(int64 count);
+ void SetDroppedFrameCount(int64 count);
+
+ void AddCommit(base::TimeDelta duration);
+ void AddPaint(base::TimeDelta duration, int64 pixels);
+ void AddRecord(base::TimeDelta duration, int64 pixels);
+ void AddRaster(base::TimeDelta total_duraction,
+ base::TimeDelta best_duration,
+ int64 pixels,
+ bool is_in_pending_tree_now_bin);
+
+ void IncrementImplThreadScrolls();
+ void IncrementMainThreadScrolls();
+
+ void AddLayersDrawn(int64 amount);
+ void AddMissingTiles(int64 amount);
+
+ void AddDeferredImageDecode(base::TimeDelta duration);
+ void AddImageGathering(base::TimeDelta duration);
+
+ void IncrementDeferredImageCacheHitCount();
+
+ void AddAnalysisResult(base::TimeDelta duration, bool is_solid_color);
+
+ protected:
+ RenderingStatsInstrumentation();
+
+ private:
+ RenderingStats rendering_stats_;
+ bool record_rendering_stats_;
+
+ base::Lock lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderingStatsInstrumentation);
+};
+
+} // namespace cc
+
+#endif // CC_DEBUG_RENDERING_STATS_INSTRUMENTATION_H_
diff --git a/chromium/cc/debug/ring_buffer.h b/chromium/cc/debug/ring_buffer.h
new file mode 100644
index 00000000000..94d8459acd7
--- /dev/null
+++ b/chromium/cc/debug/ring_buffer.h
@@ -0,0 +1,122 @@
+// Copyright 2013 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.
+
+#ifndef CC_DEBUG_RING_BUFFER_H_
+#define CC_DEBUG_RING_BUFFER_H_
+
+#include "base/logging.h"
+
+namespace cc {
+
+template<typename T, size_t kSize>
+class RingBuffer {
+ public:
+ explicit RingBuffer()
+ : current_index_(0) {
+ }
+
+ size_t BufferSize() const {
+ return kSize;
+ }
+
+ size_t CurrentIndex() const {
+ return current_index_;
+ }
+
+ // tests if a value was saved to this index
+ bool IsFilledIndex(size_t n) const {
+ return BufferIndex(n) < current_index_;
+ }
+
+ // n = 0 returns the oldest value and
+ // n = bufferSize() - 1 returns the most recent value.
+ const T& ReadBuffer(size_t n) const {
+ DCHECK(IsFilledIndex(n));
+ return buffer_[BufferIndex(n)];
+ }
+
+ T* MutableReadBuffer(size_t n) {
+ DCHECK(IsFilledIndex(n));
+ return &buffer_[BufferIndex(n)];
+ }
+
+ void SaveToBuffer(const T& value) {
+ buffer_[BufferIndex(0)] = value;
+ current_index_++;
+ }
+
+ void Clear() {
+ current_index_ = 0;
+ }
+
+ // Iterator has const access to the RingBuffer it got retrieved from.
+ class Iterator {
+ public:
+ size_t index() const { return index_; }
+
+ const T* operator->() const { return &buffer_.ReadBuffer(index_); }
+ const T* operator*() const { return &buffer_.ReadBuffer(index_); }
+
+ Iterator& operator++() {
+ index_++;
+ if (index_ == kSize)
+ out_of_range_ = true;
+ return *this;
+ }
+
+ Iterator& operator--() {
+ if (index_ == 0)
+ out_of_range_ = true;
+ index_--;
+ return *this;
+ }
+
+ operator bool() const {
+ return buffer_.IsFilledIndex(index_) && !out_of_range_;
+ }
+
+ private:
+ Iterator(const RingBuffer<T, kSize>& buffer, size_t index)
+ : buffer_(buffer),
+ index_(index),
+ out_of_range_(false) {
+ }
+
+ const RingBuffer<T, kSize>& buffer_;
+ size_t index_;
+ bool out_of_range_;
+
+ friend class RingBuffer<T, kSize>;
+ };
+
+ // Returns an Iterator pointing to the oldest value in the buffer.
+ // Example usage (iterate from oldest to newest value):
+ // for (RingBuffer<T, kSize>::Iterator it = ring_buffer.Begin(); it; ++it) {}
+ Iterator Begin() const {
+ if (current_index_ < kSize)
+ return Iterator(*this, kSize - current_index_);
+ return Iterator(*this, 0);
+ }
+
+ // Returns an Iterator pointing to the newest value in the buffer.
+ // Example usage (iterate backwards from newest to oldest value):
+ // for (RingBuffer<T, kSize>::Iterator it = ring_buffer.End(); it; --it) {}
+ Iterator End() const {
+ return Iterator(*this, kSize - 1);
+ }
+
+ private:
+ inline size_t BufferIndex(size_t n) const {
+ return (current_index_ + n) % kSize;
+ }
+
+ T buffer_[kSize];
+ size_t current_index_;
+
+ DISALLOW_COPY_AND_ASSIGN(RingBuffer);
+};
+
+} // namespace cc
+
+#endif // CC_DEBUG_RING_BUFFER_H_
diff --git a/chromium/cc/debug/test_context_provider.cc b/chromium/cc/debug/test_context_provider.cc
new file mode 100644
index 00000000000..4a3a2fcee2f
--- /dev/null
+++ b/chromium/cc/debug/test_context_provider.cc
@@ -0,0 +1,107 @@
+// Copyright 2013 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 "cc/debug/test_context_provider.h"
+
+#include "base/logging.h"
+#include "cc/debug/test_web_graphics_context_3d.h"
+
+namespace cc {
+
+// static
+scoped_refptr<TestContextProvider> TestContextProvider::Create() {
+ return Create(TestWebGraphicsContext3D::CreateFactory());
+}
+
+// static
+scoped_refptr<TestContextProvider> TestContextProvider::Create(
+ const CreateCallback& create_callback) {
+ scoped_refptr<TestContextProvider> provider = new TestContextProvider;
+ if (!provider->InitializeOnMainThread(create_callback))
+ return NULL;
+ return provider;
+}
+
+TestContextProvider::TestContextProvider()
+ : bound_(false),
+ destroyed_(false) {
+ DCHECK(main_thread_checker_.CalledOnValidThread());
+ context_thread_checker_.DetachFromThread();
+}
+
+TestContextProvider::~TestContextProvider() {
+ DCHECK(main_thread_checker_.CalledOnValidThread() ||
+ context_thread_checker_.CalledOnValidThread());
+}
+
+bool TestContextProvider::InitializeOnMainThread(
+ const CreateCallback& create_callback) {
+ DCHECK(main_thread_checker_.CalledOnValidThread());
+
+ DCHECK(!context3d_);
+ DCHECK(!create_callback.is_null());
+ context3d_ = create_callback.Run();
+ return context3d_;
+}
+
+bool TestContextProvider::BindToCurrentThread() {
+ DCHECK(context3d_);
+
+ // This is called on the thread the context will be used.
+ DCHECK(context_thread_checker_.CalledOnValidThread());
+
+ if (bound_)
+ return true;
+
+ bound_ = true;
+ if (!context3d_->makeContextCurrent()) {
+ base::AutoLock lock(destroyed_lock_);
+ destroyed_ = true;
+ return false;
+ }
+ return true;
+}
+
+WebKit::WebGraphicsContext3D* TestContextProvider::Context3d() {
+ DCHECK(context3d_);
+ DCHECK(bound_);
+ DCHECK(context_thread_checker_.CalledOnValidThread());
+
+ return context3d_.get();
+}
+
+class GrContext* TestContextProvider::GrContext() {
+ DCHECK(context3d_);
+ DCHECK(bound_);
+ DCHECK(context_thread_checker_.CalledOnValidThread());
+
+ // TODO(danakj): Make a test GrContext that works with a test Context3d.
+ return NULL;
+}
+
+void TestContextProvider::VerifyContexts() {
+ DCHECK(context3d_);
+ DCHECK(bound_);
+ DCHECK(context_thread_checker_.CalledOnValidThread());
+
+ if (context3d_->isContextLost()) {
+ base::AutoLock lock(destroyed_lock_);
+ destroyed_ = true;
+ }
+}
+
+bool TestContextProvider::DestroyedOnMainThread() {
+ DCHECK(main_thread_checker_.CalledOnValidThread());
+
+ base::AutoLock lock(destroyed_lock_);
+ return destroyed_;
+}
+
+void TestContextProvider::SetLostContextCallback(
+ const LostContextCallback& cb) {
+ DCHECK(context_thread_checker_.CalledOnValidThread());
+ NOTIMPLEMENTED();
+}
+
+} // namespace cc
diff --git a/chromium/cc/debug/test_context_provider.h b/chromium/cc/debug/test_context_provider.h
new file mode 100644
index 00000000000..3a95bfbbcb7
--- /dev/null
+++ b/chromium/cc/debug/test_context_provider.h
@@ -0,0 +1,58 @@
+// Copyright 2013 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.
+
+#ifndef CC_DEBUG_TEST_CONTEXT_PROVIDER_H_
+#define CC_DEBUG_TEST_CONTEXT_PROVIDER_H_
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_checker.h"
+#include "cc/base/cc_export.h"
+#include "cc/output/context_provider.h"
+
+namespace WebKit { class WebGraphicsContext3D; }
+
+namespace cc {
+class TestWebGraphicsContext3D;
+
+class CC_EXPORT TestContextProvider
+ : public NON_EXPORTED_BASE(cc::ContextProvider) {
+ public:
+ typedef base::Callback<scoped_ptr<TestWebGraphicsContext3D>(void)>
+ CreateCallback;
+
+ static scoped_refptr<TestContextProvider> Create();
+ static scoped_refptr<TestContextProvider> Create(
+ const CreateCallback& create_callback);
+
+ virtual bool BindToCurrentThread() OVERRIDE;
+ virtual WebKit::WebGraphicsContext3D* Context3d() OVERRIDE;
+ virtual class GrContext* GrContext() OVERRIDE;
+ virtual void VerifyContexts() OVERRIDE;
+ virtual bool DestroyedOnMainThread() OVERRIDE;
+ virtual void SetLostContextCallback(const LostContextCallback& cb) OVERRIDE;
+
+ TestWebGraphicsContext3D* TestContext3d() { return context3d_.get(); }
+
+ protected:
+ TestContextProvider();
+ virtual ~TestContextProvider();
+
+ bool InitializeOnMainThread(const CreateCallback& create_callback);
+
+ scoped_ptr<TestWebGraphicsContext3D> context3d_;
+ bool bound_;
+
+ base::ThreadChecker main_thread_checker_;
+ base::ThreadChecker context_thread_checker_;
+
+ base::Lock destroyed_lock_;
+ bool destroyed_;
+};
+
+} // namespace cc
+
+#endif // CC_DEBUG_TEST_CONTEXT_PROVIDER_H_
+
diff --git a/chromium/cc/debug/test_web_graphics_context_3d.cc b/chromium/cc/debug/test_web_graphics_context_3d.cc
new file mode 100644
index 00000000000..229ef636de3
--- /dev/null
+++ b/chromium/cc/debug/test_web_graphics_context_3d.cc
@@ -0,0 +1,654 @@
+// Copyright 2013 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 "cc/debug/test_web_graphics_context_3d.h"
+
+#include <algorithm>
+#include <string>
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "gpu/GLES2/gl2extchromium.h"
+#include "third_party/khronos/GLES2/gl2ext.h"
+
+using WebKit::WGC3Dboolean;
+using WebKit::WGC3Dchar;
+using WebKit::WGC3Denum;
+using WebKit::WGC3Dint;
+using WebKit::WGC3Dsizei;
+using WebKit::WGC3Dsizeiptr;
+using WebKit::WGC3Duint;
+using WebKit::WebGLId;
+using WebKit::WebGraphicsContext3D;
+
+namespace cc {
+
+static const WebGLId kFramebufferId = 1;
+static const WebGLId kProgramId = 2;
+static const WebGLId kRenderbufferId = 3;
+static const WebGLId kShaderId = 4;
+
+static unsigned s_context_id = 1;
+
+const WebGLId TestWebGraphicsContext3D::kExternalTextureId = 1337;
+
+static base::LazyInstance<base::Lock>::Leaky
+ g_shared_namespace_lock = LAZY_INSTANCE_INITIALIZER;
+
+TestWebGraphicsContext3D::Namespace*
+ TestWebGraphicsContext3D::shared_namespace_ = NULL;
+
+TestWebGraphicsContext3D::Namespace::Namespace()
+ : next_buffer_id(1),
+ next_image_id(1),
+ next_texture_id(1) {
+}
+
+TestWebGraphicsContext3D::Namespace::~Namespace() {
+ g_shared_namespace_lock.Get().AssertAcquired();
+ if (shared_namespace_ == this)
+ shared_namespace_ = NULL;
+}
+
+// static
+scoped_ptr<TestWebGraphicsContext3D> TestWebGraphicsContext3D::Create() {
+ return make_scoped_ptr(new TestWebGraphicsContext3D());
+}
+
+// static
+base::Callback<scoped_ptr<TestWebGraphicsContext3D>()>
+TestWebGraphicsContext3D::CreateFactory() {
+ return base::Bind(&TestWebGraphicsContext3D::Create);
+}
+
+TestWebGraphicsContext3D::TestWebGraphicsContext3D()
+ : FakeWebGraphicsContext3D(),
+ context_id_(s_context_id++),
+ support_swapbuffers_complete_callback_(true),
+ have_extension_io_surface_(false),
+ have_extension_egl_image_(false),
+ times_make_current_succeeds_(-1),
+ times_bind_texture_succeeds_(-1),
+ times_end_query_succeeds_(-1),
+ times_gen_mailbox_succeeds_(-1),
+ context_lost_(false),
+ times_map_image_chromium_succeeds_(-1),
+ times_map_buffer_chromium_succeeds_(-1),
+ context_lost_callback_(NULL),
+ swap_buffers_callback_(NULL),
+ memory_allocation_changed_callback_(NULL),
+ max_texture_size_(1024),
+ width_(0),
+ height_(0),
+ bound_buffer_(0),
+ weak_ptr_factory_(this) {
+ CreateNamespace();
+}
+
+TestWebGraphicsContext3D::TestWebGraphicsContext3D(
+ const WebGraphicsContext3D::Attributes& attributes)
+ : FakeWebGraphicsContext3D(),
+ context_id_(s_context_id++),
+ attributes_(attributes),
+ support_swapbuffers_complete_callback_(true),
+ have_extension_io_surface_(false),
+ have_extension_egl_image_(false),
+ times_make_current_succeeds_(-1),
+ times_bind_texture_succeeds_(-1),
+ times_end_query_succeeds_(-1),
+ times_gen_mailbox_succeeds_(-1),
+ context_lost_(false),
+ times_map_image_chromium_succeeds_(-1),
+ times_map_buffer_chromium_succeeds_(-1),
+ context_lost_callback_(NULL),
+ swap_buffers_callback_(NULL),
+ memory_allocation_changed_callback_(NULL),
+ max_texture_size_(1024),
+ width_(0),
+ height_(0),
+ bound_buffer_(0),
+ weak_ptr_factory_(this) {
+ CreateNamespace();
+}
+
+void TestWebGraphicsContext3D::CreateNamespace() {
+ if (attributes_.shareResources) {
+ base::AutoLock lock(g_shared_namespace_lock.Get());
+ if (shared_namespace_) {
+ namespace_ = shared_namespace_;
+ } else {
+ namespace_ = new Namespace;
+ shared_namespace_ = namespace_.get();
+ }
+ } else {
+ namespace_ = new Namespace;
+ }
+}
+
+TestWebGraphicsContext3D::~TestWebGraphicsContext3D() {
+ for (size_t i = 0; i < sync_point_callbacks_.size(); ++i) {
+ if (sync_point_callbacks_[i] != NULL)
+ delete sync_point_callbacks_[i];
+ }
+ base::AutoLock lock(g_shared_namespace_lock.Get());
+ namespace_ = NULL;
+}
+
+bool TestWebGraphicsContext3D::makeContextCurrent() {
+ if (times_make_current_succeeds_ >= 0) {
+ if (!times_make_current_succeeds_) {
+ loseContextCHROMIUM(GL_GUILTY_CONTEXT_RESET_ARB,
+ GL_INNOCENT_CONTEXT_RESET_ARB);
+ }
+ --times_make_current_succeeds_;
+ }
+ return !context_lost_;
+}
+
+int TestWebGraphicsContext3D::width() {
+ return width_;
+}
+
+int TestWebGraphicsContext3D::height() {
+ return height_;
+}
+
+void TestWebGraphicsContext3D::reshapeWithScaleFactor(
+ int width, int height, float scale_factor) {
+ width_ = width;
+ height_ = height;
+}
+
+bool TestWebGraphicsContext3D::isContextLost() {
+ return context_lost_;
+}
+
+WGC3Denum TestWebGraphicsContext3D::getGraphicsResetStatusARB() {
+ return context_lost_ ? GL_UNKNOWN_CONTEXT_RESET_ARB : GL_NO_ERROR;
+}
+
+WGC3Denum TestWebGraphicsContext3D::checkFramebufferStatus(
+ WGC3Denum target) {
+ if (context_lost_)
+ return GL_FRAMEBUFFER_UNDEFINED_OES;
+ return GL_FRAMEBUFFER_COMPLETE;
+}
+
+WebGraphicsContext3D::Attributes
+ TestWebGraphicsContext3D::getContextAttributes() {
+ return attributes_;
+}
+
+WebKit::WebString TestWebGraphicsContext3D::getString(WGC3Denum name) {
+ std::string string;
+
+ if (support_swapbuffers_complete_callback_)
+ string += "GL_CHROMIUM_swapbuffers_complete_callback";
+
+ if (name == GL_EXTENSIONS) {
+ if (have_extension_io_surface_)
+ string += " GL_CHROMIUM_iosurface GL_ARB_texture_rectangle";
+ if (have_extension_egl_image_)
+ string += " GL_OES_EGL_image_external";
+ }
+
+ return WebKit::WebString::fromUTF8(string.c_str());
+}
+
+WGC3Dint TestWebGraphicsContext3D::getUniformLocation(
+ WebGLId program,
+ const WGC3Dchar* name) {
+ return 0;
+}
+
+WGC3Dsizeiptr TestWebGraphicsContext3D::getVertexAttribOffset(
+ WGC3Duint index,
+ WGC3Denum pname) {
+ return 0;
+}
+
+WGC3Dboolean TestWebGraphicsContext3D::isBuffer(
+ WebGLId buffer) {
+ return false;
+}
+
+WGC3Dboolean TestWebGraphicsContext3D::isEnabled(
+ WGC3Denum cap) {
+ return false;
+}
+
+WGC3Dboolean TestWebGraphicsContext3D::isFramebuffer(
+ WebGLId framebuffer) {
+ return false;
+}
+
+WGC3Dboolean TestWebGraphicsContext3D::isProgram(
+ WebGLId program) {
+ return false;
+}
+
+WGC3Dboolean TestWebGraphicsContext3D::isRenderbuffer(
+ WebGLId renderbuffer) {
+ return false;
+}
+
+WGC3Dboolean TestWebGraphicsContext3D::isShader(
+ WebGLId shader) {
+ return false;
+}
+
+WGC3Dboolean TestWebGraphicsContext3D::isTexture(
+ WebGLId texture) {
+ return false;
+}
+
+WebGLId TestWebGraphicsContext3D::createBuffer() {
+ return NextBufferId();
+}
+
+void TestWebGraphicsContext3D::deleteBuffer(WebGLId id) {
+ base::AutoLock lock(namespace_->lock);
+ unsigned context_id = id >> 17;
+ unsigned buffer_id = id & 0x1ffff;
+ DCHECK(buffer_id && buffer_id < namespace_->next_buffer_id);
+ DCHECK_EQ(context_id, context_id_);
+}
+
+WebGLId TestWebGraphicsContext3D::createFramebuffer() {
+ return kFramebufferId | context_id_ << 16;
+}
+
+void TestWebGraphicsContext3D::deleteFramebuffer(WebGLId id) {
+ DCHECK_EQ(kFramebufferId | context_id_ << 16, id);
+}
+
+WebGLId TestWebGraphicsContext3D::createProgram() {
+ return kProgramId | context_id_ << 16;
+}
+
+void TestWebGraphicsContext3D::deleteProgram(WebGLId id) {
+ DCHECK_EQ(kProgramId | context_id_ << 16, id);
+}
+
+WebGLId TestWebGraphicsContext3D::createRenderbuffer() {
+ return kRenderbufferId | context_id_ << 16;
+}
+
+void TestWebGraphicsContext3D::deleteRenderbuffer(WebGLId id) {
+ DCHECK_EQ(kRenderbufferId | context_id_ << 16, id);
+}
+
+WebGLId TestWebGraphicsContext3D::createShader(WGC3Denum) {
+ return kShaderId | context_id_ << 16;
+}
+
+void TestWebGraphicsContext3D::deleteShader(WebGLId id) {
+ DCHECK_EQ(kShaderId | context_id_ << 16, id);
+}
+
+WebGLId TestWebGraphicsContext3D::createTexture() {
+ WebGLId texture_id = NextTextureId();
+ DCHECK_NE(texture_id, kExternalTextureId);
+ base::AutoLock lock(namespace_->lock);
+ namespace_->textures.push_back(texture_id);
+ return texture_id;
+}
+
+void TestWebGraphicsContext3D::deleteTexture(WebGLId texture_id) {
+ base::AutoLock lock(namespace_->lock);
+ std::vector<WebKit::WebGLId>& textures = namespace_->textures;
+ DCHECK(std::find(textures.begin(), textures.end(), texture_id) !=
+ textures.end());
+ textures.erase(std::find(textures.begin(), textures.end(), texture_id));
+}
+
+void TestWebGraphicsContext3D::attachShader(WebGLId program, WebGLId shader) {
+ DCHECK_EQ(kProgramId | context_id_ << 16, program);
+ DCHECK_EQ(kShaderId | context_id_ << 16, shader);
+}
+
+void TestWebGraphicsContext3D::useProgram(WebGLId program) {
+ if (!program)
+ return;
+ DCHECK_EQ(kProgramId | context_id_ << 16, program);
+}
+
+void TestWebGraphicsContext3D::bindFramebuffer(
+ WGC3Denum target, WebGLId framebuffer) {
+ if (!framebuffer)
+ return;
+ DCHECK_EQ(kFramebufferId | context_id_ << 16, framebuffer);
+}
+
+void TestWebGraphicsContext3D::bindRenderbuffer(
+ WGC3Denum target, WebGLId renderbuffer) {
+ if (!renderbuffer)
+ return;
+ DCHECK_EQ(kRenderbufferId | context_id_ << 16, renderbuffer);
+}
+
+void TestWebGraphicsContext3D::bindTexture(
+ WGC3Denum target, WebGLId texture_id) {
+ if (times_bind_texture_succeeds_ >= 0) {
+ if (!times_bind_texture_succeeds_) {
+ loseContextCHROMIUM(GL_GUILTY_CONTEXT_RESET_ARB,
+ GL_INNOCENT_CONTEXT_RESET_ARB);
+ }
+ --times_bind_texture_succeeds_;
+ }
+
+ if (!texture_id)
+ return;
+ if (texture_id == kExternalTextureId)
+ return;
+ base::AutoLock lock(namespace_->lock);
+ std::vector<WebKit::WebGLId>& textures = namespace_->textures;
+ DCHECK(std::find(textures.begin(), textures.end(), texture_id) !=
+ textures.end());
+ used_textures_.insert(texture_id);
+}
+
+void TestWebGraphicsContext3D::endQueryEXT(WGC3Denum target) {
+ if (times_end_query_succeeds_ >= 0) {
+ if (!times_end_query_succeeds_) {
+ loseContextCHROMIUM(GL_GUILTY_CONTEXT_RESET_ARB,
+ GL_INNOCENT_CONTEXT_RESET_ARB);
+ }
+ --times_end_query_succeeds_;
+ }
+}
+
+void TestWebGraphicsContext3D::getQueryObjectuivEXT(
+ WebGLId query,
+ WGC3Denum pname,
+ WGC3Duint* params) {
+ // If the context is lost, behave as if result is available.
+ if (pname == GL_QUERY_RESULT_AVAILABLE_EXT)
+ *params = 1;
+}
+
+void TestWebGraphicsContext3D::getIntegerv(
+ WGC3Denum pname,
+ WebKit::WGC3Dint* value) {
+ if (pname == GL_MAX_TEXTURE_SIZE)
+ *value = max_texture_size_;
+ else if (pname == GL_ACTIVE_TEXTURE)
+ *value = GL_TEXTURE0;
+}
+
+void TestWebGraphicsContext3D::genMailboxCHROMIUM(WebKit::WGC3Dbyte* mailbox) {
+ if (times_gen_mailbox_succeeds_ >= 0) {
+ if (!times_gen_mailbox_succeeds_) {
+ loseContextCHROMIUM(GL_GUILTY_CONTEXT_RESET_ARB,
+ GL_INNOCENT_CONTEXT_RESET_ARB);
+ }
+ --times_gen_mailbox_succeeds_;
+ }
+ if (context_lost_) {
+ memset(mailbox, 0, 64);
+ return;
+ }
+
+ static char mailbox_name1 = '1';
+ static char mailbox_name2 = '1';
+ mailbox[0] = mailbox_name1;
+ mailbox[1] = mailbox_name2;
+ mailbox[2] = '\0';
+ if (++mailbox_name1 == 0) {
+ mailbox_name1 = '1';
+ ++mailbox_name2;
+ }
+}
+
+void TestWebGraphicsContext3D::setContextLostCallback(
+ WebGraphicsContextLostCallback* callback) {
+ context_lost_callback_ = callback;
+}
+
+void TestWebGraphicsContext3D::loseContextCHROMIUM(WGC3Denum current,
+ WGC3Denum other) {
+ if (context_lost_)
+ return;
+ context_lost_ = true;
+ if (context_lost_callback_)
+ context_lost_callback_->onContextLost();
+
+ for (size_t i = 0; i < shared_contexts_.size(); ++i)
+ shared_contexts_[i]->loseContextCHROMIUM(current, other);
+ shared_contexts_.clear();
+}
+
+void TestWebGraphicsContext3D::signalSyncPoint(
+ unsigned sync_point,
+ WebGraphicsSyncPointCallback* callback) {
+ sync_point_callbacks_.push_back(callback);
+}
+
+void TestWebGraphicsContext3D::signalQuery(
+ WebKit::WebGLId query,
+ WebGraphicsSyncPointCallback* callback) {
+ sync_point_callbacks_.push_back(callback);
+}
+
+void TestWebGraphicsContext3D::setSwapBuffersCompleteCallbackCHROMIUM(
+ WebGraphicsSwapBuffersCompleteCallbackCHROMIUM* callback) {
+ if (support_swapbuffers_complete_callback_)
+ swap_buffers_callback_ = callback;
+}
+
+void TestWebGraphicsContext3D::setMemoryAllocationChangedCallbackCHROMIUM(
+ WebGraphicsMemoryAllocationChangedCallbackCHROMIUM* callback) {
+ memory_allocation_changed_callback_ = callback;
+}
+
+void TestWebGraphicsContext3D::prepareTexture() {
+ if (swap_buffers_callback_) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&TestWebGraphicsContext3D::SwapBuffersComplete,
+ weak_ptr_factory_.GetWeakPtr()));
+ }
+ CallAllSyncPointCallbacks();
+}
+
+void TestWebGraphicsContext3D::finish() {
+ CallAllSyncPointCallbacks();
+}
+
+void TestWebGraphicsContext3D::flush() {
+ CallAllSyncPointCallbacks();
+}
+
+static void CallAndDestroy(
+ WebKit::WebGraphicsContext3D::WebGraphicsSyncPointCallback* callback) {
+ if (!callback)
+ return;
+ callback->onSyncPointReached();
+ delete callback;
+}
+
+void TestWebGraphicsContext3D::CallAllSyncPointCallbacks() {
+ for (size_t i = 0; i < sync_point_callbacks_.size(); ++i) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&CallAndDestroy,
+ sync_point_callbacks_[i]));
+ }
+ sync_point_callbacks_.clear();
+}
+
+void TestWebGraphicsContext3D::SwapBuffersComplete() {
+ if (swap_buffers_callback_)
+ swap_buffers_callback_->onSwapBuffersComplete();
+}
+
+void TestWebGraphicsContext3D::bindBuffer(WebKit::WGC3Denum target,
+ WebKit::WebGLId buffer) {
+ bound_buffer_ = buffer;
+ if (!bound_buffer_)
+ return;
+ unsigned context_id = buffer >> 17;
+ unsigned buffer_id = buffer & 0x1ffff;
+ base::AutoLock lock(namespace_->lock);
+ DCHECK(buffer_id && buffer_id < namespace_->next_buffer_id);
+ DCHECK_EQ(context_id, context_id_);
+
+ ScopedPtrHashMap<unsigned, Buffer>& buffers = namespace_->buffers;
+ if (buffers.count(bound_buffer_) == 0)
+ buffers.set(bound_buffer_, make_scoped_ptr(new Buffer).Pass());
+
+ buffers.get(bound_buffer_)->target = target;
+}
+
+void TestWebGraphicsContext3D::bufferData(WebKit::WGC3Denum target,
+ WebKit::WGC3Dsizeiptr size,
+ const void* data,
+ WebKit::WGC3Denum usage) {
+ base::AutoLock lock(namespace_->lock);
+ ScopedPtrHashMap<unsigned, Buffer>& buffers = namespace_->buffers;
+ DCHECK_GT(buffers.count(bound_buffer_), 0u);
+ DCHECK_EQ(target, buffers.get(bound_buffer_)->target);
+ if (context_lost_) {
+ buffers.get(bound_buffer_)->pixels.reset();
+ return;
+ }
+ buffers.get(bound_buffer_)->pixels.reset(new uint8[size]);
+ if (data != NULL)
+ memcpy(buffers.get(bound_buffer_)->pixels.get(), data, size);
+}
+
+void* TestWebGraphicsContext3D::mapBufferCHROMIUM(WebKit::WGC3Denum target,
+ WebKit::WGC3Denum access) {
+ base::AutoLock lock(namespace_->lock);
+ ScopedPtrHashMap<unsigned, Buffer>& buffers = namespace_->buffers;
+ DCHECK_GT(buffers.count(bound_buffer_), 0u);
+ DCHECK_EQ(target, buffers.get(bound_buffer_)->target);
+ if (times_map_buffer_chromium_succeeds_ >= 0) {
+ if (!times_map_buffer_chromium_succeeds_) {
+ return NULL;
+ }
+ --times_map_buffer_chromium_succeeds_;
+ }
+ return buffers.get(bound_buffer_)->pixels.get();
+}
+
+WebKit::WGC3Dboolean TestWebGraphicsContext3D::unmapBufferCHROMIUM(
+ WebKit::WGC3Denum target) {
+ base::AutoLock lock(namespace_->lock);
+ ScopedPtrHashMap<unsigned, Buffer>& buffers = namespace_->buffers;
+ DCHECK_GT(buffers.count(bound_buffer_), 0u);
+ DCHECK_EQ(target, buffers.get(bound_buffer_)->target);
+ buffers.get(bound_buffer_)->pixels.reset();
+ return true;
+}
+
+void TestWebGraphicsContext3D::bindTexImage2DCHROMIUM(
+ WebKit::WGC3Denum target,
+ WebKit::WGC3Dint image_id) {
+ base::AutoLock lock(namespace_->lock);
+ DCHECK_GT(namespace_->images.count(image_id), 0u);
+}
+
+WebKit::WGC3Duint TestWebGraphicsContext3D::createImageCHROMIUM(
+ WebKit::WGC3Dsizei width, WebKit::WGC3Dsizei height,
+ WebKit::WGC3Denum internalformat) {
+ DCHECK_EQ(GL_RGBA8_OES, static_cast<int>(internalformat));
+ WebKit::WGC3Duint image_id = NextImageId();
+ base::AutoLock lock(namespace_->lock);
+ ScopedPtrHashMap<unsigned, Image>& images = namespace_->images;
+ images.set(image_id, make_scoped_ptr(new Image).Pass());
+ images.get(image_id)->pixels.reset(new uint8[width * height * 4]);
+ return image_id;
+}
+
+void TestWebGraphicsContext3D::destroyImageCHROMIUM(
+ WebKit::WGC3Duint id) {
+ base::AutoLock lock(namespace_->lock);
+ unsigned context_id = id >> 17;
+ unsigned image_id = id & 0x1ffff;
+ DCHECK(image_id && image_id < namespace_->next_image_id);
+ DCHECK_EQ(context_id, context_id_);
+}
+
+void TestWebGraphicsContext3D::getImageParameterivCHROMIUM(
+ WebKit::WGC3Duint image_id,
+ WebKit::WGC3Denum pname,
+ WebKit::WGC3Dint* params) {
+ base::AutoLock lock(namespace_->lock);
+ DCHECK_GT(namespace_->images.count(image_id), 0u);
+ DCHECK_EQ(GL_IMAGE_ROWBYTES_CHROMIUM, static_cast<int>(pname));
+ *params = 0;
+}
+
+void* TestWebGraphicsContext3D::mapImageCHROMIUM(WebKit::WGC3Duint image_id,
+ WebKit::WGC3Denum access) {
+ base::AutoLock lock(namespace_->lock);
+ ScopedPtrHashMap<unsigned, Image>& images = namespace_->images;
+ DCHECK_GT(images.count(image_id), 0u);
+ if (times_map_image_chromium_succeeds_ >= 0) {
+ if (!times_map_image_chromium_succeeds_) {
+ return NULL;
+ }
+ --times_map_image_chromium_succeeds_;
+ }
+ return images.get(image_id)->pixels.get();
+}
+
+void TestWebGraphicsContext3D::unmapImageCHROMIUM(
+ WebKit::WGC3Duint image_id) {
+ base::AutoLock lock(namespace_->lock);
+ DCHECK_GT(namespace_->images.count(image_id), 0u);
+}
+
+size_t TestWebGraphicsContext3D::NumTextures() const {
+ base::AutoLock lock(namespace_->lock);
+ return namespace_->textures.size();
+}
+
+WebKit::WebGLId TestWebGraphicsContext3D::TextureAt(int i) const {
+ base::AutoLock lock(namespace_->lock);
+ return namespace_->textures[i];
+}
+
+WebGLId TestWebGraphicsContext3D::NextTextureId() {
+ base::AutoLock lock(namespace_->lock);
+ WebGLId texture_id = namespace_->next_texture_id++;
+ DCHECK(texture_id < (1 << 16));
+ texture_id |= context_id_ << 16;
+ return texture_id;
+}
+
+WebGLId TestWebGraphicsContext3D::NextBufferId() {
+ base::AutoLock lock(namespace_->lock);
+ WebGLId buffer_id = namespace_->next_buffer_id++;
+ DCHECK(buffer_id < (1 << 17));
+ buffer_id |= context_id_ << 17;
+ return buffer_id;
+}
+
+WebKit::WGC3Duint TestWebGraphicsContext3D::NextImageId() {
+ base::AutoLock lock(namespace_->lock);
+ WGC3Duint image_id = namespace_->next_image_id++;
+ DCHECK(image_id < (1 << 17));
+ image_id |= context_id_ << 17;
+ return image_id;
+}
+
+void TestWebGraphicsContext3D::SetMemoryAllocation(
+ WebKit::WebGraphicsMemoryAllocation allocation) {
+ if (!memory_allocation_changed_callback_)
+ return;
+ memory_allocation_changed_callback_->onMemoryAllocationChanged(allocation);
+}
+
+TestWebGraphicsContext3D::Buffer::Buffer() : target(0) {}
+
+TestWebGraphicsContext3D::Buffer::~Buffer() {}
+
+TestWebGraphicsContext3D::Image::Image() {}
+
+TestWebGraphicsContext3D::Image::~Image() {}
+
+} // namespace cc
diff --git a/chromium/cc/debug/test_web_graphics_context_3d.h b/chromium/cc/debug/test_web_graphics_context_3d.h
new file mode 100644
index 00000000000..e66a16552be
--- /dev/null
+++ b/chromium/cc/debug/test_web_graphics_context_3d.h
@@ -0,0 +1,293 @@
+// Copyright 2013 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.
+
+#ifndef CC_DEBUG_TEST_WEB_GRAPHICS_CONTEXT_3D_H_
+#define CC_DEBUG_TEST_WEB_GRAPHICS_CONTEXT_3D_H_
+
+#include <vector>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/containers/hash_tables.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/stl_util.h"
+#include "base/synchronization/lock.h"
+#include "cc/base/cc_export.h"
+#include "cc/base/scoped_ptr_hash_map.h"
+#include "cc/debug/fake_web_graphics_context_3d.h"
+#include "third_party/khronos/GLES2/gl2.h"
+
+namespace WebKit { struct WebGraphicsMemoryAllocation; }
+
+namespace cc {
+
+class CC_EXPORT TestWebGraphicsContext3D : public FakeWebGraphicsContext3D {
+ public:
+ static scoped_ptr<TestWebGraphicsContext3D> Create();
+ static base::Callback<
+ scoped_ptr<TestWebGraphicsContext3D>()> CreateFactory();
+
+ virtual ~TestWebGraphicsContext3D();
+
+ virtual bool makeContextCurrent();
+
+ virtual int width();
+ virtual int height();
+
+ virtual void reshapeWithScaleFactor(
+ int width, int height, float scale_factor);
+
+ virtual bool isContextLost();
+ virtual WebKit::WGC3Denum getGraphicsResetStatusARB();
+
+ virtual void attachShader(WebKit::WebGLId program, WebKit::WebGLId shader);
+ virtual void bindFramebuffer(
+ WebKit::WGC3Denum target, WebKit::WebGLId framebuffer);
+ virtual void bindRenderbuffer(
+ WebKit::WGC3Denum target, WebKit::WebGLId renderbuffer);
+ virtual void bindTexture(
+ WebKit::WGC3Denum target,
+ WebKit::WebGLId texture_id);
+
+ virtual WebKit::WGC3Denum checkFramebufferStatus(WebKit::WGC3Denum target);
+
+ virtual Attributes getContextAttributes();
+
+ virtual WebKit::WebString getString(WebKit::WGC3Denum name);
+ virtual WebKit::WGC3Dint getUniformLocation(
+ WebKit::WebGLId program,
+ const WebKit::WGC3Dchar* name);
+ virtual WebKit::WGC3Dsizeiptr getVertexAttribOffset(
+ WebKit::WGC3Duint index,
+ WebKit::WGC3Denum pname);
+
+ virtual WebKit::WGC3Dboolean isBuffer(WebKit::WebGLId buffer);
+ virtual WebKit::WGC3Dboolean isEnabled(WebKit::WGC3Denum cap);
+ virtual WebKit::WGC3Dboolean isFramebuffer(WebKit::WebGLId framebuffer);
+ virtual WebKit::WGC3Dboolean isProgram(WebKit::WebGLId program);
+ virtual WebKit::WGC3Dboolean isRenderbuffer(WebKit::WebGLId renderbuffer);
+ virtual WebKit::WGC3Dboolean isShader(WebKit::WebGLId shader);
+ virtual WebKit::WGC3Dboolean isTexture(WebKit::WebGLId texture);
+
+ virtual void useProgram(WebKit::WebGLId program);
+
+ virtual WebKit::WebGLId createBuffer();
+ virtual WebKit::WebGLId createFramebuffer();
+ virtual WebKit::WebGLId createProgram();
+ virtual WebKit::WebGLId createRenderbuffer();
+ virtual WebKit::WebGLId createShader(WebKit::WGC3Denum);
+ virtual WebKit::WebGLId createTexture();
+
+ virtual void deleteBuffer(WebKit::WebGLId id);
+ virtual void deleteFramebuffer(WebKit::WebGLId id);
+ virtual void deleteProgram(WebKit::WebGLId id);
+ virtual void deleteRenderbuffer(WebKit::WebGLId id);
+ virtual void deleteShader(WebKit::WebGLId id);
+ virtual void deleteTexture(WebKit::WebGLId texture_id);
+
+ virtual void endQueryEXT(WebKit::WGC3Denum target);
+ virtual void getQueryObjectuivEXT(
+ WebKit::WebGLId query,
+ WebKit::WGC3Denum pname,
+ WebKit::WGC3Duint* params);
+
+ virtual void getIntegerv(
+ WebKit::WGC3Denum pname,
+ WebKit::WGC3Dint* value);
+
+ virtual void genMailboxCHROMIUM(WebKit::WGC3Dbyte* mailbox);
+ virtual void produceTextureCHROMIUM(WebKit::WGC3Denum target,
+ const WebKit::WGC3Dbyte* mailbox) { }
+ virtual void consumeTextureCHROMIUM(WebKit::WGC3Denum target,
+ const WebKit::WGC3Dbyte* mailbox) { }
+
+ virtual void setContextLostCallback(
+ WebGraphicsContextLostCallback* callback);
+
+ virtual void loseContextCHROMIUM(WebKit::WGC3Denum current,
+ WebKit::WGC3Denum other);
+
+ // Takes ownership of the |callback|.
+ virtual void signalSyncPoint(unsigned sync_point,
+ WebGraphicsSyncPointCallback* callback);
+ virtual void signalQuery(WebKit::WebGLId query,
+ WebGraphicsSyncPointCallback* callback);
+
+ virtual void setSwapBuffersCompleteCallbackCHROMIUM(
+ WebGraphicsSwapBuffersCompleteCallbackCHROMIUM* callback);
+
+ virtual void setMemoryAllocationChangedCallbackCHROMIUM(
+ WebGraphicsMemoryAllocationChangedCallbackCHROMIUM* callback);
+
+ virtual void prepareTexture();
+ virtual void finish();
+ virtual void flush();
+
+ virtual void bindBuffer(WebKit::WGC3Denum target, WebKit::WebGLId buffer);
+ virtual void bufferData(WebKit::WGC3Denum target,
+ WebKit::WGC3Dsizeiptr size,
+ const void* data,
+ WebKit::WGC3Denum usage);
+ virtual void* mapBufferCHROMIUM(WebKit::WGC3Denum target,
+ WebKit::WGC3Denum access);
+ virtual WebKit::WGC3Dboolean unmapBufferCHROMIUM(WebKit::WGC3Denum target);
+
+ virtual void bindTexImage2DCHROMIUM(WebKit::WGC3Denum target,
+ WebKit::WGC3Dint image_id);
+ virtual WebKit::WGC3Duint createImageCHROMIUM(
+ WebKit::WGC3Dsizei width,
+ WebKit::WGC3Dsizei height,
+ WebKit::WGC3Denum internalformat);
+ virtual void destroyImageCHROMIUM(WebKit::WGC3Duint image_id);
+ virtual void getImageParameterivCHROMIUM(
+ WebKit::WGC3Duint image_id,
+ WebKit::WGC3Denum pname,
+ WebKit::WGC3Dint* params);
+ virtual void* mapImageCHROMIUM(
+ WebKit::WGC3Duint image_id,
+ WebKit::WGC3Denum access);
+ virtual void unmapImageCHROMIUM(WebKit::WGC3Duint image_id);
+
+ // When set, MakeCurrent() will fail after this many times.
+ void set_times_make_current_succeeds(int times) {
+ times_make_current_succeeds_ = times;
+ }
+ void set_times_bind_texture_succeeds(int times) {
+ times_bind_texture_succeeds_ = times;
+ }
+ void set_times_end_query_succeeds(int times) {
+ times_end_query_succeeds_ = times;
+ }
+ void set_times_gen_mailbox_succeeds(int times) {
+ times_gen_mailbox_succeeds_ = times;
+ }
+
+ // When set, mapImageCHROMIUM and mapBufferCHROMIUM will return NULL after
+ // this many times.
+ void set_times_map_image_chromium_succeeds(int times) {
+ times_map_image_chromium_succeeds_ = times;
+ }
+ void set_times_map_buffer_chromium_succeeds(int times) {
+ times_map_buffer_chromium_succeeds_ = times;
+ }
+
+ size_t NumTextures() const;
+ WebKit::WebGLId TextureAt(int i) const;
+
+ size_t NumUsedTextures() const { return used_textures_.size(); }
+ bool UsedTexture(int texture) const {
+ return ContainsKey(used_textures_, texture);
+ }
+ void ResetUsedTextures() { used_textures_.clear(); }
+
+ void set_support_swapbuffers_complete_callback(bool support) {
+ support_swapbuffers_complete_callback_ = support;
+ }
+ void set_have_extension_io_surface(bool have) {
+ have_extension_io_surface_ = have;
+ }
+ void set_have_extension_egl_image(bool have) {
+ have_extension_egl_image_ = have;
+ }
+
+ // When this context is lost, all contexts in its share group are also lost.
+ void add_share_group_context(WebKit::WebGraphicsContext3D* context3d) {
+ shared_contexts_.push_back(context3d);
+ }
+
+ void set_max_texture_size(int size) { max_texture_size_ = size; }
+
+ static const WebKit::WebGLId kExternalTextureId;
+ virtual WebKit::WebGLId NextTextureId();
+
+ virtual WebKit::WebGLId NextBufferId();
+
+ virtual WebKit::WebGLId NextImageId();
+
+ void SetMemoryAllocation(WebKit::WebGraphicsMemoryAllocation allocation);
+
+ protected:
+ struct Buffer {
+ Buffer();
+ ~Buffer();
+
+ WebKit::WGC3Denum target;
+ scoped_ptr<uint8[]> pixels;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Buffer);
+ };
+
+ struct Image {
+ Image();
+ ~Image();
+
+ scoped_ptr<uint8[]> pixels;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Image);
+ };
+
+ struct Namespace : public base::RefCountedThreadSafe<Namespace> {
+ Namespace();
+
+ // Protects all fields.
+ base::Lock lock;
+ unsigned next_buffer_id;
+ unsigned next_image_id;
+ unsigned next_texture_id;
+ std::vector<WebKit::WebGLId> textures;
+ ScopedPtrHashMap<unsigned, Buffer> buffers;
+ ScopedPtrHashMap<unsigned, Image> images;
+
+ private:
+ friend class base::RefCountedThreadSafe<Namespace>;
+ ~Namespace();
+ DISALLOW_COPY_AND_ASSIGN(Namespace);
+ };
+
+ TestWebGraphicsContext3D();
+ TestWebGraphicsContext3D(
+ const WebKit::WebGraphicsContext3D::Attributes& attributes);
+
+ void CallAllSyncPointCallbacks();
+ void SwapBuffersComplete();
+ void CreateNamespace();
+
+ unsigned context_id_;
+ Attributes attributes_;
+ bool support_swapbuffers_complete_callback_;
+ bool have_extension_io_surface_;
+ bool have_extension_egl_image_;
+ int times_make_current_succeeds_;
+ int times_bind_texture_succeeds_;
+ int times_end_query_succeeds_;
+ int times_gen_mailbox_succeeds_;
+ bool context_lost_;
+ int times_map_image_chromium_succeeds_;
+ int times_map_buffer_chromium_succeeds_;
+ WebGraphicsContextLostCallback* context_lost_callback_;
+ WebGraphicsSwapBuffersCompleteCallbackCHROMIUM* swap_buffers_callback_;
+ WebGraphicsMemoryAllocationChangedCallbackCHROMIUM*
+ memory_allocation_changed_callback_;
+ std::vector<WebGraphicsSyncPointCallback*> sync_point_callbacks_;
+ base::hash_set<WebKit::WebGLId> used_textures_;
+ std::vector<WebKit::WebGraphicsContext3D*> shared_contexts_;
+ int max_texture_size_;
+ int width_;
+ int height_;
+
+ unsigned bound_buffer_;
+
+ scoped_refptr<Namespace> namespace_;
+ static Namespace* shared_namespace_;
+
+ base::WeakPtrFactory<TestWebGraphicsContext3D> weak_ptr_factory_;
+};
+
+} // namespace cc
+
+#endif // CC_DEBUG_TEST_WEB_GRAPHICS_CONTEXT_3D_H_
diff --git a/chromium/cc/debug/traced_picture.cc b/chromium/cc/debug/traced_picture.cc
new file mode 100644
index 00000000000..7f04d0d783f
--- /dev/null
+++ b/chromium/cc/debug/traced_picture.cc
@@ -0,0 +1,35 @@
+// Copyright (c) 2013 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 "cc/debug/traced_picture.h"
+
+#include "base/json/json_writer.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "cc/debug/traced_value.h"
+
+namespace cc {
+
+TracedPicture::TracedPicture(scoped_refptr<Picture> picture)
+ : picture_(picture) {
+}
+
+TracedPicture::~TracedPicture() {
+}
+
+scoped_ptr<base::debug::ConvertableToTraceFormat>
+ TracedPicture::AsTraceablePicture(Picture* picture) {
+ TracedPicture* ptr = new TracedPicture(picture);
+ scoped_ptr<TracedPicture> result(ptr);
+ return result.PassAs<base::debug::ConvertableToTraceFormat>();
+}
+
+void TracedPicture::AppendAsTraceFormat(std::string* out) const {
+ scoped_ptr<base::Value> value = picture_->AsValue();
+ std::string tmp;
+ base::JSONWriter::Write(value.get(), &tmp);
+ out->append(tmp);
+}
+
+} // namespace cc
diff --git a/chromium/cc/debug/traced_picture.h b/chromium/cc/debug/traced_picture.h
new file mode 100644
index 00000000000..9137d8f830e
--- /dev/null
+++ b/chromium/cc/debug/traced_picture.h
@@ -0,0 +1,35 @@
+// Copyright 2013 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.
+
+#ifndef CC_DEBUG_TRACED_PICTURE_H_
+#define CC_DEBUG_TRACED_PICTURE_H_
+
+#include <string>
+
+#include "base/debug/trace_event.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/resources/picture.h"
+
+namespace cc {
+
+class TracedPicture : public base::debug::ConvertableToTraceFormat {
+ public:
+ explicit TracedPicture(scoped_refptr<Picture>);
+
+ virtual ~TracedPicture();
+
+ static scoped_ptr<base::debug::ConvertableToTraceFormat>
+ AsTraceablePicture(Picture* picture);
+
+ virtual void AppendAsTraceFormat(std::string* out) const OVERRIDE;
+
+ private:
+ scoped_refptr<Picture> picture_;
+
+ DISALLOW_COPY_AND_ASSIGN(TracedPicture);
+};
+
+} // namespace cc
+
+#endif // CC_DEBUG_TRACED_PICTURE_H_
diff --git a/chromium/cc/debug/traced_value.cc b/chromium/cc/debug/traced_value.cc
new file mode 100644
index 00000000000..fbca5c60c80
--- /dev/null
+++ b/chromium/cc/debug/traced_value.cc
@@ -0,0 +1,53 @@
+// Copyright (c) 2013 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 "cc/debug/traced_value.h"
+
+#include "base/json/json_writer.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+
+namespace cc {
+
+scoped_ptr<base::Value> TracedValue::CreateIDRef(const void* id) {
+ scoped_ptr<base::DictionaryValue> res(new base::DictionaryValue());
+ res->SetString("id_ref", base::StringPrintf("%p", id));
+ return res.PassAs<base::Value>();
+}
+
+void TracedValue::MakeDictIntoImplicitSnapshot(
+ base::DictionaryValue* dict, const char* object_name, const void* id) {
+ dict->SetString("id", base::StringPrintf("%s/%p", object_name, id));
+}
+
+void TracedValue::MakeDictIntoImplicitSnapshotWithCategory(
+ const char* category,
+ base::DictionaryValue* dict,
+ const char* object_name,
+ const void* id) {
+ dict->SetString("cat", category);
+ MakeDictIntoImplicitSnapshot(dict, object_name, id);
+}
+
+scoped_ptr<base::debug::ConvertableToTraceFormat> TracedValue::FromValue(
+ base::Value* value) {
+ TracedValue* ptr = new TracedValue(value);
+ scoped_ptr<TracedValue> result(ptr);
+ return result.PassAs<base::debug::ConvertableToTraceFormat>();
+}
+
+TracedValue::TracedValue(base::Value* value)
+ : value_(value) {
+}
+
+TracedValue::~TracedValue() {
+}
+
+void TracedValue::AppendAsTraceFormat(std::string* out) const {
+ std::string tmp;
+ base::JSONWriter::Write(value_.get(), &tmp);
+ *out += tmp;
+}
+
+} // namespace cc
diff --git a/chromium/cc/debug/traced_value.h b/chromium/cc/debug/traced_value.h
new file mode 100644
index 00000000000..148aca8ebb6
--- /dev/null
+++ b/chromium/cc/debug/traced_value.h
@@ -0,0 +1,46 @@
+// Copyright 2013 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.
+
+#ifndef CC_DEBUG_TRACED_VALUE_H_
+#define CC_DEBUG_TRACED_VALUE_H_
+
+#include <string>
+
+#include "base/debug/trace_event.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace base {
+class DictionaryValue;
+class Value;
+}
+namespace cc {
+
+class TracedValue : public base::debug::ConvertableToTraceFormat {
+ public:
+ static scoped_ptr<base::Value> CreateIDRef(const void* id);
+ static void MakeDictIntoImplicitSnapshot(
+ base::DictionaryValue* dict, const char* object_name, const void* id);
+ static void MakeDictIntoImplicitSnapshotWithCategory(
+ const char* category,
+ base::DictionaryValue* dict,
+ const char* object_name,
+ const void* id);
+ static scoped_ptr<ConvertableToTraceFormat> FromValue(
+ base::Value* value);
+
+ virtual ~TracedValue();
+
+ virtual void AppendAsTraceFormat(std::string* out) const OVERRIDE;
+
+ private:
+ explicit TracedValue(base::Value* value);
+
+ scoped_ptr<base::Value> value_;
+
+ DISALLOW_COPY_AND_ASSIGN(TracedValue);
+};
+
+} // namespace cc
+
+#endif // CC_DEBUG_TRACED_VALUE_H_
diff --git a/chromium/cc/input/input_handler.h b/chromium/cc/input/input_handler.h
new file mode 100644
index 00000000000..db265617fe8
--- /dev/null
+++ b/chromium/cc/input/input_handler.h
@@ -0,0 +1,141 @@
+// Copyright 2011 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.
+
+#ifndef CC_INPUT_INPUT_HANDLER_H_
+#define CC_INPUT_INPUT_HANDLER_H_
+
+#include "base/basictypes.h"
+#include "base/time/time.h"
+#include "cc/base/cc_export.h"
+#include "cc/input/scrollbar.h"
+
+namespace gfx {
+class Point;
+class PointF;
+class Vector2d;
+class Vector2dF;
+}
+
+namespace ui { struct LatencyInfo; }
+
+namespace cc {
+
+class LayerScrollOffsetDelegate;
+
+struct DidOverscrollParams {
+ gfx::Vector2dF accumulated_overscroll;
+ gfx::Vector2dF latest_overscroll_delta;
+ gfx::Vector2dF current_fling_velocity;
+};
+
+class CC_EXPORT InputHandlerClient {
+ public:
+ virtual ~InputHandlerClient() {}
+
+ virtual void WillShutdown() = 0;
+ virtual void Animate(base::TimeTicks time) = 0;
+ virtual void MainThreadHasStoppedFlinging() = 0;
+
+ // Called when scroll deltas reaching the root scrolling layer go unused.
+ // The accumulated overscroll is scoped by the most recent call to
+ // InputHandler::ScrollBegin.
+ virtual void DidOverscroll(const DidOverscrollParams& params) = 0;
+
+ protected:
+ InputHandlerClient() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(InputHandlerClient);
+};
+
+// The InputHandler is a way for the embedders to interact with the impl thread
+// side of the compositor implementation. There is one InputHandler per
+// LayerTreeHost. To use the input handler, implement the InputHanderClient
+// interface and bind it to the handler on the compositor thread.
+class CC_EXPORT InputHandler {
+ public:
+ enum ScrollStatus { ScrollOnMainThread, ScrollStarted, ScrollIgnored };
+ enum ScrollInputType { Gesture, Wheel, NonBubblingGesture };
+
+ // Binds a client to this handler to receive notifications. Only one client
+ // can be bound to an InputHandler. The client must live at least until the
+ // handler calls WillShutdown() on the client.
+ virtual void BindToClient(InputHandlerClient* client) = 0;
+
+ // Selects a layer to be scrolled at a given point in viewport (logical
+ // pixel) coordinates. Returns ScrollStarted if the layer at the coordinates
+ // can be scrolled, ScrollOnMainThread if the scroll event should instead be
+ // delegated to the main thread, or ScrollIgnored if there is nothing to be
+ // scrolled at the given coordinates.
+ virtual ScrollStatus ScrollBegin(gfx::Point viewport_point,
+ ScrollInputType type) = 0;
+
+ // Scroll the selected layer starting at the given position. If the scroll
+ // type given to ScrollBegin was a gesture, then the scroll point and delta
+ // should be in viewport (logical pixel) coordinates. Otherwise they are in
+ // scrolling layer's (logical pixel) space. If there is no room to move the
+ // layer in the requested direction, its first ancestor layer that can be
+ // scrolled will be moved instead. If no layer can be moved in the requested
+ // direction at all, then false is returned. If any layer is moved, then
+ // true is returned.
+ // If the scroll delta hits the root layer, and the layer can no longer move,
+ // the root overscroll accumulated within this ScrollBegin() scope is reported
+ // to the client.
+ // Should only be called if ScrollBegin() returned ScrollStarted.
+ virtual bool ScrollBy(gfx::Point viewport_point,
+ gfx::Vector2dF scroll_delta) = 0;
+
+ virtual bool ScrollVerticallyByPage(
+ gfx::Point viewport_point,
+ ScrollDirection direction) = 0;
+
+ // Returns ScrollStarted if a layer was being actively being scrolled,
+ // ScrollIgnored if not.
+ virtual ScrollStatus FlingScrollBegin() = 0;
+
+ virtual void NotifyCurrentFlingVelocity(gfx::Vector2dF velocity) = 0;
+
+ // Stop scrolling the selected layer. Should only be called if ScrollBegin()
+ // returned ScrollStarted.
+ virtual void ScrollEnd() = 0;
+
+ virtual void SetRootLayerScrollOffsetDelegate(
+ LayerScrollOffsetDelegate* root_layer_scroll_offset_delegate) = 0;
+
+ // Called when the value returned by
+ // LayerScrollOffsetDelegate.GetTotalScrollOffset has changed for reasons
+ // other than a SetTotalScrollOffset call.
+ // NOTE: This should only called after a valid delegate was set via a call to
+ // SetRootLayerScrollOffsetDelegate.
+ virtual void OnRootLayerDelegatedScrollOffsetChanged() = 0;
+
+ virtual void PinchGestureBegin() = 0;
+ virtual void PinchGestureUpdate(float magnify_delta, gfx::Point anchor) = 0;
+ virtual void PinchGestureEnd() = 0;
+
+ virtual void StartPageScaleAnimation(gfx::Vector2d target_offset,
+ bool anchor_point,
+ float page_scale,
+ base::TimeTicks start_time,
+ base::TimeDelta duration) = 0;
+
+ // Request another callback to InputHandlerClient::Animate().
+ virtual void ScheduleAnimation() = 0;
+
+ virtual bool HaveTouchEventHandlersAt(gfx::Point viewport_point) = 0;
+
+ virtual void SetLatencyInfoForInputEvent(
+ const ui::LatencyInfo& latency_info) = 0;
+
+ protected:
+ InputHandler() {}
+ virtual ~InputHandler() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(InputHandler);
+};
+
+} // namespace cc
+
+#endif // CC_INPUT_INPUT_HANDLER_H_
diff --git a/chromium/cc/input/layer_scroll_offset_delegate.h b/chromium/cc/input/layer_scroll_offset_delegate.h
new file mode 100644
index 00000000000..895d36d7490
--- /dev/null
+++ b/chromium/cc/input/layer_scroll_offset_delegate.h
@@ -0,0 +1,41 @@
+// Copyright 2013 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.
+
+#ifndef CC_INPUT_LAYER_SCROLL_OFFSET_DELEGATE_H_
+#define CC_INPUT_LAYER_SCROLL_OFFSET_DELEGATE_H_
+
+#include "base/basictypes.h"
+#include "ui/gfx/vector2d_f.h"
+
+namespace cc {
+
+// The LayerScrollOffsetDelegate allows for the embedder to take ownership of
+// the scroll offset of the root layer.
+//
+// The LayerScrollOffsetDelegate is only used on the impl thread.
+class LayerScrollOffsetDelegate {
+ public:
+ // This is called by the compositor when the scroll offset of the layer would
+ // have otherwise changed.
+ virtual void SetTotalScrollOffset(gfx::Vector2dF new_value) = 0;
+
+ // This is called by the compositor to query the current scroll offset of the
+ // layer.
+ // There is no requirement that the return values of this method are
+ // stable in time (two subsequent calls may yield different results).
+ // The return value is not required to be related to the values passed in to
+ // the SetTotalScrollOffset method in any way.
+ virtual gfx::Vector2dF GetTotalScrollOffset() = 0;
+
+ protected:
+ LayerScrollOffsetDelegate() {}
+ virtual ~LayerScrollOffsetDelegate() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LayerScrollOffsetDelegate);
+};
+
+} // namespace cc
+
+#endif // CC_INPUT_LAYER_SCROLL_OFFSET_DELEGATE_H_
diff --git a/chromium/cc/input/page_scale_animation.cc b/chromium/cc/input/page_scale_animation.cc
new file mode 100644
index 00000000000..a535c87ad97
--- /dev/null
+++ b/chromium/cc/input/page_scale_animation.cc
@@ -0,0 +1,232 @@
+// 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 "cc/input/page_scale_animation.h"
+
+#include <math.h>
+
+#include "base/logging.h"
+#include "cc/animation/timing_function.h"
+#include "ui/gfx/point_f.h"
+#include "ui/gfx/rect_f.h"
+#include "ui/gfx/vector2d_conversions.h"
+
+namespace {
+
+// This takes a viewport-relative vector and returns a vector whose values are
+// between 0 and 1, representing the percentage position within the viewport.
+gfx::Vector2dF NormalizeFromViewport(gfx::Vector2dF denormalized,
+ gfx::SizeF viewport_size) {
+ return gfx::ScaleVector2d(denormalized,
+ 1.f / viewport_size.width(),
+ 1.f / viewport_size.height());
+}
+
+gfx::Vector2dF DenormalizeToViewport(gfx::Vector2dF normalized,
+ gfx::SizeF viewport_size) {
+ return gfx::ScaleVector2d(normalized,
+ viewport_size.width(),
+ viewport_size.height());
+}
+
+gfx::Vector2dF InterpolateBetween(gfx::Vector2dF start,
+ gfx::Vector2dF end,
+ float interp) {
+ return start + gfx::ScaleVector2d(end - start, interp);
+}
+
+} // namespace
+
+namespace cc {
+
+scoped_ptr<PageScaleAnimation> PageScaleAnimation::Create(
+ gfx::Vector2dF start_scroll_offset,
+ float start_page_scale_factor,
+ gfx::SizeF viewport_size,
+ gfx::SizeF root_layer_size,
+ double start_time,
+ scoped_ptr<TimingFunction> timing_function) {
+ return make_scoped_ptr(new PageScaleAnimation(start_scroll_offset,
+ start_page_scale_factor,
+ viewport_size,
+ root_layer_size,
+ start_time,
+ timing_function.Pass()));
+}
+
+PageScaleAnimation::PageScaleAnimation(
+ gfx::Vector2dF start_scroll_offset,
+ float start_page_scale_factor,
+ gfx::SizeF viewport_size,
+ gfx::SizeF root_layer_size,
+ double start_time,
+ scoped_ptr<TimingFunction> timing_function)
+ : start_page_scale_factor_(start_page_scale_factor),
+ target_page_scale_factor_(0.f),
+ start_scroll_offset_(start_scroll_offset),
+ start_anchor_(),
+ target_anchor_(),
+ viewport_size_(viewport_size),
+ root_layer_size_(root_layer_size),
+ start_time_(start_time),
+ duration_(0.0),
+ timing_function_(timing_function.Pass()) {}
+
+PageScaleAnimation::~PageScaleAnimation() {}
+
+void PageScaleAnimation::ZoomTo(gfx::Vector2dF target_scroll_offset,
+ float target_page_scale_factor,
+ double duration) {
+ target_page_scale_factor_ = target_page_scale_factor;
+ target_scroll_offset_ = target_scroll_offset;
+ ClampTargetScrollOffset();
+ duration_ = duration;
+
+ if (start_page_scale_factor_ == target_page_scale_factor) {
+ start_anchor_ = start_scroll_offset_;
+ target_anchor_ = target_scroll_offset;
+ return;
+ }
+
+ // For uniform-looking zooming, infer an anchor from the start and target
+ // viewport rects.
+ InferTargetAnchorFromScrollOffsets();
+ start_anchor_ = target_anchor_;
+}
+
+void PageScaleAnimation::ZoomWithAnchor(gfx::Vector2dF anchor,
+ float target_page_scale_factor,
+ double duration) {
+ start_anchor_ = anchor;
+ target_page_scale_factor_ = target_page_scale_factor;
+ duration_ = duration;
+
+ // We start zooming out from the anchor tapped by the user. But if
+ // the target scale is impossible to attain without hitting the root layer
+ // edges, then infer an anchor that doesn't collide with the edges.
+ // We will interpolate between the two anchors during the animation.
+ InferTargetScrollOffsetFromStartAnchor();
+ ClampTargetScrollOffset();
+
+ if (start_page_scale_factor_ == target_page_scale_factor_) {
+ target_anchor_ = start_anchor_;
+ return;
+ }
+ InferTargetAnchorFromScrollOffsets();
+}
+
+void PageScaleAnimation::InferTargetScrollOffsetFromStartAnchor() {
+ gfx::Vector2dF normalized = NormalizeFromViewport(
+ start_anchor_ - start_scroll_offset_, StartViewportSize());
+ target_scroll_offset_ =
+ start_anchor_ - DenormalizeToViewport(normalized, TargetViewportSize());
+}
+
+void PageScaleAnimation::InferTargetAnchorFromScrollOffsets() {
+ // The anchor is the point which is at the same normalized relative position
+ // within both start viewport rect and target viewport rect. For example, a
+ // zoom-in double-tap to a perfectly centered rect will have normalized
+ // anchor (0.5, 0.5), while one to a rect touching the bottom-right of the
+ // screen will have normalized anchor (1.0, 1.0). In other words, it obeys
+ // the equations:
+ // anchor = start_size * normalized + start_offset
+ // anchor = target_size * normalized + target_offset
+ // where both anchor and normalized begin as unknowns. Solving
+ // for the normalized, we get the following:
+ float width_scale =
+ 1.f / (TargetViewportSize().width() - StartViewportSize().width());
+ float height_scale =
+ 1.f / (TargetViewportSize().height() - StartViewportSize().height());
+ gfx::Vector2dF normalized = gfx::ScaleVector2d(
+ start_scroll_offset_ - target_scroll_offset_, width_scale, height_scale);
+ target_anchor_ =
+ target_scroll_offset_ + DenormalizeToViewport(normalized,
+ TargetViewportSize());
+}
+
+void PageScaleAnimation::ClampTargetScrollOffset() {
+ gfx::Vector2dF max_scroll_offset =
+ gfx::RectF(root_layer_size_).bottom_right() -
+ gfx::RectF(TargetViewportSize()).bottom_right();
+ target_scroll_offset_.SetToMax(gfx::Vector2dF());
+ target_scroll_offset_.SetToMin(max_scroll_offset);
+}
+
+gfx::SizeF PageScaleAnimation::StartViewportSize() const {
+ return gfx::ScaleSize(viewport_size_, 1.f / start_page_scale_factor_);
+}
+
+gfx::SizeF PageScaleAnimation::TargetViewportSize() const {
+ return gfx::ScaleSize(viewport_size_, 1.f / target_page_scale_factor_);
+}
+
+gfx::SizeF PageScaleAnimation::ViewportSizeAt(float interp) const {
+ return gfx::ScaleSize(viewport_size_, 1.f / PageScaleFactorAt(interp));
+}
+
+gfx::Vector2dF PageScaleAnimation::ScrollOffsetAtTime(double time) const {
+ return ScrollOffsetAt(InterpAtTime(time));
+}
+
+float PageScaleAnimation::PageScaleFactorAtTime(double time) const {
+ return PageScaleFactorAt(InterpAtTime(time));
+}
+
+bool PageScaleAnimation::IsAnimationCompleteAtTime(double time) const {
+ return time >= end_time();
+}
+
+float PageScaleAnimation::InterpAtTime(double time) const {
+ DCHECK_GE(time, start_time_);
+ if (IsAnimationCompleteAtTime(time))
+ return 1.f;
+
+ const double normalized_time = (time - start_time_) / duration_;
+ return timing_function_->GetValue(normalized_time);
+}
+
+gfx::Vector2dF PageScaleAnimation::ScrollOffsetAt(float interp) const {
+ if (interp <= 0.f)
+ return start_scroll_offset_;
+ if (interp >= 1.f)
+ return target_scroll_offset_;
+
+ return AnchorAt(interp) - ViewportRelativeAnchorAt(interp);
+}
+
+gfx::Vector2dF PageScaleAnimation::AnchorAt(float interp) const {
+ // Interpolate from start to target anchor in absolute space.
+ return InterpolateBetween(start_anchor_, target_anchor_, interp);
+}
+
+gfx::Vector2dF PageScaleAnimation::ViewportRelativeAnchorAt(
+ float interp) const {
+ // Interpolate from start to target anchor in normalized space.
+ gfx::Vector2dF start_normalized =
+ NormalizeFromViewport(start_anchor_ - start_scroll_offset_,
+ StartViewportSize());
+ gfx::Vector2dF target_normalized =
+ NormalizeFromViewport(target_anchor_ - target_scroll_offset_,
+ TargetViewportSize());
+ gfx::Vector2dF interp_normalized =
+ InterpolateBetween(start_normalized, target_normalized, interp);
+
+ return DenormalizeToViewport(interp_normalized, ViewportSizeAt(interp));
+}
+
+float PageScaleAnimation::PageScaleFactorAt(float interp) const {
+ if (interp <= 0.f)
+ return start_page_scale_factor_;
+ if (interp >= 1.f)
+ return target_page_scale_factor_;
+
+ // Linearly interpolate the magnitude in log scale.
+ float diff = target_page_scale_factor_ / start_page_scale_factor_;
+ float log_diff = log(diff);
+ log_diff *= interp;
+ diff = exp(log_diff);
+ return start_page_scale_factor_ * diff;
+}
+
+} // namespace cc
diff --git a/chromium/cc/input/page_scale_animation.h b/chromium/cc/input/page_scale_animation.h
new file mode 100644
index 00000000000..b6d35104153
--- /dev/null
+++ b/chromium/cc/input/page_scale_animation.h
@@ -0,0 +1,110 @@
+// Copyright 2011 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.
+
+#ifndef CC_INPUT_PAGE_SCALE_ANIMATION_H_
+#define CC_INPUT_PAGE_SCALE_ANIMATION_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/gfx/size.h"
+#include "ui/gfx/vector2d_f.h"
+
+namespace cc {
+class TimingFunction;
+
+// A small helper class that does the math for zoom animations, primarily for
+// double-tap zoom. Initialize it with starting and ending scroll/page scale
+// positions and an animation length time, then call ...AtTime() at every frame
+// to obtain the current interpolated position. The supplied timing function
+// is used to ease the animation.
+//
+// All sizes and vectors in this class's public methods are in the root scroll
+// layer's coordinate space.
+class PageScaleAnimation {
+ public:
+ // Construct with the state at the beginning of the animation.
+ static scoped_ptr<PageScaleAnimation> Create(
+ gfx::Vector2dF start_scroll_offset,
+ float start_page_scale_factor,
+ gfx::SizeF viewport_size,
+ gfx::SizeF root_layer_size,
+ double start_time,
+ scoped_ptr<TimingFunction> timing_function);
+
+ ~PageScaleAnimation();
+
+ // The following methods initialize the animation. Call one of them
+ // immediately after construction to set the final scroll and page scale.
+
+ // Zoom while explicitly specifying the top-left scroll position.
+ void ZoomTo(gfx::Vector2dF target_scroll_offset,
+ float target_page_scale_factor,
+ double duration);
+
+ // Zoom based on a specified anchor. The animator will attempt to keep it
+ // at the same position on the physical display throughout the animation,
+ // unless the edges of the root layer are hit. The anchor is specified
+ // as an offset from the content layer.
+ void ZoomWithAnchor(gfx::Vector2dF anchor,
+ float target_page_scale_factor,
+ double duration);
+
+ // Call these functions while the animation is in progress to output the
+ // current state.
+ gfx::Vector2dF ScrollOffsetAtTime(double time) const;
+ float PageScaleFactorAtTime(double time) const;
+ bool IsAnimationCompleteAtTime(double time) const;
+
+ // The following methods return state which is invariant throughout the
+ // course of the animation.
+ double start_time() const { return start_time_; }
+ double duration() const { return duration_; }
+ double end_time() const { return start_time_ + duration_; }
+ gfx::Vector2dF target_scroll_offset() const { return target_scroll_offset_; }
+ float target_page_scale_factor() const { return target_page_scale_factor_; }
+
+ protected:
+ PageScaleAnimation(gfx::Vector2dF start_scroll_offset,
+ float start_page_scale_factor,
+ gfx::SizeF viewport_size,
+ gfx::SizeF root_layer_size,
+ double start_time,
+ scoped_ptr<TimingFunction> timing_function);
+
+ private:
+ void ClampTargetScrollOffset();
+ void InferTargetScrollOffsetFromStartAnchor();
+ void InferTargetAnchorFromScrollOffsets();
+
+ gfx::SizeF StartViewportSize() const;
+ gfx::SizeF TargetViewportSize() const;
+ float InterpAtTime(double time) const;
+ gfx::SizeF ViewportSizeAt(float interp) const;
+ gfx::Vector2dF ScrollOffsetAt(float interp) const;
+ gfx::Vector2dF AnchorAt(float interp) const;
+ gfx::Vector2dF ViewportRelativeAnchorAt(float interp) const;
+ float PageScaleFactorAt(float interp) const;
+
+ float start_page_scale_factor_;
+ float target_page_scale_factor_;
+ gfx::Vector2dF start_scroll_offset_;
+ gfx::Vector2dF target_scroll_offset_;
+
+ gfx::Vector2dF start_anchor_;
+ gfx::Vector2dF target_anchor_;
+
+ gfx::SizeF viewport_size_;
+ gfx::SizeF root_layer_size_;
+
+ double start_time_;
+ double duration_;
+
+ scoped_ptr<TimingFunction> timing_function_;
+
+ DISALLOW_COPY_AND_ASSIGN(PageScaleAnimation);
+};
+
+} // namespace cc
+
+#endif // CC_INPUT_PAGE_SCALE_ANIMATION_H_
diff --git a/chromium/cc/input/scrollbar.h b/chromium/cc/input/scrollbar.h
new file mode 100644
index 00000000000..4c7e33837f6
--- /dev/null
+++ b/chromium/cc/input/scrollbar.h
@@ -0,0 +1,40 @@
+// Copyright 2013 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.
+
+#ifndef CC_INPUT_SCROLLBAR_H_
+#define CC_INPUT_SCROLLBAR_H_
+
+#include "cc/base/cc_export.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/rect.h"
+
+class SkCanvas;
+
+namespace cc {
+
+enum ScrollbarOrientation { HORIZONTAL, VERTICAL };
+enum ScrollDirection { SCROLL_BACKWARD, SCROLL_FORWARD };
+// For now, TRACK includes everything but the thumb including background and
+// buttons.
+enum ScrollbarPart { THUMB, TRACK };
+
+class Scrollbar {
+ public:
+ virtual ~Scrollbar() {}
+
+ virtual ScrollbarOrientation Orientation() const = 0;
+ virtual gfx::Point Location() const = 0;
+ virtual bool IsOverlay() const = 0;
+ virtual bool HasThumb() const = 0;
+ virtual int ThumbThickness() const = 0;
+ virtual int ThumbLength() const = 0;
+ virtual gfx::Rect TrackRect() const = 0;
+ virtual void PaintPart(SkCanvas* canvas,
+ ScrollbarPart part,
+ gfx::Rect content_rect) = 0;
+};
+
+} // namespace cc
+
+#endif // CC_INPUT_SCROLLBAR_H_
diff --git a/chromium/cc/input/top_controls_manager.cc b/chromium/cc/input/top_controls_manager.cc
new file mode 100644
index 00000000000..925830cf40e
--- /dev/null
+++ b/chromium/cc/input/top_controls_manager.cc
@@ -0,0 +1,230 @@
+// Copyright 2013 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 "cc/input/top_controls_manager.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/time/time.h"
+#include "cc/animation/keyframed_animation_curve.h"
+#include "cc/animation/timing_function.h"
+#include "cc/input/top_controls_manager_client.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "ui/gfx/transform.h"
+#include "ui/gfx/vector2d_f.h"
+
+namespace cc {
+namespace {
+// These constants were chosen empirically for their visually pleasant behavior.
+// Contact tedchoc@chromium.org for questions about changing these values.
+const int64 kShowHideMaxDurationMs = 200;
+}
+
+// static
+scoped_ptr<TopControlsManager> TopControlsManager::Create(
+ TopControlsManagerClient* client,
+ float top_controls_height,
+ float top_controls_show_threshold,
+ float top_controls_hide_threshold) {
+ return make_scoped_ptr(new TopControlsManager(client,
+ top_controls_height,
+ top_controls_show_threshold,
+ top_controls_hide_threshold));
+}
+
+TopControlsManager::TopControlsManager(TopControlsManagerClient* client,
+ float top_controls_height,
+ float top_controls_show_threshold,
+ float top_controls_hide_threshold)
+ : client_(client),
+ animation_direction_(NO_ANIMATION),
+ permitted_state_(BOTH),
+ controls_top_offset_(0.f),
+ top_controls_height_(top_controls_height),
+ current_scroll_delta_(0.f),
+ controls_scroll_begin_offset_(0.f),
+ top_controls_show_height_(
+ top_controls_height * top_controls_hide_threshold),
+ top_controls_hide_height_(
+ top_controls_height * (1.f - top_controls_show_threshold)) {
+ CHECK(client_);
+}
+
+TopControlsManager::~TopControlsManager() {
+}
+
+void TopControlsManager::UpdateTopControlsState(TopControlsState constraints,
+ TopControlsState current,
+ bool animate) {
+ DCHECK(!(constraints == SHOWN && current == HIDDEN));
+ DCHECK(!(constraints == HIDDEN && current == SHOWN));
+
+ permitted_state_ = constraints;
+
+ // Don't do anything if it doesn't matter which state the controls are in.
+ if (constraints == BOTH && current == BOTH)
+ return;
+
+ // Don't do anything if there is no change in offset.
+ float final_controls_position = 0.f;
+ if (constraints == HIDDEN || current == HIDDEN) {
+ final_controls_position = -top_controls_height_;
+ }
+ if (final_controls_position == controls_top_offset_) {
+ return;
+ }
+
+ AnimationDirection animation_direction = SHOWING_CONTROLS;
+ if (constraints == HIDDEN || current == HIDDEN)
+ animation_direction = HIDING_CONTROLS;
+ ResetAnimations();
+ if (animate) {
+ SetupAnimation(animation_direction);
+ } else {
+ controls_top_offset_ = final_controls_position;
+ }
+ client_->DidChangeTopControlsPosition();
+}
+
+void TopControlsManager::ScrollBegin() {
+ ResetAnimations();
+ current_scroll_delta_ = 0.f;
+ controls_scroll_begin_offset_ = controls_top_offset_;
+}
+
+gfx::Vector2dF TopControlsManager::ScrollBy(
+ const gfx::Vector2dF pending_delta) {
+ if (permitted_state_ == SHOWN && pending_delta.y() > 0)
+ return pending_delta;
+ else if (permitted_state_ == HIDDEN && pending_delta.y() < 0)
+ return pending_delta;
+
+ current_scroll_delta_ += pending_delta.y();
+
+ float old_offset = controls_top_offset_;
+ SetControlsTopOffset(controls_scroll_begin_offset_ - current_scroll_delta_);
+
+ // If the controls are fully visible, treat the current position as the
+ // new baseline even if the gesture didn't end.
+ if (controls_top_offset_ == 0.f) {
+ current_scroll_delta_ = 0.f;
+ controls_scroll_begin_offset_ = 0.f;
+ }
+
+ ResetAnimations();
+
+ gfx::Vector2dF applied_delta(0.f, old_offset - controls_top_offset_);
+ return pending_delta - applied_delta;
+}
+
+void TopControlsManager::ScrollEnd() {
+ StartAnimationIfNecessary();
+}
+
+void TopControlsManager::SetControlsTopOffset(float controls_top_offset) {
+ controls_top_offset = std::max(controls_top_offset, -top_controls_height_);
+ controls_top_offset = std::min(controls_top_offset, 0.f);
+
+ if (controls_top_offset_ == controls_top_offset)
+ return;
+
+ controls_top_offset_ = controls_top_offset;
+
+ client_->DidChangeTopControlsPosition();
+}
+
+gfx::Vector2dF TopControlsManager::Animate(base::TimeTicks monotonic_time) {
+ if (!top_controls_animation_ || !client_->HaveRootScrollLayer())
+ return gfx::Vector2dF();
+
+ double time = (monotonic_time - base::TimeTicks()).InMillisecondsF();
+
+ float old_offset = controls_top_offset_;
+ SetControlsTopOffset(top_controls_animation_->GetValue(time));
+
+ if (IsAnimationCompleteAtTime(monotonic_time))
+ ResetAnimations();
+
+ gfx::Vector2dF scroll_delta(0.f, controls_top_offset_ - old_offset);
+ return scroll_delta;
+}
+
+void TopControlsManager::ResetAnimations() {
+ if (top_controls_animation_)
+ top_controls_animation_.reset();
+
+ animation_direction_ = NO_ANIMATION;
+}
+
+void TopControlsManager::SetupAnimation(AnimationDirection direction) {
+ DCHECK(direction != NO_ANIMATION);
+
+ if (direction == SHOWING_CONTROLS && controls_top_offset_ == 0)
+ return;
+
+ if (direction == HIDING_CONTROLS &&
+ controls_top_offset_ == -top_controls_height_) {
+ return;
+ }
+
+ if (top_controls_animation_ && animation_direction_ == direction)
+ return;
+
+ top_controls_animation_ = KeyframedFloatAnimationCurve::Create();
+ double start_time =
+ (base::TimeTicks::Now() - base::TimeTicks()).InMillisecondsF();
+ top_controls_animation_->AddKeyframe(
+ FloatKeyframe::Create(start_time, controls_top_offset_,
+ scoped_ptr<TimingFunction>()));
+ float max_ending_offset =
+ (direction == SHOWING_CONTROLS ? 1 : -1) * top_controls_height_;
+ top_controls_animation_->AddKeyframe(
+ FloatKeyframe::Create(start_time + kShowHideMaxDurationMs,
+ controls_top_offset_ + max_ending_offset,
+ EaseTimingFunction::Create()));
+ animation_direction_ = direction;
+ client_->DidChangeTopControlsPosition();
+}
+
+void TopControlsManager::StartAnimationIfNecessary() {
+ if (controls_top_offset_ != 0
+ && controls_top_offset_ != -top_controls_height_) {
+ AnimationDirection show_controls = NO_ANIMATION;
+
+ if (controls_top_offset_ >= -top_controls_show_height_) {
+ // If we're showing so much that the hide threshold won't trigger, show.
+ show_controls = SHOWING_CONTROLS;
+ } else if (controls_top_offset_ <= -top_controls_hide_height_) {
+ // If we're showing so little that the show threshold won't trigger, hide.
+ show_controls = HIDING_CONTROLS;
+ } else {
+ // If we could be either showing or hiding, we determine which one to
+ // do based on whether or not the total scroll delta was moving up or
+ // down.
+ show_controls = current_scroll_delta_ <= 0.f ?
+ SHOWING_CONTROLS : HIDING_CONTROLS;
+ }
+
+ if (show_controls != NO_ANIMATION)
+ SetupAnimation(show_controls);
+ }
+}
+
+bool TopControlsManager::IsAnimationCompleteAtTime(base::TimeTicks time) {
+ if (!top_controls_animation_)
+ return true;
+
+ double time_ms = (time - base::TimeTicks()).InMillisecondsF();
+ float new_offset = top_controls_animation_->GetValue(time_ms);
+
+ if ((animation_direction_ == SHOWING_CONTROLS && new_offset >= 0) ||
+ (animation_direction_ == HIDING_CONTROLS
+ && new_offset <= -top_controls_height_)) {
+ return true;
+ }
+ return false;
+}
+
+} // namespace cc
diff --git a/chromium/cc/input/top_controls_manager.h b/chromium/cc/input/top_controls_manager.h
new file mode 100644
index 00000000000..9bc3040d31a
--- /dev/null
+++ b/chromium/cc/input/top_controls_manager.h
@@ -0,0 +1,99 @@
+// Copyright 2013 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.
+
+#ifndef CC_INPUT_TOP_CONTROLS_MANAGER_H_
+#define CC_INPUT_TOP_CONTROLS_MANAGER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "cc/input/top_controls_state.h"
+#include "cc/layers/layer_impl.h"
+#include "ui/gfx/size.h"
+#include "ui/gfx/vector2d_f.h"
+
+namespace base {
+class TimeTicks;
+}
+
+namespace cc {
+
+class KeyframedFloatAnimationCurve;
+class LayerTreeImpl;
+class TopControlsManagerClient;
+
+// Manages the position of the top controls.
+class CC_EXPORT TopControlsManager
+ : public base::SupportsWeakPtr<TopControlsManager> {
+ public:
+ enum AnimationDirection {
+ NO_ANIMATION,
+ SHOWING_CONTROLS,
+ HIDING_CONTROLS
+ };
+
+ static scoped_ptr<TopControlsManager> Create(
+ TopControlsManagerClient* client,
+ float top_controls_height,
+ float top_controls_show_threshold,
+ float top_controls_hide_threshold);
+ virtual ~TopControlsManager();
+
+ float controls_top_offset() { return controls_top_offset_; }
+ float content_top_offset() {
+ return controls_top_offset_ + top_controls_height_;
+ }
+ KeyframedFloatAnimationCurve* animation() {
+ return top_controls_animation_.get();
+ }
+ AnimationDirection animation_direction() { return animation_direction_; }
+
+ void UpdateTopControlsState(TopControlsState constraints,
+ TopControlsState current,
+ bool animate);
+
+ void ScrollBegin();
+ gfx::Vector2dF ScrollBy(const gfx::Vector2dF pending_delta);
+ void ScrollEnd();
+
+ gfx::Vector2dF Animate(base::TimeTicks monotonic_time);
+
+ protected:
+ TopControlsManager(TopControlsManagerClient* client,
+ float top_controls_height,
+ float top_controls_show_threshold,
+ float top_controls_hide_threshold);
+
+ private:
+ void SetControlsTopOffset(float offset);
+ void ResetAnimations();
+ void SetupAnimation(AnimationDirection direction);
+ void StartAnimationIfNecessary();
+ bool IsAnimationCompleteAtTime(base::TimeTicks time);
+
+ TopControlsManagerClient* client_; // The client manages the lifecycle of
+ // this.
+
+ scoped_ptr<KeyframedFloatAnimationCurve> top_controls_animation_;
+ AnimationDirection animation_direction_;
+ TopControlsState permitted_state_;
+ float controls_top_offset_;
+ float top_controls_height_;
+
+ float current_scroll_delta_;
+ float controls_scroll_begin_offset_;
+
+ // The height of the visible top control such that it must be shown when
+ // the user stops the scroll.
+ float top_controls_show_height_;
+
+ // The height of the visible top control such that it must be hidden when
+ // the user stops the scroll.
+ float top_controls_hide_height_;
+
+ DISALLOW_COPY_AND_ASSIGN(TopControlsManager);
+};
+
+} // namespace cc
+
+#endif // CC_INPUT_TOP_CONTROLS_MANAGER_H_
diff --git a/chromium/cc/input/top_controls_manager_client.h b/chromium/cc/input/top_controls_manager_client.h
new file mode 100644
index 00000000000..5fed3bf787e
--- /dev/null
+++ b/chromium/cc/input/top_controls_manager_client.h
@@ -0,0 +1,23 @@
+// Copyright 2013 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.
+
+#ifndef CC_INPUT_TOP_CONTROLS_MANAGER_CLIENT_H_
+#define CC_INPUT_TOP_CONTROLS_MANAGER_CLIENT_H_
+
+namespace cc {
+
+class LayerTreeImpl;
+
+class CC_EXPORT TopControlsManagerClient {
+ public:
+ virtual void DidChangeTopControlsPosition() = 0;
+ virtual bool HaveRootScrollLayer() const = 0;
+
+ protected:
+ virtual ~TopControlsManagerClient() {}
+};
+
+} // namespace cc
+
+#endif // CC_INPUT_TOP_CONTROLS_MANAGER_CLIENT_H_
diff --git a/chromium/cc/input/top_controls_manager_unittest.cc b/chromium/cc/input/top_controls_manager_unittest.cc
new file mode 100644
index 00000000000..da7475f576c
--- /dev/null
+++ b/chromium/cc/input/top_controls_manager_unittest.cc
@@ -0,0 +1,290 @@
+// Copyright 2013 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 "cc/input/top_controls_manager.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "cc/input/top_controls_manager_client.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/test/fake_impl_proxy.h"
+#include "cc/test/fake_layer_tree_host_impl.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/vector2d_f.h"
+
+namespace cc {
+namespace {
+
+static const float kTopControlsHeight = 100;
+
+class MockTopControlsManagerClient : public TopControlsManagerClient {
+ public:
+ MockTopControlsManagerClient(float top_controls_show_threshold,
+ float top_controls_hide_threshold)
+ : host_impl_(&proxy_),
+ redraw_needed_(false),
+ update_draw_properties_needed_(false),
+ top_controls_show_threshold_(top_controls_show_threshold),
+ top_controls_hide_threshold_(top_controls_hide_threshold) {
+ active_tree_ = LayerTreeImpl::create(&host_impl_);
+ root_scroll_layer_ = LayerImpl::Create(active_tree_.get(), 1);
+ }
+
+ virtual ~MockTopControlsManagerClient() {}
+
+ virtual void DidChangeTopControlsPosition() OVERRIDE {
+ redraw_needed_ = true;
+ update_draw_properties_needed_ = true;
+ }
+
+ virtual bool HaveRootScrollLayer() const OVERRIDE {
+ return true;
+ }
+
+ LayerImpl* rootScrollLayer() {
+ return root_scroll_layer_.get();
+ }
+
+ TopControlsManager* manager() {
+ if (!manager_) {
+ manager_ = TopControlsManager::Create(this,
+ kTopControlsHeight,
+ top_controls_show_threshold_,
+ top_controls_hide_threshold_);
+ }
+ return manager_.get();
+ }
+
+ private:
+ FakeImplProxy proxy_;
+ FakeLayerTreeHostImpl host_impl_;
+ scoped_ptr<LayerTreeImpl> active_tree_;
+ scoped_ptr<LayerImpl> root_scroll_layer_;
+ scoped_ptr<TopControlsManager> manager_;
+ bool redraw_needed_;
+ bool update_draw_properties_needed_;
+
+ float top_controls_show_threshold_;
+ float top_controls_hide_threshold_;
+};
+
+TEST(TopControlsManagerTest, EnsureScrollThresholdApplied) {
+ MockTopControlsManagerClient client(0.5f, 0.5f);
+ TopControlsManager* manager = client.manager();
+
+ manager->ScrollBegin();
+
+ // Scroll down to hide the controls entirely.
+ manager->ScrollBy(gfx::Vector2dF(0.f, 30.f));
+ EXPECT_EQ(-30.f, manager->controls_top_offset());
+
+ manager->ScrollBy(gfx::Vector2dF(0.f, 30.f));
+ EXPECT_EQ(-60.f, manager->controls_top_offset());
+
+ manager->ScrollBy(gfx::Vector2dF(0.f, 100.f));
+ EXPECT_EQ(-100.f, manager->controls_top_offset());
+
+ // Scroll back up a bit and ensure the controls don't move until we cross
+ // the threshold.
+ manager->ScrollBy(gfx::Vector2dF(0.f, -10.f));
+ EXPECT_EQ(-100.f, manager->controls_top_offset());
+
+ manager->ScrollBy(gfx::Vector2dF(0.f, -50.f));
+ EXPECT_EQ(-100.f, manager->controls_top_offset());
+
+ // After hitting the threshold, further scrolling up should result in the top
+ // controls showing.
+ manager->ScrollBy(gfx::Vector2dF(0.f, -10.f));
+ EXPECT_EQ(-90.f, manager->controls_top_offset());
+
+ manager->ScrollBy(gfx::Vector2dF(0.f, -50.f));
+ EXPECT_EQ(-40.f, manager->controls_top_offset());
+
+ // Reset the scroll threshold by going further up the page than the initial
+ // threshold.
+ manager->ScrollBy(gfx::Vector2dF(0.f, -100.f));
+ EXPECT_EQ(0.f, manager->controls_top_offset());
+
+ // See that scrolling down the page now will result in the controls hiding.
+ manager->ScrollBy(gfx::Vector2dF(0.f, 20.f));
+ EXPECT_EQ(-20.f, manager->controls_top_offset());
+
+ manager->ScrollEnd();
+}
+
+TEST(TopControlsManagerTest, PartialShownHideAnimation) {
+ MockTopControlsManagerClient client(0.5f, 0.5f);
+ TopControlsManager* manager = client.manager();
+ manager->ScrollBegin();
+ manager->ScrollBy(gfx::Vector2dF(0.f, 300.f));
+ EXPECT_EQ(-100.f, manager->controls_top_offset());
+ EXPECT_EQ(0.f, manager->content_top_offset());
+ manager->ScrollEnd();
+
+ manager->ScrollBegin();
+ manager->ScrollBy(gfx::Vector2dF(0.f, -15.f));
+ EXPECT_EQ(-85.f, manager->controls_top_offset());
+ EXPECT_EQ(15.f, manager->content_top_offset());
+ manager->ScrollEnd();
+
+ EXPECT_TRUE(manager->animation());
+
+ base::TimeTicks time = base::TimeTicks::Now();
+ float previous_offset = manager->controls_top_offset();
+ while (manager->animation()) {
+ time = base::TimeDelta::FromMicroseconds(100) + time;
+ manager->Animate(time);
+ EXPECT_LT(manager->controls_top_offset(), previous_offset);
+ previous_offset = manager->controls_top_offset();
+ }
+ EXPECT_FALSE(manager->animation());
+ EXPECT_EQ(-100.f, manager->controls_top_offset());
+ EXPECT_EQ(0.f, manager->content_top_offset());
+}
+
+TEST(TopControlsManagerTest, PartialShownShowAnimation) {
+ MockTopControlsManagerClient client(0.5f, 0.5f);
+ TopControlsManager* manager = client.manager();
+ manager->ScrollBegin();
+ manager->ScrollBy(gfx::Vector2dF(0.f, 300.f));
+ EXPECT_EQ(-100.f, manager->controls_top_offset());
+ EXPECT_EQ(0.f, manager->content_top_offset());
+ manager->ScrollEnd();
+
+ manager->ScrollBegin();
+ manager->ScrollBy(gfx::Vector2dF(0.f, -70.f));
+ EXPECT_EQ(-30.f, manager->controls_top_offset());
+ EXPECT_EQ(70.f, manager->content_top_offset());
+ manager->ScrollEnd();
+
+ EXPECT_TRUE(manager->animation());
+
+ base::TimeTicks time = base::TimeTicks::Now();
+ float previous_offset = manager->controls_top_offset();
+ while (manager->animation()) {
+ time = base::TimeDelta::FromMicroseconds(100) + time;
+ manager->Animate(time);
+ EXPECT_GT(manager->controls_top_offset(), previous_offset);
+ previous_offset = manager->controls_top_offset();
+ }
+ EXPECT_FALSE(manager->animation());
+ EXPECT_EQ(0.f, manager->controls_top_offset());
+ EXPECT_EQ(100.f, manager->content_top_offset());
+}
+
+TEST(TopControlsManagerTest, PartialHiddenWithAmbiguousThresholdShows) {
+ MockTopControlsManagerClient client(0.25f, 0.25f);
+ TopControlsManager* manager = client.manager();
+
+ manager->ScrollBegin();
+
+ manager->ScrollBy(gfx::Vector2dF(0.f, 20.f));
+ EXPECT_EQ(-20.f, manager->controls_top_offset());
+ EXPECT_EQ(80.f, manager->content_top_offset());
+
+ manager->ScrollEnd();
+ EXPECT_TRUE(manager->animation());
+
+ base::TimeTicks time = base::TimeTicks::Now();
+ float previous_offset = manager->controls_top_offset();
+ while (manager->animation()) {
+ time = base::TimeDelta::FromMicroseconds(100) + time;
+ manager->Animate(time);
+ EXPECT_GT(manager->controls_top_offset(), previous_offset);
+ previous_offset = manager->controls_top_offset();
+ }
+ EXPECT_FALSE(manager->animation());
+ EXPECT_EQ(0.f, manager->controls_top_offset());
+ EXPECT_EQ(100.f, manager->content_top_offset());
+}
+
+TEST(TopControlsManagerTest, PartialHiddenWithAmbiguousThresholdHides) {
+ MockTopControlsManagerClient client(0.25f, 0.25f);
+ TopControlsManager* manager = client.manager();
+
+ manager->ScrollBegin();
+
+ manager->ScrollBy(gfx::Vector2dF(0.f, 30.f));
+ EXPECT_EQ(-30.f, manager->controls_top_offset());
+ EXPECT_EQ(70.f, manager->content_top_offset());
+
+ manager->ScrollEnd();
+ EXPECT_TRUE(manager->animation());
+
+ base::TimeTicks time = base::TimeTicks::Now();
+ float previous_offset = manager->controls_top_offset();
+ while (manager->animation()) {
+ time = base::TimeDelta::FromMicroseconds(100) + time;
+ manager->Animate(time);
+ EXPECT_LT(manager->controls_top_offset(), previous_offset);
+ previous_offset = manager->controls_top_offset();
+ }
+ EXPECT_FALSE(manager->animation());
+ EXPECT_EQ(-100.f, manager->controls_top_offset());
+ EXPECT_EQ(0.f, manager->content_top_offset());
+}
+
+TEST(TopControlsManagerTest, PartialShownWithAmbiguousThresholdHides) {
+ MockTopControlsManagerClient client(0.25f, 0.25f);
+ TopControlsManager* manager = client.manager();
+
+ manager->ScrollBy(gfx::Vector2dF(0.f, 200.f));
+ EXPECT_EQ(-100.f, manager->controls_top_offset());
+ EXPECT_EQ(0.f, manager->content_top_offset());
+
+ manager->ScrollBegin();
+
+ manager->ScrollBy(gfx::Vector2dF(0.f, -20.f));
+ EXPECT_EQ(-80.f, manager->controls_top_offset());
+ EXPECT_EQ(20.f, manager->content_top_offset());
+
+ manager->ScrollEnd();
+ EXPECT_TRUE(manager->animation());
+
+ base::TimeTicks time = base::TimeTicks::Now();
+ float previous_offset = manager->controls_top_offset();
+ while (manager->animation()) {
+ time = base::TimeDelta::FromMicroseconds(100) + time;
+ manager->Animate(time);
+ EXPECT_LT(manager->controls_top_offset(), previous_offset);
+ previous_offset = manager->controls_top_offset();
+ }
+ EXPECT_FALSE(manager->animation());
+ EXPECT_EQ(-100.f, manager->controls_top_offset());
+ EXPECT_EQ(0.f, manager->content_top_offset());
+}
+
+TEST(TopControlsManagerTest, PartialShownWithAmbiguousThresholdShows) {
+ MockTopControlsManagerClient client(0.25f, 0.25f);
+ TopControlsManager* manager = client.manager();
+
+ manager->ScrollBy(gfx::Vector2dF(0.f, 200.f));
+ EXPECT_EQ(-100.f, manager->controls_top_offset());
+ EXPECT_EQ(0.f, manager->content_top_offset());
+
+ manager->ScrollBegin();
+
+ manager->ScrollBy(gfx::Vector2dF(0.f, -30.f));
+ EXPECT_EQ(-70.f, manager->controls_top_offset());
+ EXPECT_EQ(30.f, manager->content_top_offset());
+
+ manager->ScrollEnd();
+ EXPECT_TRUE(manager->animation());
+
+ base::TimeTicks time = base::TimeTicks::Now();
+ float previous_offset = manager->controls_top_offset();
+ while (manager->animation()) {
+ time = base::TimeDelta::FromMicroseconds(100) + time;
+ manager->Animate(time);
+ EXPECT_GT(manager->controls_top_offset(), previous_offset);
+ previous_offset = manager->controls_top_offset();
+ }
+ EXPECT_FALSE(manager->animation());
+ EXPECT_EQ(0.f, manager->controls_top_offset());
+ EXPECT_EQ(100.f, manager->content_top_offset());
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/input/top_controls_state.h b/chromium/cc/input/top_controls_state.h
new file mode 100644
index 00000000000..87b86c02691
--- /dev/null
+++ b/chromium/cc/input/top_controls_state.h
@@ -0,0 +1,17 @@
+// Copyright (c) 2013 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.
+
+#ifndef CC_INPUT_TOP_CONTROLS_STATE_H_
+#define CC_INPUT_TOP_CONTROLS_STATE_H_
+
+namespace cc {
+enum TopControlsState {
+ SHOWN = 1,
+ HIDDEN = 2,
+ BOTH = 3
+};
+}
+
+#endif // CC_INPUT_TOP_CONTROLS_STATE_H_
+
diff --git a/chromium/cc/layers/append_quads_data.h b/chromium/cc/layers/append_quads_data.h
new file mode 100644
index 00000000000..1ec8184a17a
--- /dev/null
+++ b/chromium/cc/layers/append_quads_data.h
@@ -0,0 +1,37 @@
+// 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.
+
+#ifndef CC_LAYERS_APPEND_QUADS_DATA_H_
+#define CC_LAYERS_APPEND_QUADS_DATA_H_
+
+#include "base/basictypes.h"
+#include "cc/quads/render_pass.h"
+
+namespace cc {
+
+struct AppendQuadsData {
+ AppendQuadsData()
+ : had_occlusion_from_outside_target_surface(false),
+ had_incomplete_tile(false),
+ num_missing_tiles(0),
+ render_pass_id(0, 0) {}
+
+ explicit AppendQuadsData(RenderPass::Id render_pass_id)
+ : had_occlusion_from_outside_target_surface(false),
+ had_incomplete_tile(false),
+ num_missing_tiles(0),
+ render_pass_id(render_pass_id) {}
+
+ // Set by the QuadCuller.
+ bool had_occlusion_from_outside_target_surface;
+ // Set by the layer appending quads.
+ bool had_incomplete_tile;
+ // Set by the layer appending quads.
+ int64 num_missing_tiles;
+ // Given to the layer appending quads.
+ const RenderPass::Id render_pass_id;
+};
+
+} // namespace cc
+#endif // CC_LAYERS_APPEND_QUADS_DATA_H_
diff --git a/chromium/cc/layers/compositing_reasons.h b/chromium/cc/layers/compositing_reasons.h
new file mode 100644
index 00000000000..28c2f7b4dc6
--- /dev/null
+++ b/chromium/cc/layers/compositing_reasons.h
@@ -0,0 +1,60 @@
+// Copyright 2013 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.
+
+#ifndef CC_LAYERS_COMPOSITING_REASONS_H_
+#define CC_LAYERS_COMPOSITING_REASONS_H_
+
+#include "base/port.h"
+
+namespace cc {
+
+// This is a clone of CompositingReasons and WebCompositingReasons from Blink.
+const uint64 kCompositingReasonUnknown = 0;
+const uint64 kCompositingReason3DTransform = GG_UINT64_C(1) << 0;
+const uint64 kCompositingReasonVideo = GG_UINT64_C(1) << 1;
+const uint64 kCompositingReasonCanvas = GG_UINT64_C(1) << 2;
+const uint64 kCompositingReasonPlugin = GG_UINT64_C(1) << 3;
+const uint64 kCompositingReasonIFrame = GG_UINT64_C(1) << 4;
+const uint64 kCompositingReasonBackfaceVisibilityHidden = GG_UINT64_C(1) << 5;
+const uint64 kCompositingReasonAnimation = GG_UINT64_C(1) << 6;
+const uint64 kCompositingReasonFilters = GG_UINT64_C(1) << 7;
+const uint64 kCompositingReasonPositionFixed = GG_UINT64_C(1) << 8;
+const uint64 kCompositingReasonPositionSticky = GG_UINT64_C(1) << 9;
+const uint64 kCompositingReasonOverflowScrollingTouch = GG_UINT64_C(1) << 10;
+const uint64 kCompositingReasonBlending = GG_UINT64_C(1) << 11;
+const uint64 kCompositingReasonAssumedOverlap = GG_UINT64_C(1) << 12;
+const uint64 kCompositingReasonOverlap = GG_UINT64_C(1) << 13;
+const uint64 kCompositingReasonNegativeZIndexChildren = GG_UINT64_C(1) << 14;
+const uint64 kCompositingReasonTransformWithCompositedDescendants =
+ GG_UINT64_C(1) << 15;
+const uint64 kCompositingReasonOpacityWithCompositedDescendants =
+ GG_UINT64_C(1) << 16;
+const uint64 kCompositingReasonMaskWithCompositedDescendants =
+ GG_UINT64_C(1) << 17;
+const uint64 kCompositingReasonReflectionWithCompositedDescendants =
+ GG_UINT64_C(1) << 18;
+const uint64 kCompositingReasonFilterWithCompositedDescendants =
+ GG_UINT64_C(1) << 19;
+const uint64 kCompositingReasonBlendingWithCompositedDescendants =
+ GG_UINT64_C(1) << 20;
+const uint64 kCompositingReasonClipsCompositingDescendants =
+ GG_UINT64_C(1) << 21;
+const uint64 kCompositingReasonPerspective = GG_UINT64_C(1) << 22;
+const uint64 kCompositingReasonPreserve3D = GG_UINT64_C(1) << 23;
+const uint64 kCompositingReasonReflectionOfCompositedParent =
+ GG_UINT64_C(1) << 24;
+const uint64 kCompositingReasonRoot = GG_UINT64_C(1) << 25;
+const uint64 kCompositingReasonLayerForClip = GG_UINT64_C(1) << 26;
+const uint64 kCompositingReasonLayerForScrollbar = GG_UINT64_C(1) << 27;
+const uint64 kCompositingReasonLayerForScrollingContainer =
+ GG_UINT64_C(1) << 28;
+const uint64 kCompositingReasonLayerForForeground = GG_UINT64_C(1) << 29;
+const uint64 kCompositingReasonLayerForBackground = GG_UINT64_C(1) << 30;
+const uint64 kCompositingReasonLayerForMask = GG_UINT64_C(1) << 31;
+
+typedef uint64 CompositingReasons;
+
+} // namespace cc
+
+#endif // CC_LAYERS_COMPOSITING_REASONS_H_
diff --git a/chromium/cc/layers/content_layer.cc b/chromium/cc/layers/content_layer.cc
new file mode 100644
index 00000000000..2053ecbeac1
--- /dev/null
+++ b/chromium/cc/layers/content_layer.cc
@@ -0,0 +1,148 @@
+// Copyright 2010 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 "cc/layers/content_layer.h"
+
+#include "base/auto_reset.h"
+#include "base/metrics/histogram.h"
+#include "base/time/time.h"
+#include "cc/layers/content_layer_client.h"
+#include "cc/resources/bitmap_content_layer_updater.h"
+#include "cc/resources/bitmap_skpicture_content_layer_updater.h"
+#include "cc/resources/layer_painter.h"
+#include "cc/trees/layer_tree_host.h"
+
+namespace cc {
+
+ContentLayerPainter::ContentLayerPainter(ContentLayerClient* client)
+ : client_(client) {}
+
+scoped_ptr<ContentLayerPainter> ContentLayerPainter::Create(
+ ContentLayerClient* client) {
+ return make_scoped_ptr(new ContentLayerPainter(client));
+}
+
+void ContentLayerPainter::Paint(SkCanvas* canvas,
+ gfx::Rect content_rect,
+ gfx::RectF* opaque) {
+ base::TimeTicks paint_start = base::TimeTicks::HighResNow();
+ client_->PaintContents(canvas, content_rect, opaque);
+ base::TimeTicks paint_end = base::TimeTicks::HighResNow();
+ double pixels_per_sec = (content_rect.width() * content_rect.height()) /
+ (paint_end - paint_start).InSecondsF();
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Renderer4.AccelContentPaintDurationMS",
+ (paint_end - paint_start).InMilliseconds(),
+ 0,
+ 120,
+ 30);
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Renderer4.AccelContentPaintMegapixPerSecond",
+ pixels_per_sec / 1000000,
+ 10,
+ 210,
+ 30);
+}
+
+scoped_refptr<ContentLayer> ContentLayer::Create(ContentLayerClient* client) {
+ return make_scoped_refptr(new ContentLayer(client));
+}
+
+ContentLayer::ContentLayer(ContentLayerClient* client)
+ : TiledLayer(),
+ client_(client),
+ can_use_lcd_text_last_frame_(can_use_lcd_text()) {
+}
+
+ContentLayer::~ContentLayer() {}
+
+bool ContentLayer::DrawsContent() const {
+ return TiledLayer::DrawsContent() && client_;
+}
+
+void ContentLayer::SetLayerTreeHost(LayerTreeHost* host) {
+ TiledLayer::SetLayerTreeHost(host);
+
+ if (!updater_.get())
+ return;
+
+ if (host) {
+ updater_->set_rendering_stats_instrumentation(
+ host->rendering_stats_instrumentation());
+ } else {
+ updater_->set_rendering_stats_instrumentation(NULL);
+ }
+}
+
+void ContentLayer::SetTexturePriorities(
+ const PriorityCalculator& priority_calc) {
+ // Update the tile data before creating all the layer's tiles.
+ UpdateTileSizeAndTilingOption();
+
+ TiledLayer::SetTexturePriorities(priority_calc);
+}
+
+bool ContentLayer::Update(ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion) {
+ {
+ base::AutoReset<bool> ignore_set_needs_commit(&ignore_set_needs_commit_,
+ true);
+
+ CreateUpdaterIfNeeded();
+ UpdateCanUseLCDText();
+ }
+
+ bool updated = TiledLayer::Update(queue, occlusion);
+ return updated;
+}
+
+bool ContentLayer::NeedMoreUpdates() {
+ return NeedsIdlePaint();
+}
+
+LayerUpdater* ContentLayer::Updater() const {
+ return updater_.get();
+}
+
+void ContentLayer::CreateUpdaterIfNeeded() {
+ if (updater_.get())
+ return;
+ scoped_ptr<LayerPainter> painter =
+ ContentLayerPainter::Create(client_).PassAs<LayerPainter>();
+ if (layer_tree_host()->settings().per_tile_painting_enabled) {
+ updater_ = BitmapSkPictureContentLayerUpdater::Create(
+ painter.Pass(),
+ rendering_stats_instrumentation(),
+ id());
+ } else {
+ updater_ = BitmapContentLayerUpdater::Create(
+ painter.Pass(),
+ rendering_stats_instrumentation(),
+ id());
+ }
+ updater_->SetOpaque(contents_opaque());
+
+ unsigned texture_format =
+ layer_tree_host()->GetRendererCapabilities().best_texture_format;
+ SetTextureFormat(texture_format);
+}
+
+void ContentLayer::SetContentsOpaque(bool opaque) {
+ Layer::SetContentsOpaque(opaque);
+ if (updater_.get())
+ updater_->SetOpaque(opaque);
+}
+
+void ContentLayer::UpdateCanUseLCDText() {
+ if (can_use_lcd_text_last_frame_ == can_use_lcd_text())
+ return;
+
+ can_use_lcd_text_last_frame_ = can_use_lcd_text();
+ if (client_)
+ client_->DidChangeLayerCanUseLCDText();
+}
+
+bool ContentLayer::SupportsLCDText() const {
+ return true;
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/content_layer.h b/chromium/cc/layers/content_layer.h
new file mode 100644
index 00000000000..f49215b6b40
--- /dev/null
+++ b/chromium/cc/layers/content_layer.h
@@ -0,0 +1,74 @@
+// Copyright 2010 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.
+
+#ifndef CC_LAYERS_CONTENT_LAYER_H_
+#define CC_LAYERS_CONTENT_LAYER_H_
+
+#include "base/basictypes.h"
+#include "cc/base/cc_export.h"
+#include "cc/layers/tiled_layer.h"
+#include "cc/resources/layer_painter.h"
+
+class SkCanvas;
+
+namespace cc {
+
+class ContentLayerClient;
+class ContentLayerUpdater;
+
+class CC_EXPORT ContentLayerPainter : public LayerPainter {
+ public:
+ static scoped_ptr<ContentLayerPainter> Create(ContentLayerClient* client);
+
+ virtual void Paint(SkCanvas* canvas,
+ gfx::Rect content_rect,
+ gfx::RectF* opaque) OVERRIDE;
+
+ private:
+ explicit ContentLayerPainter(ContentLayerClient* client);
+
+ ContentLayerClient* client_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContentLayerPainter);
+};
+
+// A layer that renders its contents into an SkCanvas.
+class CC_EXPORT ContentLayer : public TiledLayer {
+ public:
+ static scoped_refptr<ContentLayer> Create(ContentLayerClient* client);
+
+ void ClearClient() { client_ = NULL; }
+
+ virtual bool DrawsContent() const OVERRIDE;
+ virtual void SetLayerTreeHost(LayerTreeHost* layer_tree_host) OVERRIDE;
+ virtual void SetTexturePriorities(const PriorityCalculator& priority_calc)
+ OVERRIDE;
+ virtual bool Update(ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion) OVERRIDE;
+ virtual bool NeedMoreUpdates() OVERRIDE;
+
+ virtual void SetContentsOpaque(bool contents_opaque) OVERRIDE;
+
+ virtual bool SupportsLCDText() const OVERRIDE;
+
+ protected:
+ explicit ContentLayer(ContentLayerClient* client);
+ virtual ~ContentLayer();
+
+ private:
+ // TiledLayer implementation.
+ virtual LayerUpdater* Updater() const OVERRIDE;
+ virtual void CreateUpdaterIfNeeded() OVERRIDE;
+
+ void UpdateCanUseLCDText();
+
+ ContentLayerClient* client_;
+ scoped_refptr<ContentLayerUpdater> updater_;
+ bool can_use_lcd_text_last_frame_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContentLayer);
+};
+
+} // namespace cc
+#endif // CC_LAYERS_CONTENT_LAYER_H_
diff --git a/chromium/cc/layers/content_layer_client.h b/chromium/cc/layers/content_layer_client.h
new file mode 100644
index 00000000000..e59c1d8c3e1
--- /dev/null
+++ b/chromium/cc/layers/content_layer_client.h
@@ -0,0 +1,35 @@
+// 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.
+
+#ifndef CC_LAYERS_CONTENT_LAYER_CLIENT_H_
+#define CC_LAYERS_CONTENT_LAYER_CLIENT_H_
+
+#include "cc/base/cc_export.h"
+
+class SkCanvas;
+
+namespace gfx {
+class Rect;
+class RectF;
+}
+
+namespace cc {
+
+class CC_EXPORT ContentLayerClient {
+ public:
+ virtual void PaintContents(SkCanvas* canvas,
+ gfx::Rect clip,
+ gfx::RectF* opaque) = 0;
+
+ // Called by the content layer during the update phase.
+ // If the client paints LCD text, it may want to invalidate the layer.
+ virtual void DidChangeLayerCanUseLCDText() = 0;
+
+ protected:
+ virtual ~ContentLayerClient() {}
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_CONTENT_LAYER_CLIENT_H_
diff --git a/chromium/cc/layers/content_layer_unittest.cc b/chromium/cc/layers/content_layer_unittest.cc
new file mode 100644
index 00000000000..724845883e9
--- /dev/null
+++ b/chromium/cc/layers/content_layer_unittest.cc
@@ -0,0 +1,60 @@
+// 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 "cc/layers/content_layer.h"
+
+#include "cc/layers/content_layer_client.h"
+#include "cc/resources/bitmap_content_layer_updater.h"
+#include "cc/test/fake_rendering_stats_instrumentation.h"
+#include "cc/test/geometry_test_utils.h"
+#include "skia/ext/platform_canvas.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/rect_conversions.h"
+
+namespace cc {
+namespace {
+
+class MockContentLayerClient : public ContentLayerClient {
+ public:
+ explicit MockContentLayerClient(gfx::Rect opaque_layer_rect)
+ : opaque_layer_rect_(opaque_layer_rect) {}
+
+ virtual void PaintContents(SkCanvas* canvas,
+ gfx::Rect clip,
+ gfx::RectF* opaque) OVERRIDE {
+ *opaque = gfx::RectF(opaque_layer_rect_);
+ }
+ virtual void DidChangeLayerCanUseLCDText() OVERRIDE {}
+
+ private:
+ gfx::Rect opaque_layer_rect_;
+};
+
+TEST(ContentLayerTest, ContentLayerPainterWithDeviceScale) {
+ float contents_scale = 2.f;
+ gfx::Rect content_rect(10, 10, 100, 100);
+ gfx::Rect opaque_rect_in_layer_space(5, 5, 20, 20);
+ gfx::Rect opaque_rect_in_content_space = gfx::ScaleToEnclosingRect(
+ opaque_rect_in_layer_space, contents_scale, contents_scale);
+ MockContentLayerClient client(opaque_rect_in_layer_space);
+ FakeRenderingStatsInstrumentation stats_instrumentation;
+ scoped_refptr<BitmapContentLayerUpdater> updater =
+ BitmapContentLayerUpdater::Create(
+ ContentLayerPainter::Create(&client).PassAs<LayerPainter>(),
+ &stats_instrumentation,
+ 0);
+
+ gfx::Rect resulting_opaque_rect;
+ updater->PrepareToUpdate(content_rect,
+ gfx::Size(256, 256),
+ contents_scale,
+ contents_scale,
+ &resulting_opaque_rect);
+
+ EXPECT_EQ(opaque_rect_in_content_space.ToString(),
+ resulting_opaque_rect.ToString());
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/layers/contents_scaling_layer.cc b/chromium/cc/layers/contents_scaling_layer.cc
new file mode 100644
index 00000000000..44a3f372c65
--- /dev/null
+++ b/chromium/cc/layers/contents_scaling_layer.cc
@@ -0,0 +1,54 @@
+// 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 "cc/layers/contents_scaling_layer.h"
+#include "ui/gfx/size_conversions.h"
+
+namespace cc {
+
+gfx::Size ContentsScalingLayer::ComputeContentBoundsForScale(
+ float scale_x,
+ float scale_y) const {
+ return gfx::ToCeiledSize(gfx::ScaleSize(bounds(), scale_x, scale_y));
+}
+
+ContentsScalingLayer::ContentsScalingLayer()
+ : last_update_contents_scale_x_(0.f),
+ last_update_contents_scale_y_(0.f) {}
+
+ContentsScalingLayer::~ContentsScalingLayer() {
+}
+
+void ContentsScalingLayer::CalculateContentsScale(
+ float ideal_contents_scale,
+ float device_scale_factor,
+ float page_scale_factor,
+ bool animating_transform_to_screen,
+ float* contents_scale_x,
+ float* contents_scale_y,
+ gfx::Size* content_bounds) {
+ *contents_scale_x = ideal_contents_scale;
+ *contents_scale_y = ideal_contents_scale;
+ *content_bounds = ComputeContentBoundsForScale(
+ ideal_contents_scale,
+ ideal_contents_scale);
+}
+
+bool ContentsScalingLayer::Update(
+ ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion) {
+ bool updated = Layer::Update(queue, occlusion);
+
+ if (draw_properties().contents_scale_x == last_update_contents_scale_x_ &&
+ draw_properties().contents_scale_y == last_update_contents_scale_y_)
+ return updated;
+
+ last_update_contents_scale_x_ = draw_properties().contents_scale_x;
+ last_update_contents_scale_y_ = draw_properties().contents_scale_y;
+ // Invalidate the whole layer if scale changed.
+ SetNeedsDisplayRect(gfx::Rect(paint_properties().bounds));
+ return updated;
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/contents_scaling_layer.h b/chromium/cc/layers/contents_scaling_layer.h
new file mode 100644
index 00000000000..a2648d1e01a
--- /dev/null
+++ b/chromium/cc/layers/contents_scaling_layer.h
@@ -0,0 +1,45 @@
+// 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.
+
+#ifndef CC_LAYERS_CONTENTS_SCALING_LAYER_H_
+#define CC_LAYERS_CONTENTS_SCALING_LAYER_H_
+
+#include "cc/base/cc_export.h"
+#include "cc/layers/layer.h"
+
+namespace cc {
+
+// Base class for layers that need contents scale.
+// The content bounds are determined by bounds and scale of the contents.
+class CC_EXPORT ContentsScalingLayer : public Layer {
+ public:
+ virtual void CalculateContentsScale(
+ float ideal_contents_scale,
+ float device_scale_factor,
+ float page_scale_factor,
+ bool animating_transform_to_screen,
+ float* contents_scale_x,
+ float* contents_scale_y,
+ gfx::Size* content_bounds) OVERRIDE;
+
+ virtual bool Update(
+ ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion) OVERRIDE;
+
+ protected:
+ ContentsScalingLayer();
+ virtual ~ContentsScalingLayer();
+
+ gfx::Size ComputeContentBoundsForScale(float scale_x, float scale_y) const;
+
+ private:
+ float last_update_contents_scale_x_;
+ float last_update_contents_scale_y_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContentsScalingLayer);
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_CONTENTS_SCALING_LAYER_H__
diff --git a/chromium/cc/layers/contents_scaling_layer_unittest.cc b/chromium/cc/layers/contents_scaling_layer_unittest.cc
new file mode 100644
index 00000000000..a75046d932d
--- /dev/null
+++ b/chromium/cc/layers/contents_scaling_layer_unittest.cc
@@ -0,0 +1,77 @@
+// 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 "cc/layers/contents_scaling_layer.h"
+
+#include <vector>
+
+#include "cc/test/fake_layer_tree_host.h"
+#include "cc/test/geometry_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+class MockContentsScalingLayer : public ContentsScalingLayer {
+ public:
+ MockContentsScalingLayer()
+ : ContentsScalingLayer() {}
+
+ virtual void SetNeedsDisplayRect(const gfx::RectF& dirty_rect) OVERRIDE {
+ last_needs_display_rect_ = dirty_rect;
+ ContentsScalingLayer::SetNeedsDisplayRect(dirty_rect);
+ }
+
+ const gfx::RectF& LastNeedsDisplayRect() const {
+ return last_needs_display_rect_;
+ }
+
+ private:
+ virtual ~MockContentsScalingLayer() {}
+
+ gfx::RectF last_needs_display_rect_;
+};
+
+static void CalcDrawProps(FakeLayerTreeHost* host, float device_scale_factor) {
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ host->root_layer(), gfx::Size(500, 500), &render_surface_layer_list);
+ inputs.device_scale_factor = device_scale_factor;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+}
+
+TEST(ContentsScalingLayerTest, CheckContentsBounds) {
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+
+ scoped_refptr<MockContentsScalingLayer> test_layer =
+ make_scoped_refptr(new MockContentsScalingLayer());
+
+ scoped_refptr<Layer> root = Layer::Create();
+ root->AddChild(test_layer);
+ host->SetRootLayer(root);
+
+ test_layer->SetBounds(gfx::Size(320, 240));
+
+ CalcDrawProps(host.get(), 1.f);
+ EXPECT_FLOAT_EQ(1.f, test_layer->contents_scale_x());
+ EXPECT_FLOAT_EQ(1.f, test_layer->contents_scale_y());
+ EXPECT_EQ(320, test_layer->content_bounds().width());
+ EXPECT_EQ(240, test_layer->content_bounds().height());
+
+ CalcDrawProps(host.get(), 2.f);
+ EXPECT_EQ(640, test_layer->content_bounds().width());
+ EXPECT_EQ(480, test_layer->content_bounds().height());
+
+ test_layer->SetBounds(gfx::Size(10, 20));
+ CalcDrawProps(host.get(), 2.f);
+ EXPECT_EQ(20, test_layer->content_bounds().width());
+ EXPECT_EQ(40, test_layer->content_bounds().height());
+
+ CalcDrawProps(host.get(), 1.33f);
+ EXPECT_EQ(14, test_layer->content_bounds().width());
+ EXPECT_EQ(27, test_layer->content_bounds().height());
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/layers/delegated_renderer_layer.cc b/chromium/cc/layers/delegated_renderer_layer.cc
new file mode 100644
index 00000000000..66064d9e849
--- /dev/null
+++ b/chromium/cc/layers/delegated_renderer_layer.cc
@@ -0,0 +1,114 @@
+// 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 "cc/layers/delegated_renderer_layer.h"
+
+#include "cc/layers/delegated_renderer_layer_client.h"
+#include "cc/layers/delegated_renderer_layer_impl.h"
+#include "cc/output/delegated_frame_data.h"
+
+namespace cc {
+
+scoped_refptr<DelegatedRendererLayer> DelegatedRendererLayer::Create(
+ DelegatedRendererLayerClient* client) {
+ return scoped_refptr<DelegatedRendererLayer>(
+ new DelegatedRendererLayer(client));
+}
+
+DelegatedRendererLayer::DelegatedRendererLayer(
+ DelegatedRendererLayerClient* client)
+ : Layer(),
+ client_(client) {}
+
+DelegatedRendererLayer::~DelegatedRendererLayer() {}
+
+scoped_ptr<LayerImpl> DelegatedRendererLayer::CreateLayerImpl(
+ LayerTreeImpl* tree_impl) {
+ return DelegatedRendererLayerImpl::Create(
+ tree_impl, layer_id_).PassAs<LayerImpl>();
+}
+
+bool DelegatedRendererLayer::DrawsContent() const {
+ return Layer::DrawsContent() && !frame_size_.IsEmpty();
+}
+
+void DelegatedRendererLayer::PushPropertiesTo(LayerImpl* impl) {
+ Layer::PushPropertiesTo(impl);
+
+ DelegatedRendererLayerImpl* delegated_impl =
+ static_cast<DelegatedRendererLayerImpl*>(impl);
+
+ delegated_impl->SetDisplaySize(display_size_);
+
+ if (frame_data_)
+ delegated_impl->SetFrameData(frame_data_.Pass(), damage_in_frame_);
+ frame_data_.reset();
+ damage_in_frame_ = gfx::RectF();
+
+ delegated_impl->CollectUnusedResources(
+ &unused_resources_for_child_compositor_);
+
+ if (client_)
+ client_->DidCommitFrameData();
+
+ // TODO(danakj): TakeUnusedResourcesForChildCompositor requires a push
+ // properties to happen in order to collect unused resources returned
+ // from the parent compositor. crbug.com/259090
+ needs_push_properties_ = true;
+}
+
+void DelegatedRendererLayer::SetDisplaySize(gfx::Size size) {
+ if (display_size_ == size)
+ return;
+ display_size_ = size;
+ SetNeedsCommit();
+}
+
+void DelegatedRendererLayer::SetFrameData(
+ scoped_ptr<DelegatedFrameData> new_frame_data) {
+ if (frame_data_) {
+ // Copy the resources from the last provided frame into the new frame, as
+ // it may use resources that were transferred in the last frame.
+ new_frame_data->resource_list.insert(new_frame_data->resource_list.end(),
+ frame_data_->resource_list.begin(),
+ frame_data_->resource_list.end());
+ }
+ frame_data_ = new_frame_data.Pass();
+ if (!frame_data_->render_pass_list.empty()) {
+ RenderPass* root_pass = frame_data_->render_pass_list.back();
+ damage_in_frame_.Union(root_pass->damage_rect);
+ frame_size_ = root_pass->output_rect.size();
+
+ // TODO(danakj): This could be optimized to only add resources to the
+ // frame_data_ if they are actually used in the frame. For now, it will
+ // cause the parent (this layer) to hold onto some resources it doesn't
+ // need to for an extra frame.
+ for (size_t i = 0; i < unused_resources_for_child_compositor_.size(); ++i) {
+ frame_data_->resource_list.push_back(
+ unused_resources_for_child_compositor_[i]);
+ }
+ unused_resources_for_child_compositor_.clear();
+ } else {
+ frame_size_ = gfx::Size();
+ }
+ SetNeedsCommit();
+}
+
+void DelegatedRendererLayer::TakeUnusedResourcesForChildCompositor(
+ TransferableResourceArray* array) {
+ DCHECK(array->empty());
+ array->clear();
+
+ array->swap(unused_resources_for_child_compositor_);
+}
+
+bool DelegatedRendererLayer::BlocksPendingCommit() const {
+ // The active frame needs to be replaced and resources returned before the
+ // commit is called complete. This is true even whenever there may be
+ // resources to return, regardless of if the layer will draw in its new
+ // state.
+ return true;
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/delegated_renderer_layer.h b/chromium/cc/layers/delegated_renderer_layer.h
new file mode 100644
index 00000000000..3f5280eec5f
--- /dev/null
+++ b/chromium/cc/layers/delegated_renderer_layer.h
@@ -0,0 +1,58 @@
+// 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.
+
+#ifndef CC_LAYERS_DELEGATED_RENDERER_LAYER_H_
+#define CC_LAYERS_DELEGATED_RENDERER_LAYER_H_
+
+#include "cc/base/cc_export.h"
+#include "cc/layers/layer.h"
+#include "cc/resources/transferable_resource.h"
+
+namespace cc {
+
+class DelegatedFrameData;
+class DelegatedRendererLayerClient;
+
+class CC_EXPORT DelegatedRendererLayer : public Layer {
+ public:
+ static scoped_refptr<DelegatedRendererLayer> Create(
+ DelegatedRendererLayerClient* client);
+
+ virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl)
+ OVERRIDE;
+ virtual void PushPropertiesTo(LayerImpl* impl) OVERRIDE;
+ virtual bool DrawsContent() const OVERRIDE;
+
+ // Set the size at which the frame should be displayed, with the origin at the
+ // layer's origin. This must always contain at least the layer's bounds. A
+ // value of (0, 0) implies that the frame should be displayed to fit exactly
+ // in the layer's bounds.
+ void SetDisplaySize(gfx::Size size);
+
+ void SetFrameData(scoped_ptr<DelegatedFrameData> frame_data);
+
+ // Passes ownership of any unused resources that had been given by the child
+ // compositor to the given array, so they can be given back to the child.
+ void TakeUnusedResourcesForChildCompositor(TransferableResourceArray* array);
+
+ virtual bool BlocksPendingCommit() const OVERRIDE;
+
+ protected:
+ explicit DelegatedRendererLayer(DelegatedRendererLayerClient* client);
+ virtual ~DelegatedRendererLayer();
+
+ private:
+ scoped_ptr<DelegatedFrameData> frame_data_;
+ gfx::RectF damage_in_frame_;
+ gfx::Size frame_size_;
+ gfx::Size display_size_;
+ TransferableResourceArray unused_resources_for_child_compositor_;
+
+ DelegatedRendererLayerClient* client_;
+
+ DISALLOW_COPY_AND_ASSIGN(DelegatedRendererLayer);
+};
+
+} // namespace cc
+#endif // CC_LAYERS_DELEGATED_RENDERER_LAYER_H_
diff --git a/chromium/cc/layers/delegated_renderer_layer_client.h b/chromium/cc/layers/delegated_renderer_layer_client.h
new file mode 100644
index 00000000000..eb599358074
--- /dev/null
+++ b/chromium/cc/layers/delegated_renderer_layer_client.h
@@ -0,0 +1,23 @@
+// Copyright 2013 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.
+
+#ifndef CC_LAYERS_DELEGATED_RENDERER_LAYER_CLIENT_H_
+#define CC_LAYERS_DELEGATED_RENDERER_LAYER_CLIENT_H_
+
+#include "cc/base/cc_export.h"
+
+namespace cc {
+
+class CC_EXPORT DelegatedRendererLayerClient {
+ public:
+ // Called after the object passed in SetFrameData was handed over
+ // to the DelegatedRendererLayerImpl.
+ virtual void DidCommitFrameData() = 0;
+
+ protected:
+ virtual ~DelegatedRendererLayerClient() {}
+};
+
+} // namespace cc
+#endif // CC_LAYERS_DELEGATED_RENDERER_LAYER_CLIENT_H_
diff --git a/chromium/cc/layers/delegated_renderer_layer_impl.cc b/chromium/cc/layers/delegated_renderer_layer_impl.cc
new file mode 100644
index 00000000000..2dfdac6eb2d
--- /dev/null
+++ b/chromium/cc/layers/delegated_renderer_layer_impl.cc
@@ -0,0 +1,489 @@
+// 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 "cc/layers/delegated_renderer_layer_impl.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/containers/hash_tables.h"
+#include "cc/base/math_util.h"
+#include "cc/layers/append_quads_data.h"
+#include "cc/layers/quad_sink.h"
+#include "cc/layers/render_pass_sink.h"
+#include "cc/output/delegated_frame_data.h"
+#include "cc/quads/render_pass_draw_quad.h"
+#include "cc/quads/solid_color_draw_quad.h"
+#include "cc/trees/layer_tree_impl.h"
+
+namespace cc {
+
+DelegatedRendererLayerImpl::DelegatedRendererLayerImpl(
+ LayerTreeImpl* tree_impl, int id)
+ : LayerImpl(tree_impl, id),
+ have_render_passes_to_push_(false),
+ child_id_(0),
+ own_child_id_(false) {
+}
+
+DelegatedRendererLayerImpl::~DelegatedRendererLayerImpl() {
+ ClearRenderPasses();
+ ClearChildId();
+}
+
+bool DelegatedRendererLayerImpl::HasDelegatedContent() const {
+ return !render_passes_in_draw_order_.empty();
+}
+
+bool DelegatedRendererLayerImpl::HasContributingDelegatedRenderPasses() const {
+ // The root RenderPass for the layer is merged with its target
+ // RenderPass in each frame. So we only have extra RenderPasses
+ // to merge when we have a non-root RenderPass present.
+ return render_passes_in_draw_order_.size() > 1;
+}
+
+static ResourceProvider::ResourceId ResourceRemapHelper(
+ bool* invalid_frame,
+ const ResourceProvider::ResourceIdMap& child_to_parent_map,
+ ResourceProvider::ResourceIdSet *remapped_resources,
+ ResourceProvider::ResourceId id) {
+
+ ResourceProvider::ResourceIdMap::const_iterator it =
+ child_to_parent_map.find(id);
+ if (it == child_to_parent_map.end()) {
+ *invalid_frame = true;
+ return 0;
+ }
+
+ DCHECK_EQ(it->first, id);
+ ResourceProvider::ResourceId remapped_id = it->second;
+ remapped_resources->insert(remapped_id);
+ return remapped_id;
+}
+
+void DelegatedRendererLayerImpl::PushPropertiesTo(LayerImpl* layer) {
+ LayerImpl::PushPropertiesTo(layer);
+
+ DelegatedRendererLayerImpl* delegated_layer =
+ static_cast<DelegatedRendererLayerImpl*>(layer);
+
+ // If we have a new child_id to give to the active layer, it should
+ // have already deleted its old child_id.
+ DCHECK(delegated_layer->child_id_ == 0 ||
+ delegated_layer->child_id_ == child_id_);
+ delegated_layer->child_id_ = child_id_;
+ delegated_layer->own_child_id_ = true;
+ own_child_id_ = false;
+
+ delegated_layer->SetDisplaySize(display_size_);
+ if (have_render_passes_to_push_) {
+ // This passes ownership of the render passes to the active tree.
+ delegated_layer->SetRenderPasses(&render_passes_in_draw_order_);
+ DCHECK(render_passes_in_draw_order_.empty());
+ have_render_passes_to_push_ = false;
+ }
+
+ // This is just a copy for testing since we keep the data on the pending layer
+ // for returning resources to the child for now.
+ delegated_layer->resources_ = resources_;
+}
+
+void DelegatedRendererLayerImpl::SetFrameData(
+ scoped_ptr<DelegatedFrameData> frame_data,
+ gfx::RectF damage_in_frame) {
+ DCHECK(frame_data);
+
+ // A frame with an empty root render pass is invalid.
+ DCHECK(frame_data->render_pass_list.empty() ||
+ !frame_data->render_pass_list.back()->output_rect.IsEmpty());
+
+ CreateChildIdIfNeeded();
+ DCHECK(child_id_);
+
+ ResourceProvider* resource_provider = layer_tree_impl()->resource_provider();
+ const ResourceProvider::ResourceIdMap& resource_map =
+ resource_provider->GetChildToParentMap(child_id_);
+
+ resource_provider->ReceiveFromChild(child_id_, frame_data->resource_list);
+
+ bool invalid_frame = false;
+ ResourceProvider::ResourceIdSet used_resources;
+ DrawQuad::ResourceIteratorCallback remap_resources_to_parent_callback =
+ base::Bind(&ResourceRemapHelper,
+ &invalid_frame,
+ resource_map,
+ &used_resources);
+ for (size_t i = 0; i < frame_data->render_pass_list.size(); ++i) {
+ RenderPass* pass = frame_data->render_pass_list[i];
+ for (size_t j = 0; j < pass->quad_list.size(); ++j) {
+ DrawQuad* quad = pass->quad_list[j];
+ quad->IterateResources(remap_resources_to_parent_callback);
+ }
+ }
+
+ if (invalid_frame)
+ return;
+
+ // Display size is already set so we can compute what the damage rect
+ // will be in layer space.
+ if (!frame_data->render_pass_list.empty()) {
+ RenderPass* new_root_pass = frame_data->render_pass_list.back();
+ gfx::RectF damage_in_layer = MathUtil::MapClippedRect(
+ DelegatedFrameToLayerSpaceTransform(
+ new_root_pass->output_rect.size()),
+ damage_in_frame);
+ set_update_rect(gfx::UnionRects(update_rect(), damage_in_layer));
+ }
+
+ // Save the remapped quads on the layer. This steals the quads and render
+ // passes from the frame_data.
+ SetRenderPasses(&frame_data->render_pass_list);
+ resources_.swap(used_resources);
+ have_render_passes_to_push_ = true;
+}
+
+void DelegatedRendererLayerImpl::CollectUnusedResources(
+ TransferableResourceArray* resources_for_ack) {
+ CreateChildIdIfNeeded();
+ DCHECK(child_id_);
+
+ ResourceProvider* resource_provider = layer_tree_impl()->resource_provider();
+ const ResourceProvider::ResourceIdMap& resource_map =
+ resource_provider->GetChildToParentMap(child_id_);
+
+ ResourceProvider::ResourceIdArray unused_resources;
+ for (ResourceProvider::ResourceIdMap::const_iterator it =
+ resource_map.begin();
+ it != resource_map.end();
+ ++it) {
+ bool resource_is_in_current_frame = resources_.count(it->second) > 0;
+ bool resource_is_in_use = resource_provider->InUseByConsumer(it->second);
+ if (!resource_is_in_current_frame && !resource_is_in_use)
+ unused_resources.push_back(it->second);
+ }
+ resource_provider->PrepareSendToChild(
+ child_id_, unused_resources, resources_for_ack);
+}
+
+void DelegatedRendererLayerImpl::SetDisplaySize(gfx::Size size) {
+ if (display_size_ == size)
+ return;
+ display_size_ = size;
+ NoteLayerPropertyChanged();
+}
+
+void DelegatedRendererLayerImpl::SetRenderPasses(
+ ScopedPtrVector<RenderPass>* render_passes_in_draw_order) {
+ gfx::RectF old_root_damage;
+ if (!render_passes_in_draw_order_.empty())
+ old_root_damage = render_passes_in_draw_order_.back()->damage_rect;
+
+ ClearRenderPasses();
+
+ for (size_t i = 0; i < render_passes_in_draw_order->size(); ++i) {
+ ScopedPtrVector<RenderPass>::iterator to_take =
+ render_passes_in_draw_order->begin() + i;
+ render_passes_index_by_id_.insert(
+ std::pair<RenderPass::Id, int>((*to_take)->id, i));
+ scoped_ptr<RenderPass> taken_render_pass =
+ render_passes_in_draw_order->take(to_take);
+ render_passes_in_draw_order_.push_back(taken_render_pass.Pass());
+ }
+
+ if (!render_passes_in_draw_order_.empty())
+ render_passes_in_draw_order_.back()->damage_rect.Union(old_root_damage);
+
+ // Give back an empty array instead of nulls.
+ render_passes_in_draw_order->clear();
+}
+
+void DelegatedRendererLayerImpl::ClearRenderPasses() {
+ // TODO(danakj): Release the resources back to the nested compositor.
+ render_passes_index_by_id_.clear();
+ render_passes_in_draw_order_.clear();
+}
+
+scoped_ptr<LayerImpl> DelegatedRendererLayerImpl::CreateLayerImpl(
+ LayerTreeImpl* tree_impl) {
+ return DelegatedRendererLayerImpl::Create(
+ tree_impl, id()).PassAs<LayerImpl>();
+}
+
+void DelegatedRendererLayerImpl::DidLoseOutputSurface() {
+ ClearRenderPasses();
+ ClearChildId();
+}
+
+gfx::Transform DelegatedRendererLayerImpl::DelegatedFrameToLayerSpaceTransform(
+ gfx::Size frame_size) const {
+ gfx::Size display_size = display_size_.IsEmpty() ? bounds() : display_size_;
+
+ gfx::Transform delegated_frame_to_layer_space_transform;
+ delegated_frame_to_layer_space_transform.Scale(
+ static_cast<double>(display_size.width()) / frame_size.width(),
+ static_cast<double>(display_size.height()) / frame_size.height());
+ return delegated_frame_to_layer_space_transform;
+}
+
+static inline int IndexToId(int index) { return index + 1; }
+static inline int IdToIndex(int id) { return id - 1; }
+
+RenderPass::Id DelegatedRendererLayerImpl::FirstContributingRenderPassId()
+ const {
+ return RenderPass::Id(id(), IndexToId(0));
+}
+
+RenderPass::Id DelegatedRendererLayerImpl::NextContributingRenderPassId(
+ RenderPass::Id previous) const {
+ return RenderPass::Id(previous.layer_id, previous.index + 1);
+}
+
+RenderPass::Id DelegatedRendererLayerImpl::ConvertDelegatedRenderPassId(
+ RenderPass::Id delegated_render_pass_id) const {
+ base::hash_map<RenderPass::Id, int>::const_iterator found =
+ render_passes_index_by_id_.find(delegated_render_pass_id);
+ DCHECK(found != render_passes_index_by_id_.end());
+ unsigned delegated_render_pass_index = found->second;
+ return RenderPass::Id(id(), IndexToId(delegated_render_pass_index));
+}
+
+void DelegatedRendererLayerImpl::AppendContributingRenderPasses(
+ RenderPassSink* render_pass_sink) {
+ DCHECK(HasContributingDelegatedRenderPasses());
+
+ for (size_t i = 0; i < render_passes_in_draw_order_.size() - 1; ++i) {
+ RenderPass::Id output_render_pass_id =
+ ConvertDelegatedRenderPassId(render_passes_in_draw_order_[i]->id);
+
+ // Don't clash with the RenderPass we generate if we own a RenderSurface.
+ DCHECK_GT(output_render_pass_id.index, 0);
+
+ render_pass_sink->AppendRenderPass(
+ render_passes_in_draw_order_[i]->Copy(output_render_pass_id));
+ }
+}
+
+bool DelegatedRendererLayerImpl::WillDraw(DrawMode draw_mode,
+ ResourceProvider* resource_provider) {
+ if (draw_mode == DRAW_MODE_RESOURCELESS_SOFTWARE)
+ return false;
+ return LayerImpl::WillDraw(draw_mode, resource_provider);
+}
+
+void DelegatedRendererLayerImpl::AppendQuads(
+ QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data) {
+ AppendRainbowDebugBorder(quad_sink, append_quads_data);
+
+ if (render_passes_in_draw_order_.empty())
+ return;
+
+ RenderPass::Id target_render_pass_id = append_quads_data->render_pass_id;
+
+ const RenderPass* root_delegated_render_pass =
+ render_passes_in_draw_order_.back();
+
+ DCHECK(root_delegated_render_pass->output_rect.origin().IsOrigin());
+ gfx::Size frame_size = root_delegated_render_pass->output_rect.size();
+
+ // If the index of the EenderPassId is 0, then it is a RenderPass generated
+ // for a layer in this compositor, not the delegated renderer. Then we want to
+ // merge our root RenderPass with the target RenderPass. Otherwise, it is some
+ // RenderPass which we added from the delegated renderer.
+ bool should_merge_root_render_pass_with_target = !target_render_pass_id.index;
+ if (should_merge_root_render_pass_with_target) {
+ // Verify that the RenderPass we are appending to is created our
+ // render_target.
+ DCHECK(target_render_pass_id.layer_id == render_target()->id());
+
+ AppendRenderPassQuads(
+ quad_sink, append_quads_data, root_delegated_render_pass, frame_size);
+ } else {
+ // Verify that the RenderPass we are appending to was created by us.
+ DCHECK(target_render_pass_id.layer_id == id());
+
+ int render_pass_index = IdToIndex(target_render_pass_id.index);
+ const RenderPass* delegated_render_pass =
+ render_passes_in_draw_order_[render_pass_index];
+ AppendRenderPassQuads(
+ quad_sink, append_quads_data, delegated_render_pass, frame_size);
+ }
+}
+
+void DelegatedRendererLayerImpl::AppendRainbowDebugBorder(
+ QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data) {
+ if (!ShowDebugBorders())
+ return;
+
+ SharedQuadState* shared_quad_state =
+ quad_sink->UseSharedQuadState(CreateSharedQuadState());
+
+ SkColor color;
+ float border_width;
+ GetDebugBorderProperties(&color, &border_width);
+
+ SkColor colors[] = {
+ 0x80ff0000, // Red.
+ 0x80ffa500, // Orange.
+ 0x80ffff00, // Yellow.
+ 0x80008000, // Green.
+ 0x800000ff, // Blue.
+ 0x80ee82ee, // Violet.
+ };
+ const int kNumColors = arraysize(colors);
+
+ const int kStripeWidth = 300;
+ const int kStripeHeight = 300;
+
+ for (size_t i = 0; ; ++i) {
+ // For horizontal lines.
+ int x = kStripeWidth * i;
+ int width = std::min(kStripeWidth, content_bounds().width() - x - 1);
+
+ // For vertical lines.
+ int y = kStripeHeight * i;
+ int height = std::min(kStripeHeight, content_bounds().height() - y - 1);
+
+ gfx::Rect top(x, 0, width, border_width);
+ gfx::Rect bottom(x,
+ content_bounds().height() - border_width,
+ width,
+ border_width);
+ gfx::Rect left(0, y, border_width, height);
+ gfx::Rect right(content_bounds().width() - border_width,
+ y,
+ border_width,
+ height);
+
+ if (top.IsEmpty() && left.IsEmpty())
+ break;
+
+ if (!top.IsEmpty()) {
+ scoped_ptr<SolidColorDrawQuad> top_quad = SolidColorDrawQuad::Create();
+ top_quad->SetNew(shared_quad_state, top, colors[i % kNumColors], false);
+ quad_sink->Append(top_quad.PassAs<DrawQuad>(), append_quads_data);
+
+ scoped_ptr<SolidColorDrawQuad> bottom_quad = SolidColorDrawQuad::Create();
+ bottom_quad->SetNew(shared_quad_state,
+ bottom,
+ colors[kNumColors - 1 - (i % kNumColors)],
+ false);
+ quad_sink->Append(bottom_quad.PassAs<DrawQuad>(), append_quads_data);
+ }
+ if (!left.IsEmpty()) {
+ scoped_ptr<SolidColorDrawQuad> left_quad = SolidColorDrawQuad::Create();
+ left_quad->SetNew(shared_quad_state,
+ left,
+ colors[kNumColors - 1 - (i % kNumColors)],
+ false);
+ quad_sink->Append(left_quad.PassAs<DrawQuad>(), append_quads_data);
+
+ scoped_ptr<SolidColorDrawQuad> right_quad = SolidColorDrawQuad::Create();
+ right_quad->SetNew(
+ shared_quad_state, right, colors[i % kNumColors], false);
+ quad_sink->Append(right_quad.PassAs<DrawQuad>(), append_quads_data);
+ }
+ }
+}
+
+void DelegatedRendererLayerImpl::AppendRenderPassQuads(
+ QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data,
+ const RenderPass* delegated_render_pass,
+ gfx::Size frame_size) const {
+
+ const SharedQuadState* delegated_shared_quad_state = NULL;
+ SharedQuadState* output_shared_quad_state = NULL;
+
+ for (size_t i = 0; i < delegated_render_pass->quad_list.size(); ++i) {
+ const DrawQuad* delegated_quad = delegated_render_pass->quad_list[i];
+
+ if (delegated_quad->shared_quad_state != delegated_shared_quad_state) {
+ delegated_shared_quad_state = delegated_quad->shared_quad_state;
+ output_shared_quad_state = quad_sink->UseSharedQuadState(
+ delegated_shared_quad_state->Copy());
+
+ bool is_root_delegated_render_pass =
+ delegated_render_pass == render_passes_in_draw_order_.back();
+ if (is_root_delegated_render_pass) {
+ // Don't allow areas inside the bounds that are empty.
+ DCHECK(display_size_.IsEmpty() ||
+ gfx::Rect(display_size_).Contains(gfx::Rect(bounds())));
+ gfx::Transform delegated_frame_to_target_transform =
+ draw_transform() * DelegatedFrameToLayerSpaceTransform(frame_size);
+
+ output_shared_quad_state->content_to_target_transform.ConcatTransform(
+ delegated_frame_to_target_transform);
+
+ if (render_target() == this) {
+ DCHECK(!is_clipped());
+ DCHECK(render_surface());
+ output_shared_quad_state->clip_rect = MathUtil::MapClippedRect(
+ delegated_frame_to_target_transform,
+ output_shared_quad_state->clip_rect);
+ } else {
+ gfx::Rect clip_rect = drawable_content_rect();
+ if (output_shared_quad_state->is_clipped) {
+ clip_rect.Intersect(MathUtil::MapClippedRect(
+ delegated_frame_to_target_transform,
+ output_shared_quad_state->clip_rect));
+ }
+ output_shared_quad_state->clip_rect = clip_rect;
+ output_shared_quad_state->is_clipped = true;
+ }
+
+ output_shared_quad_state->opacity *= draw_opacity();
+ }
+ }
+ DCHECK(output_shared_quad_state);
+
+ scoped_ptr<DrawQuad> output_quad;
+ if (delegated_quad->material != DrawQuad::RENDER_PASS) {
+ output_quad = delegated_quad->Copy(output_shared_quad_state);
+ } else {
+ RenderPass::Id delegated_contributing_render_pass_id =
+ RenderPassDrawQuad::MaterialCast(delegated_quad)->render_pass_id;
+ RenderPass::Id output_contributing_render_pass_id =
+ ConvertDelegatedRenderPassId(delegated_contributing_render_pass_id);
+ DCHECK(output_contributing_render_pass_id !=
+ append_quads_data->render_pass_id);
+
+ output_quad = RenderPassDrawQuad::MaterialCast(delegated_quad)->Copy(
+ output_shared_quad_state,
+ output_contributing_render_pass_id).PassAs<DrawQuad>();
+ }
+ DCHECK(output_quad.get());
+
+ quad_sink->Append(output_quad.Pass(), append_quads_data);
+ }
+}
+
+const char* DelegatedRendererLayerImpl::LayerTypeAsString() const {
+ return "cc::DelegatedRendererLayerImpl";
+}
+
+void DelegatedRendererLayerImpl::CreateChildIdIfNeeded() {
+ if (child_id_)
+ return;
+
+ ResourceProvider* resource_provider = layer_tree_impl()->resource_provider();
+ child_id_ = resource_provider->CreateChild();
+ own_child_id_ = true;
+}
+
+void DelegatedRendererLayerImpl::ClearChildId() {
+ if (!child_id_)
+ return;
+
+ if (own_child_id_) {
+ ResourceProvider* provider = layer_tree_impl()->resource_provider();
+ provider->DestroyChild(child_id_);
+ }
+
+ child_id_ = 0;
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/delegated_renderer_layer_impl.h b/chromium/cc/layers/delegated_renderer_layer_impl.h
new file mode 100644
index 00000000000..9cfdcb6a351
--- /dev/null
+++ b/chromium/cc/layers/delegated_renderer_layer_impl.h
@@ -0,0 +1,103 @@
+// 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.
+
+#ifndef CC_LAYERS_DELEGATED_RENDERER_LAYER_IMPL_H_
+#define CC_LAYERS_DELEGATED_RENDERER_LAYER_IMPL_H_
+
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/base/scoped_ptr_vector.h"
+#include "cc/layers/layer_impl.h"
+
+namespace cc {
+class DelegatedFrameData;
+class RenderPassSink;
+
+class CC_EXPORT DelegatedRendererLayerImpl : public LayerImpl {
+ public:
+ static scoped_ptr<DelegatedRendererLayerImpl> Create(
+ LayerTreeImpl* tree_impl, int id) {
+ return make_scoped_ptr(new DelegatedRendererLayerImpl(tree_impl, id));
+ }
+ virtual ~DelegatedRendererLayerImpl();
+
+ // LayerImpl overrides.
+ virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl)
+ OVERRIDE;
+ virtual bool HasDelegatedContent() const OVERRIDE;
+ virtual bool HasContributingDelegatedRenderPasses() const OVERRIDE;
+ virtual RenderPass::Id FirstContributingRenderPassId() const OVERRIDE;
+ virtual RenderPass::Id NextContributingRenderPassId(
+ RenderPass::Id previous) const OVERRIDE;
+ virtual void DidLoseOutputSurface() OVERRIDE;
+ virtual bool WillDraw(DrawMode draw_mode,
+ ResourceProvider* resource_provider) OVERRIDE;
+ virtual void AppendQuads(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data) OVERRIDE;
+ virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE;
+
+ void AppendContributingRenderPasses(RenderPassSink* render_pass_sink);
+
+ void SetFrameData(scoped_ptr<DelegatedFrameData> frame_data,
+ gfx::RectF damage_in_frame);
+
+ void CollectUnusedResources(TransferableResourceArray* resources_for_ack);
+
+ void SetDisplaySize(gfx::Size size);
+
+ protected:
+ DelegatedRendererLayerImpl(LayerTreeImpl* tree_impl, int id);
+
+ int ChildIdForTesting() const { return child_id_; }
+ const ScopedPtrVector<RenderPass>& RenderPassesInDrawOrderForTesting() const {
+ return render_passes_in_draw_order_;
+ }
+ const ResourceProvider::ResourceIdSet& ResourcesForTesting() const {
+ return resources_;
+ }
+
+ private:
+ // Creates an ID with the resource provider for the child renderer
+ // that will be sending quads to the layer.
+ void CreateChildIdIfNeeded();
+ void ClearChildId();
+
+ void AppendRainbowDebugBorder(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data);
+
+ void SetRenderPasses(
+ ScopedPtrVector<RenderPass>* render_passes_in_draw_order);
+ void ClearRenderPasses();
+
+ RenderPass::Id ConvertDelegatedRenderPassId(
+ RenderPass::Id delegated_render_pass_id) const;
+
+ gfx::Transform DelegatedFrameToLayerSpaceTransform(gfx::Size frame_size)
+ const;
+
+ void AppendRenderPassQuads(
+ QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data,
+ const RenderPass* delegated_render_pass,
+ gfx::Size frame_size) const;
+
+ // LayerImpl overrides.
+ virtual const char* LayerTypeAsString() const OVERRIDE;
+
+ bool have_render_passes_to_push_;
+ ScopedPtrVector<RenderPass> render_passes_in_draw_order_;
+ base::hash_map<RenderPass::Id, int> render_passes_index_by_id_;
+ ResourceProvider::ResourceIdSet resources_;
+
+ gfx::Size display_size_;
+ int child_id_;
+ bool own_child_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(DelegatedRendererLayerImpl);
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_DELEGATED_RENDERER_LAYER_IMPL_H_
diff --git a/chromium/cc/layers/delegated_renderer_layer_impl_unittest.cc b/chromium/cc/layers/delegated_renderer_layer_impl_unittest.cc
new file mode 100644
index 00000000000..2537c53053b
--- /dev/null
+++ b/chromium/cc/layers/delegated_renderer_layer_impl_unittest.cc
@@ -0,0 +1,1254 @@
+// 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 "cc/layers/delegated_renderer_layer_impl.h"
+
+#include "cc/base/scoped_ptr_vector.h"
+#include "cc/debug/test_web_graphics_context_3d.h"
+#include "cc/layers/append_quads_data.h"
+#include "cc/layers/quad_sink.h"
+#include "cc/layers/solid_color_layer_impl.h"
+#include "cc/quads/render_pass_draw_quad.h"
+#include "cc/quads/solid_color_draw_quad.h"
+#include "cc/test/fake_delegated_renderer_layer_impl.h"
+#include "cc/test/fake_layer_tree_host_impl.h"
+#include "cc/test/fake_layer_tree_host_impl_client.h"
+#include "cc/test/fake_output_surface.h"
+#include "cc/test/fake_proxy.h"
+#include "cc/test/fake_rendering_stats_instrumentation.h"
+#include "cc/test/geometry_test_utils.h"
+#include "cc/test/mock_quad_culler.h"
+#include "cc/test/render_pass_test_common.h"
+#include "cc/test/render_pass_test_utils.h"
+#include "cc/trees/layer_tree_host_impl.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "cc/trees/single_thread_proxy.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+namespace {
+
+class DelegatedRendererLayerImplTest : public testing::Test {
+ public:
+ DelegatedRendererLayerImplTest()
+ : proxy_(),
+ always_impl_thread_and_main_thread_blocked_(&proxy_) {
+ LayerTreeSettings settings;
+ settings.minimum_occlusion_tracking_size = gfx::Size();
+
+ host_impl_ = LayerTreeHostImpl::Create(settings,
+ &client_,
+ &proxy_,
+ &stats_instrumentation_);
+ host_impl_->InitializeRenderer(CreateFakeOutputSurface());
+ host_impl_->SetViewportSize(gfx::Size(10, 10));
+ }
+
+ protected:
+ FakeProxy proxy_;
+ FakeLayerTreeHostImplClient client_;
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ always_impl_thread_and_main_thread_blocked_;
+ FakeRenderingStatsInstrumentation stats_instrumentation_;
+ scoped_ptr<LayerTreeHostImpl> host_impl_;
+};
+
+class DelegatedRendererLayerImplTestSimple
+ : public DelegatedRendererLayerImplTest {
+ public:
+ DelegatedRendererLayerImplTestSimple()
+ : DelegatedRendererLayerImplTest() {
+ scoped_ptr<LayerImpl> root_layer = SolidColorLayerImpl::Create(
+ host_impl_->active_tree(), 1).PassAs<LayerImpl>();
+ scoped_ptr<LayerImpl> layer_before = SolidColorLayerImpl::Create(
+ host_impl_->active_tree(), 2).PassAs<LayerImpl>();
+ scoped_ptr<LayerImpl> layer_after = SolidColorLayerImpl::Create(
+ host_impl_->active_tree(), 3).PassAs<LayerImpl>();
+ scoped_ptr<FakeDelegatedRendererLayerImpl> delegated_renderer_layer =
+ FakeDelegatedRendererLayerImpl::Create(host_impl_->active_tree(), 4);
+
+ host_impl_->SetViewportSize(gfx::Size(100, 100));
+ root_layer->SetBounds(gfx::Size(100, 100));
+
+ layer_before->SetPosition(gfx::Point(20, 20));
+ layer_before->SetBounds(gfx::Size(14, 14));
+ layer_before->SetContentBounds(gfx::Size(14, 14));
+ layer_before->SetDrawsContent(true);
+ layer_before->SetForceRenderSurface(true);
+
+ layer_after->SetPosition(gfx::Point(5, 5));
+ layer_after->SetBounds(gfx::Size(15, 15));
+ layer_after->SetContentBounds(gfx::Size(15, 15));
+ layer_after->SetDrawsContent(true);
+ layer_after->SetForceRenderSurface(true);
+
+ delegated_renderer_layer->SetPosition(gfx::Point(3, 3));
+ delegated_renderer_layer->SetBounds(gfx::Size(10, 10));
+ delegated_renderer_layer->SetContentBounds(gfx::Size(10, 10));
+ delegated_renderer_layer->SetDrawsContent(true);
+ gfx::Transform transform;
+ transform.Translate(1.0, 1.0);
+ delegated_renderer_layer->SetTransform(transform);
+
+ ScopedPtrVector<RenderPass> delegated_render_passes;
+ TestRenderPass* pass1 = AddRenderPass(
+ &delegated_render_passes,
+ RenderPass::Id(9, 6),
+ gfx::Rect(6, 6, 6, 6),
+ gfx::Transform());
+ AddQuad(pass1, gfx::Rect(0, 0, 6, 6), 33u);
+ TestRenderPass* pass2 = AddRenderPass(
+ &delegated_render_passes,
+ RenderPass::Id(9, 7),
+ gfx::Rect(7, 7, 7, 7),
+ gfx::Transform());
+ AddQuad(pass2, gfx::Rect(0, 0, 7, 7), 22u);
+ AddRenderPassQuad(pass2, pass1);
+ TestRenderPass* pass3 = AddRenderPass(
+ &delegated_render_passes,
+ RenderPass::Id(9, 8),
+ gfx::Rect(0, 0, 8, 8),
+ gfx::Transform());
+ AddRenderPassQuad(pass3, pass2);
+ delegated_renderer_layer->SetFrameDataForRenderPasses(
+ &delegated_render_passes);
+
+ // The RenderPasses should be taken by the layer.
+ EXPECT_EQ(0u, delegated_render_passes.size());
+
+ root_layer_ = root_layer.get();
+ layer_before_ = layer_before.get();
+ layer_after_ = layer_after.get();
+ delegated_renderer_layer_ = delegated_renderer_layer.get();
+
+ // Force the delegated RenderPasses to come before the RenderPass from
+ // layer_after.
+ layer_after->AddChild(delegated_renderer_layer.PassAs<LayerImpl>());
+ root_layer->AddChild(layer_after.Pass());
+
+ // Get the RenderPass generated by layer_before to come before the delegated
+ // RenderPasses.
+ root_layer->AddChild(layer_before.Pass());
+ host_impl_->active_tree()->SetRootLayer(root_layer.Pass());
+ }
+
+ protected:
+ LayerImpl* root_layer_;
+ LayerImpl* layer_before_;
+ LayerImpl* layer_after_;
+ DelegatedRendererLayerImpl* delegated_renderer_layer_;
+};
+
+TEST_F(DelegatedRendererLayerImplTestSimple, AddsContributingRenderPasses) {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Each non-DelegatedRendererLayer added one RenderPass. The
+ // DelegatedRendererLayer added two contributing passes.
+ ASSERT_EQ(5u, frame.render_passes.size());
+
+ // The DelegatedRendererLayer should have added its contributing RenderPasses
+ // to the frame.
+ EXPECT_EQ(4, frame.render_passes[1]->id.layer_id);
+ EXPECT_EQ(1, frame.render_passes[1]->id.index);
+ EXPECT_EQ(4, frame.render_passes[2]->id.layer_id);
+ EXPECT_EQ(2, frame.render_passes[2]->id.index);
+ // And all other RenderPasses should be non-delegated.
+ EXPECT_NE(4, frame.render_passes[0]->id.layer_id);
+ EXPECT_EQ(0, frame.render_passes[0]->id.index);
+ EXPECT_NE(4, frame.render_passes[3]->id.layer_id);
+ EXPECT_EQ(0, frame.render_passes[3]->id.index);
+ EXPECT_NE(4, frame.render_passes[4]->id.layer_id);
+ EXPECT_EQ(0, frame.render_passes[4]->id.index);
+
+ // The DelegatedRendererLayer should have added its RenderPasses to the frame
+ // in order.
+ EXPECT_EQ(gfx::Rect(6, 6, 6, 6).ToString(),
+ frame.render_passes[1]->output_rect.ToString());
+ EXPECT_EQ(gfx::Rect(7, 7, 7, 7).ToString(),
+ frame.render_passes[2]->output_rect.ToString());
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+}
+
+TEST_F(DelegatedRendererLayerImplTestSimple,
+ AddsQuadsToContributingRenderPasses) {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Each non-DelegatedRendererLayer added one RenderPass. The
+ // DelegatedRendererLayer added two contributing passes.
+ ASSERT_EQ(5u, frame.render_passes.size());
+
+ // The DelegatedRendererLayer should have added its contributing RenderPasses
+ // to the frame.
+ EXPECT_EQ(4, frame.render_passes[1]->id.layer_id);
+ EXPECT_EQ(1, frame.render_passes[1]->id.index);
+ EXPECT_EQ(4, frame.render_passes[2]->id.layer_id);
+ EXPECT_EQ(2, frame.render_passes[2]->id.index);
+
+ // The DelegatedRendererLayer should have added copies of its quads to
+ // contributing RenderPasses.
+ ASSERT_EQ(1u, frame.render_passes[1]->quad_list.size());
+ EXPECT_EQ(gfx::Rect(0, 0, 6, 6).ToString(),
+ frame.render_passes[1]->quad_list[0]->rect.ToString());
+
+ // Verify it added the right quads.
+ ASSERT_EQ(2u, frame.render_passes[2]->quad_list.size());
+ EXPECT_EQ(gfx::Rect(0, 0, 7, 7).ToString(),
+ frame.render_passes[2]->quad_list[0]->rect.ToString());
+ EXPECT_EQ(gfx::Rect(6, 6, 6, 6).ToString(),
+ frame.render_passes[2]->quad_list[1]->rect.ToString());
+ ASSERT_EQ(1u, frame.render_passes[1]->quad_list.size());
+ EXPECT_EQ(gfx::Rect(0, 0, 6, 6).ToString(),
+ frame.render_passes[1]->quad_list[0]->rect.ToString());
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+}
+
+TEST_F(DelegatedRendererLayerImplTestSimple, AddsQuadsToTargetRenderPass) {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Each non-DelegatedRendererLayer added one RenderPass. The
+ // DelegatedRendererLayer added two contributing passes.
+ ASSERT_EQ(5u, frame.render_passes.size());
+
+ // The layer's target is the RenderPass from layer_after_.
+ EXPECT_EQ(RenderPass::Id(3, 0), frame.render_passes[3]->id);
+
+ // The DelegatedRendererLayer should have added copies of quads in its root
+ // RenderPass to its target RenderPass. The layer_after_ also adds one quad.
+ ASSERT_EQ(2u, frame.render_passes[3]->quad_list.size());
+
+ // Verify it added the right quads.
+ EXPECT_EQ(gfx::Rect(7, 7, 7, 7).ToString(),
+ frame.render_passes[3]->quad_list[0]->rect.ToString());
+
+ // Its target layer should have a quad as well.
+ EXPECT_EQ(gfx::Rect(0, 0, 15, 15).ToString(),
+ frame.render_passes[3]->quad_list[1]->rect.ToString());
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+}
+
+TEST_F(DelegatedRendererLayerImplTestSimple,
+ QuadsFromRootRenderPassAreModifiedForTheTarget) {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Each non-DelegatedRendererLayer added one RenderPass. The
+ // DelegatedRendererLayer added two contributing passes.
+ ASSERT_EQ(5u, frame.render_passes.size());
+
+ // The DelegatedRendererLayer is at position 3,3 compared to its target, and
+ // has a translation transform of 1,1. So its root RenderPass' quads should
+ // all be transformed by that combined amount.
+ // The DelegatedRendererLayer has a size of 10x10, but the root delegated
+ // RenderPass has a size of 8x8, so any quads should be scaled by 10/8.
+ gfx::Transform transform;
+ transform.Translate(4.0, 4.0);
+ transform.Scale(10.0 / 8.0, 10.0 / 8.0);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ transform, frame.render_passes[3]->quad_list[0]->quadTransform());
+
+ // Quads from non-root RenderPasses should not be shifted though.
+ ASSERT_EQ(2u, frame.render_passes[2]->quad_list.size());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ gfx::Transform(), frame.render_passes[2]->quad_list[0]->quadTransform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ gfx::Transform(), frame.render_passes[2]->quad_list[1]->quadTransform());
+ ASSERT_EQ(1u, frame.render_passes[1]->quad_list.size());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ gfx::Transform(), frame.render_passes[1]->quad_list[0]->quadTransform());
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+}
+
+TEST_F(DelegatedRendererLayerImplTestSimple, DoesNotOwnARenderSurface) {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ // If the DelegatedRendererLayer is axis aligned and has opacity 1, then it
+ // has no need to be a RenderSurface for the quads it carries.
+ EXPECT_FALSE(delegated_renderer_layer_->render_surface());
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+}
+
+TEST_F(DelegatedRendererLayerImplTestSimple, DoesOwnARenderSurfaceForOpacity) {
+ delegated_renderer_layer_->SetOpacity(0.5f);
+
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ // This test case has quads from multiple layers in the delegated renderer, so
+ // if the DelegatedRendererLayer has opacity < 1, it should end up with a
+ // render surface.
+ EXPECT_TRUE(delegated_renderer_layer_->render_surface());
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+}
+
+TEST_F(DelegatedRendererLayerImplTestSimple,
+ DoesOwnARenderSurfaceForTransform) {
+ gfx::Transform rotation;
+ rotation.RotateAboutZAxis(30.0);
+ delegated_renderer_layer_->SetTransform(rotation);
+
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ // This test case has quads from multiple layers in the delegated renderer, so
+ // if the DelegatedRendererLayer has opacity < 1, it should end up with a
+ // render surface.
+ EXPECT_TRUE(delegated_renderer_layer_->render_surface());
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+}
+
+class DelegatedRendererLayerImplTestOwnSurface
+ : public DelegatedRendererLayerImplTestSimple {
+ public:
+ DelegatedRendererLayerImplTestOwnSurface()
+ : DelegatedRendererLayerImplTestSimple() {
+ delegated_renderer_layer_->SetForceRenderSurface(true);
+ }
+};
+
+TEST_F(DelegatedRendererLayerImplTestOwnSurface, AddsRenderPasses) {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Each non-DelegatedRendererLayer added one RenderPass. The
+ // DelegatedRendererLayer added two contributing passes and its owned surface
+ // added one pass.
+ ASSERT_EQ(6u, frame.render_passes.size());
+
+ // The DelegatedRendererLayer should have added its contributing RenderPasses
+ // to the frame.
+ EXPECT_EQ(4, frame.render_passes[1]->id.layer_id);
+ EXPECT_EQ(1, frame.render_passes[1]->id.index);
+ EXPECT_EQ(4, frame.render_passes[2]->id.layer_id);
+ EXPECT_EQ(2, frame.render_passes[2]->id.index);
+ // The DelegatedRendererLayer should have added a RenderPass for its surface
+ // to the frame.
+ EXPECT_EQ(4, frame.render_passes[1]->id.layer_id);
+ EXPECT_EQ(0, frame.render_passes[3]->id.index);
+ // And all other RenderPasses should be non-delegated.
+ EXPECT_NE(4, frame.render_passes[0]->id.layer_id);
+ EXPECT_EQ(0, frame.render_passes[0]->id.index);
+ EXPECT_NE(4, frame.render_passes[4]->id.layer_id);
+ EXPECT_EQ(0, frame.render_passes[4]->id.index);
+ EXPECT_NE(4, frame.render_passes[5]->id.layer_id);
+ EXPECT_EQ(0, frame.render_passes[5]->id.index);
+
+ // The DelegatedRendererLayer should have added its RenderPasses to the frame
+ // in order.
+ EXPECT_EQ(gfx::Rect(6, 6, 6, 6).ToString(),
+ frame.render_passes[1]->output_rect.ToString());
+ EXPECT_EQ(gfx::Rect(7, 7, 7, 7).ToString(),
+ frame.render_passes[2]->output_rect.ToString());
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+}
+
+TEST_F(DelegatedRendererLayerImplTestOwnSurface,
+ AddsQuadsToContributingRenderPasses) {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Each non-DelegatedRendererLayer added one RenderPass. The
+ // DelegatedRendererLayer added two contributing passes and its owned surface
+ // added one pass.
+ ASSERT_EQ(6u, frame.render_passes.size());
+
+ // The DelegatedRendererLayer should have added its contributing RenderPasses
+ // to the frame.
+ EXPECT_EQ(4, frame.render_passes[1]->id.layer_id);
+ EXPECT_EQ(1, frame.render_passes[1]->id.index);
+ EXPECT_EQ(4, frame.render_passes[2]->id.layer_id);
+ EXPECT_EQ(2, frame.render_passes[2]->id.index);
+
+ // The DelegatedRendererLayer should have added copies of its quads to
+ // contributing RenderPasses.
+ ASSERT_EQ(1u, frame.render_passes[1]->quad_list.size());
+ EXPECT_EQ(gfx::Rect(0, 0, 6, 6).ToString(),
+ frame.render_passes[1]->quad_list[0]->rect.ToString());
+
+ // Verify it added the right quads.
+ ASSERT_EQ(2u, frame.render_passes[2]->quad_list.size());
+ EXPECT_EQ(gfx::Rect(0, 0, 7, 7).ToString(),
+ frame.render_passes[2]->quad_list[0]->rect.ToString());
+ EXPECT_EQ(gfx::Rect(6, 6, 6, 6).ToString(),
+ frame.render_passes[2]->quad_list[1]->rect.ToString());
+ ASSERT_EQ(1u, frame.render_passes[1]->quad_list.size());
+ EXPECT_EQ(gfx::Rect(0, 0, 6, 6).ToString(),
+ frame.render_passes[1]->quad_list[0]->rect.ToString());
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+}
+
+TEST_F(DelegatedRendererLayerImplTestOwnSurface, AddsQuadsToTargetRenderPass) {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Each non-DelegatedRendererLayer added one RenderPass. The
+ // DelegatedRendererLayer added two contributing passes and its owned surface
+ // added one pass.
+ ASSERT_EQ(6u, frame.render_passes.size());
+
+ // The layer's target is the RenderPass owned by itself.
+ EXPECT_EQ(RenderPass::Id(4, 0), frame.render_passes[3]->id);
+
+ // The DelegatedRendererLayer should have added copies of quads in its root
+ // RenderPass to its target RenderPass.
+ // The layer_after also adds one quad.
+ ASSERT_EQ(1u, frame.render_passes[3]->quad_list.size());
+
+ // Verify it added the right quads.
+ EXPECT_EQ(gfx::Rect(7, 7, 7, 7).ToString(),
+ frame.render_passes[3]->quad_list[0]->rect.ToString());
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+}
+
+TEST_F(DelegatedRendererLayerImplTestOwnSurface,
+ QuadsFromRootRenderPassAreNotModifiedForTheTarget) {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Each non-DelegatedRendererLayer added one RenderPass. The
+ // DelegatedRendererLayer added two contributing passes and its owned surface
+ // added one pass.
+ ASSERT_EQ(6u, frame.render_passes.size());
+
+ // Because the DelegatedRendererLayer owns a RenderSurfaceImpl, its root
+ // RenderPass' quads do not need to be translated at all. However, they are
+ // scaled from the frame's size (8x8) to the layer's bounds (10x10).
+ gfx::Transform transform;
+ transform.Scale(10.0 / 8.0, 10.0 / 8.0);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ transform, frame.render_passes[3]->quad_list[0]->quadTransform());
+
+ // Quads from non-root RenderPasses should not be shifted either.
+ ASSERT_EQ(2u, frame.render_passes[2]->quad_list.size());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ gfx::Transform(), frame.render_passes[2]->quad_list[0]->quadTransform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ gfx::Transform(), frame.render_passes[2]->quad_list[1]->quadTransform());
+ ASSERT_EQ(1u, frame.render_passes[1]->quad_list.size());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ gfx::Transform(), frame.render_passes[1]->quad_list[0]->quadTransform());
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+}
+
+class DelegatedRendererLayerImplTestTransform
+ : public DelegatedRendererLayerImplTest {
+ public:
+ void SetUpTest() {
+ host_impl_->SetDeviceScaleFactor(2.f);
+
+ scoped_ptr<LayerImpl> root_layer = LayerImpl::Create(
+ host_impl_->active_tree(), 1);
+ scoped_ptr<FakeDelegatedRendererLayerImpl> delegated_renderer_layer =
+ FakeDelegatedRendererLayerImpl::Create(host_impl_->active_tree(), 2);
+
+ host_impl_->SetViewportSize(gfx::Size(200, 200));
+ root_layer->SetBounds(gfx::Size(100, 100));
+
+ delegated_renderer_layer->SetPosition(gfx::Point(20, 20));
+ delegated_renderer_layer->SetBounds(gfx::Size(30, 30));
+ delegated_renderer_layer->SetContentBounds(gfx::Size(30, 30));
+ delegated_renderer_layer->SetDrawsContent(true);
+ gfx::Transform transform;
+ transform.Scale(2.0, 2.0);
+ transform.Translate(8.0, 8.0);
+ delegated_renderer_layer->SetTransform(transform);
+
+ ScopedPtrVector<RenderPass> delegated_render_passes;
+
+ gfx::Size child_pass_content_bounds(7, 7);
+ gfx::Rect child_pass_rect(20, 20, 7, 7);
+ gfx::Transform child_pass_transform;
+ child_pass_transform.Scale(0.8, 0.8);
+ child_pass_transform.Translate(9.0, 9.0);
+ gfx::Rect child_pass_clip_rect(21, 21, 3, 3);
+ bool child_pass_clipped = false;
+
+ {
+ TestRenderPass* pass = AddRenderPass(
+ &delegated_render_passes,
+ RenderPass::Id(10, 7),
+ child_pass_rect,
+ gfx::Transform());
+ MockQuadCuller quad_sink(&pass->quad_list, &pass->shared_quad_state_list);
+ AppendQuadsData data(pass->id);
+ SharedQuadState* shared_quad_state = quad_sink.UseSharedQuadState(
+ SharedQuadState::Create());
+ shared_quad_state->SetAll(
+ child_pass_transform,
+ child_pass_content_bounds,
+ child_pass_rect,
+ child_pass_clip_rect,
+ child_pass_clipped,
+ 1.f);
+
+ scoped_ptr<SolidColorDrawQuad> color_quad;
+ color_quad = SolidColorDrawQuad::Create();
+ color_quad->SetNew(shared_quad_state, gfx::Rect(20, 20, 3, 7), 1u, false);
+ quad_sink.Append(color_quad.PassAs<DrawQuad>(), &data);
+
+ color_quad = SolidColorDrawQuad::Create();
+ color_quad->SetNew(shared_quad_state, gfx::Rect(23, 20, 4, 7), 1u, false);
+ quad_sink.Append(color_quad.PassAs<DrawQuad>(), &data);
+ }
+
+ gfx::Size root_pass_content_bounds(50, 50);
+ gfx::Rect root_pass_rect(0, 0, 50, 50);
+ gfx::Transform root_pass_transform;
+ root_pass_transform.Scale(1.5, 1.5);
+ root_pass_transform.Translate(7.0, 7.0);
+ gfx::Rect root_pass_clip_rect(10, 10, 35, 35);
+ bool root_pass_clipped = root_delegated_render_pass_is_clipped_;
+
+ TestRenderPass* pass = AddRenderPass(
+ &delegated_render_passes,
+ RenderPass::Id(9, 6),
+ root_pass_rect,
+ gfx::Transform());
+ MockQuadCuller quad_sink(&pass->quad_list, &pass->shared_quad_state_list);
+ AppendQuadsData data(pass->id);
+ SharedQuadState* shared_quad_state =
+ quad_sink.UseSharedQuadState(SharedQuadState::Create());
+ shared_quad_state->SetAll(
+ root_pass_transform,
+ root_pass_content_bounds,
+ root_pass_rect,
+ root_pass_clip_rect,
+ root_pass_clipped,
+ 1.f);
+
+ scoped_ptr<RenderPassDrawQuad> render_pass_quad =
+ RenderPassDrawQuad::Create();
+ render_pass_quad->SetNew(
+ shared_quad_state,
+ gfx::Rect(5, 5, 7, 7), // rect
+ RenderPass::Id(10, 7), // render_pass_id
+ false, // is_replica
+ 0, // mask_resource_id
+ child_pass_rect, // contents_changed_since_last_frame
+ gfx::RectF(), // mask_uv_rect
+ FilterOperations(), // filters
+ skia::RefPtr<SkImageFilter>(), // filter
+ FilterOperations()); // background_filters
+ quad_sink.Append(render_pass_quad.PassAs<DrawQuad>(), &data);
+
+ scoped_ptr<SolidColorDrawQuad> color_quad;
+ color_quad = SolidColorDrawQuad::Create();
+ color_quad->SetNew(shared_quad_state, gfx::Rect(0, 0, 10, 10), 1u, false);
+ quad_sink.Append(color_quad.PassAs<DrawQuad>(), &data);
+
+ color_quad = SolidColorDrawQuad::Create();
+ color_quad->SetNew(shared_quad_state, gfx::Rect(0, 10, 10, 10), 2u, false);
+ quad_sink.Append(color_quad.PassAs<DrawQuad>(), &data);
+
+ color_quad = SolidColorDrawQuad::Create();
+ color_quad->SetNew(shared_quad_state, gfx::Rect(10, 0, 10, 10), 3u, false);
+ quad_sink.Append(color_quad.PassAs<DrawQuad>(), &data);
+
+ color_quad = SolidColorDrawQuad::Create();
+ color_quad->SetNew(shared_quad_state, gfx::Rect(10, 10, 10, 10), 4u, false);
+ quad_sink.Append(color_quad.PassAs<DrawQuad>(), &data);
+
+ delegated_renderer_layer->SetFrameDataForRenderPasses(
+ &delegated_render_passes);
+
+ // The RenderPasses should be taken by the layer.
+ EXPECT_EQ(0u, delegated_render_passes.size());
+
+ root_layer_ = root_layer.get();
+ delegated_renderer_layer_ = delegated_renderer_layer.get();
+
+ root_layer->AddChild(delegated_renderer_layer.PassAs<LayerImpl>());
+ host_impl_->active_tree()->SetRootLayer(root_layer.Pass());
+ }
+
+ void VerifyRenderPasses(
+ const LayerTreeHostImpl::FrameData& frame,
+ size_t num_render_passes,
+ const SharedQuadState** root_delegated_shared_quad_state,
+ const SharedQuadState** contrib_delegated_shared_quad_state) {
+ ASSERT_EQ(num_render_passes, frame.render_passes.size());
+ // The contributing render pass in the DelegatedRendererLayer.
+ EXPECT_EQ(2, frame.render_passes[0]->id.layer_id);
+ EXPECT_EQ(1, frame.render_passes[0]->id.index);
+ // The root render pass.
+ EXPECT_EQ(1, frame.render_passes.back()->id.layer_id);
+ EXPECT_EQ(0, frame.render_passes.back()->id.index);
+
+ const QuadList& contrib_delegated_quad_list =
+ frame.render_passes[0]->quad_list;
+ ASSERT_EQ(2u, contrib_delegated_quad_list.size());
+
+ const QuadList& root_delegated_quad_list =
+ frame.render_passes[1]->quad_list;
+ ASSERT_EQ(5u, root_delegated_quad_list.size());
+
+ // All quads in a render pass should share the same state.
+ *contrib_delegated_shared_quad_state =
+ contrib_delegated_quad_list[0]->shared_quad_state;
+ EXPECT_EQ(*contrib_delegated_shared_quad_state,
+ contrib_delegated_quad_list[1]->shared_quad_state);
+
+ *root_delegated_shared_quad_state =
+ root_delegated_quad_list[0]->shared_quad_state;
+ EXPECT_EQ(*root_delegated_shared_quad_state,
+ root_delegated_quad_list[1]->shared_quad_state);
+ EXPECT_EQ(*root_delegated_shared_quad_state,
+ root_delegated_quad_list[2]->shared_quad_state);
+ EXPECT_EQ(*root_delegated_shared_quad_state,
+ root_delegated_quad_list[3]->shared_quad_state);
+ EXPECT_EQ(*root_delegated_shared_quad_state,
+ root_delegated_quad_list[4]->shared_quad_state);
+
+ EXPECT_NE(*contrib_delegated_shared_quad_state,
+ *root_delegated_shared_quad_state);
+ }
+
+ protected:
+ LayerImpl* root_layer_;
+ DelegatedRendererLayerImpl* delegated_renderer_layer_;
+ bool root_delegated_render_pass_is_clipped_;
+};
+
+TEST_F(DelegatedRendererLayerImplTestTransform, QuadsUnclipped_NoSurface) {
+ root_delegated_render_pass_is_clipped_ = false;
+ SetUpTest();
+
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ const SharedQuadState* root_delegated_shared_quad_state = NULL;
+ const SharedQuadState* contrib_delegated_shared_quad_state = NULL;
+ VerifyRenderPasses(
+ frame,
+ 2,
+ &root_delegated_shared_quad_state,
+ &contrib_delegated_shared_quad_state);
+
+ // When the quads don't have a clip of their own, the clip rect is set to
+ // the drawable_content_rect of the delegated renderer layer.
+ EXPECT_EQ(gfx::Rect(42, 42, 120, 120).ToString(),
+ root_delegated_shared_quad_state->clip_rect.ToString());
+
+ // Even though the quads in the root pass have no clip of their own, they
+ // inherit the clip rect from the delegated renderer layer if it does not
+ // own a surface.
+ EXPECT_TRUE(root_delegated_shared_quad_state->is_clipped);
+
+ gfx::Transform expected;
+ // Device scale factor is 2.
+ expected.Scale(2.0, 2.0);
+ // This is the transform from the layer's space to its target.
+ // The position (20) - the width / scale (30 / 2) = 20 - 15 = 5
+ expected.Translate(5.0, 5.0);
+ expected.Scale(2.0, 2.0);
+ expected.Translate(8.0, 8.0);
+ // The frame has size 50x50 but the layer's bounds are 30x30.
+ expected.Scale(30.0 / 50.0, 30.0 / 50.0);
+ // This is the transform within the source frame.
+ expected.Scale(1.5, 1.5);
+ expected.Translate(7.0, 7.0);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, root_delegated_shared_quad_state->content_to_target_transform);
+
+ // The contributing render pass should not be transformed from its input.
+ EXPECT_EQ(gfx::Rect(21, 21, 3, 3).ToString(),
+ contrib_delegated_shared_quad_state->clip_rect.ToString());
+ EXPECT_FALSE(contrib_delegated_shared_quad_state->is_clipped);
+ expected.MakeIdentity();
+ expected.Scale(0.8, 0.8);
+ expected.Translate(9.0, 9.0);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected,
+ contrib_delegated_shared_quad_state->content_to_target_transform);
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+}
+
+TEST_F(DelegatedRendererLayerImplTestTransform, QuadsClipped_NoSurface) {
+ root_delegated_render_pass_is_clipped_ = true;
+ SetUpTest();
+
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ const SharedQuadState* root_delegated_shared_quad_state = NULL;
+ const SharedQuadState* contrib_delegated_shared_quad_state = NULL;
+ VerifyRenderPasses(
+ frame,
+ 2,
+ &root_delegated_shared_quad_state,
+ &contrib_delegated_shared_quad_state);
+
+ // Since the quads have a clip_rect it should be modified by delegated
+ // renderer layer's draw_transform.
+ // The position of the resulting clip_rect is:
+ // (clip rect position (10) * scale to layer (30/50) + translate (8)) *
+ // layer scale (2) + layer position (20) = 48
+ // But the layer is centered, so: 48 - (width / 2) = 48 - 30 / 2 = 33
+ // The device scale is 2, so everything gets doubled, giving 66.
+ //
+ // The size is 35x35 scaled to fit inside the layer's bounds at 30x30 from
+ // a frame at 50x50: 35 * 2 (device scale) * 30 / 50 = 42. The device scale
+ // doubles this to 84.
+ EXPECT_EQ(gfx::Rect(66, 66, 84, 84).ToString(),
+ root_delegated_shared_quad_state->clip_rect.ToString());
+
+ // The quads had a clip and it should be preserved.
+ EXPECT_TRUE(root_delegated_shared_quad_state->is_clipped);
+
+ gfx::Transform expected;
+ // Device scale factor is 2.
+ expected.Scale(2.0, 2.0);
+ // This is the transform from the layer's space to its target.
+ // The position (20) - the width / scale (30 / 2) = 20 - 15 = 5
+ expected.Translate(5.0, 5.0);
+ expected.Scale(2.0, 2.0);
+ expected.Translate(8.0, 8.0);
+ // The frame has size 50x50 but the layer's bounds are 30x30.
+ expected.Scale(30.0 / 50.0, 30.0 / 50.0);
+ // This is the transform within the source frame.
+ expected.Scale(1.5, 1.5);
+ expected.Translate(7.0, 7.0);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, root_delegated_shared_quad_state->content_to_target_transform);
+
+ // The contributing render pass should not be transformed from its input.
+ EXPECT_EQ(gfx::Rect(21, 21, 3, 3).ToString(),
+ contrib_delegated_shared_quad_state->clip_rect.ToString());
+ EXPECT_FALSE(contrib_delegated_shared_quad_state->is_clipped);
+ expected.MakeIdentity();
+ expected.Scale(0.8, 0.8);
+ expected.Translate(9.0, 9.0);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected,
+ contrib_delegated_shared_quad_state->content_to_target_transform);
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+}
+
+TEST_F(DelegatedRendererLayerImplTestTransform, QuadsUnclipped_Surface) {
+ root_delegated_render_pass_is_clipped_ = false;
+ SetUpTest();
+
+ delegated_renderer_layer_->SetForceRenderSurface(true);
+
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ const SharedQuadState* root_delegated_shared_quad_state = NULL;
+ const SharedQuadState* contrib_delegated_shared_quad_state = NULL;
+ VerifyRenderPasses(
+ frame,
+ 3,
+ &root_delegated_shared_quad_state,
+ &contrib_delegated_shared_quad_state);
+
+ // When the layer owns a surface, then its position and translation are not
+ // a part of its draw transform.
+ // The position of the resulting clip_rect is:
+ // (clip rect position (10) * scale to layer (30/50)) * device scale (2) = 12
+ // The size is 35x35 scaled to fit inside the layer's bounds at 30x30 from
+ // a frame at 50x50: 35 * 2 (device scale) * 30 / 50 = 42.
+ EXPECT_EQ(gfx::Rect(12, 12, 42, 42).ToString(),
+ root_delegated_shared_quad_state->clip_rect.ToString());
+
+ // Since the layer owns a surface it doesn't need to clip its quads, so
+ // unclipped quads remain unclipped.
+ EXPECT_FALSE(root_delegated_shared_quad_state->is_clipped);
+
+ gfx::Transform expected;
+ // Device scale factor is 2.
+ expected.Scale(2.0, 2.0);
+ // The frame has size 50x50 but the layer's bounds are 30x30.
+ expected.Scale(30.0 / 50.0, 30.0 / 50.0);
+ // This is the transform within the source frame.
+ expected.Scale(1.5, 1.5);
+ expected.Translate(7.0, 7.0);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, root_delegated_shared_quad_state->content_to_target_transform);
+
+ // The contributing render pass should not be transformed from its input.
+ EXPECT_EQ(gfx::Rect(21, 21, 3, 3).ToString(),
+ contrib_delegated_shared_quad_state->clip_rect.ToString());
+ EXPECT_FALSE(contrib_delegated_shared_quad_state->is_clipped);
+ expected.MakeIdentity();
+ expected.Scale(0.8, 0.8);
+ expected.Translate(9.0, 9.0);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected,
+ contrib_delegated_shared_quad_state->content_to_target_transform);
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+}
+
+TEST_F(DelegatedRendererLayerImplTestTransform, QuadsClipped_Surface) {
+ root_delegated_render_pass_is_clipped_ = true;
+ SetUpTest();
+
+ delegated_renderer_layer_->SetForceRenderSurface(true);
+
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ const SharedQuadState* root_delegated_shared_quad_state = NULL;
+ const SharedQuadState* contrib_delegated_shared_quad_state = NULL;
+ VerifyRenderPasses(
+ frame,
+ 3,
+ &root_delegated_shared_quad_state,
+ &contrib_delegated_shared_quad_state);
+
+ // When the layer owns a surface, then its position and translation are not
+ // a part of its draw transform.
+ // The position of the resulting clip_rect is:
+ // (clip rect position (10) * scale to layer (30/50)) * device scale (2) = 12
+ // The size is 35x35 scaled to fit inside the layer's bounds at 30x30 from
+ // a frame at 50x50: 35 * 2 (device scale) * 30 / 50 = 42.
+ EXPECT_EQ(gfx::Rect(12, 12, 42, 42).ToString(),
+ root_delegated_shared_quad_state->clip_rect.ToString());
+
+ // The quads had a clip and it should be preserved.
+ EXPECT_TRUE(root_delegated_shared_quad_state->is_clipped);
+
+ gfx::Transform expected;
+ // Device scale factor is 2.
+ expected.Scale(2.0, 2.0);
+ // The frame has size 50x50 but the layer's bounds are 30x30.
+ expected.Scale(30.0 / 50.0, 30.0 / 50.0);
+ // This is the transform within the source frame.
+ expected.Scale(1.5, 1.5);
+ expected.Translate(7.0, 7.0);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected, root_delegated_shared_quad_state->content_to_target_transform);
+
+ // The contributing render pass should not be transformed from its input.
+ EXPECT_EQ(gfx::Rect(21, 21, 3, 3).ToString(),
+ contrib_delegated_shared_quad_state->clip_rect.ToString());
+ EXPECT_FALSE(contrib_delegated_shared_quad_state->is_clipped);
+ expected.MakeIdentity();
+ expected.Scale(0.8, 0.8);
+ expected.Translate(9.0, 9.0);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected,
+ contrib_delegated_shared_quad_state->content_to_target_transform);
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+}
+
+class DelegatedRendererLayerImplTestClip
+ : public DelegatedRendererLayerImplTest {
+ public:
+ void SetUpTest() {
+ scoped_ptr<LayerImpl> root_layer =
+ LayerImpl::Create(host_impl_->active_tree(), 1);
+ scoped_ptr<FakeDelegatedRendererLayerImpl> delegated_renderer_layer =
+ FakeDelegatedRendererLayerImpl::Create(host_impl_->active_tree(), 2);
+ scoped_ptr<LayerImpl> clip_layer =
+ LayerImpl::Create(host_impl_->active_tree(), 3);
+ scoped_ptr<LayerImpl> origin_layer =
+ LayerImpl::Create(host_impl_->active_tree(), 4);
+
+ host_impl_->SetViewportSize(gfx::Size(100, 100));
+ root_layer->SetBounds(gfx::Size(100, 100));
+
+ delegated_renderer_layer->SetPosition(gfx::Point(20, 20));
+ delegated_renderer_layer->SetBounds(gfx::Size(50, 50));
+ delegated_renderer_layer->SetContentBounds(gfx::Size(50, 50));
+ delegated_renderer_layer->SetDrawsContent(true);
+
+ ScopedPtrVector<RenderPass> delegated_render_passes;
+
+ gfx::Size child_pass_content_bounds(7, 7);
+ gfx::Rect child_pass_rect(20, 20, 7, 7);
+ gfx::Transform child_pass_transform;
+ gfx::Rect child_pass_clip_rect(21, 21, 3, 3);
+ bool child_pass_clipped = false;
+
+ {
+ TestRenderPass* pass = AddRenderPass(
+ &delegated_render_passes,
+ RenderPass::Id(10, 7),
+ child_pass_rect,
+ gfx::Transform());
+ MockQuadCuller quad_sink(&pass->quad_list, &pass->shared_quad_state_list);
+ AppendQuadsData data(pass->id);
+ SharedQuadState* shared_quad_state =
+ quad_sink.UseSharedQuadState(SharedQuadState::Create());
+ shared_quad_state->SetAll(
+ child_pass_transform,
+ child_pass_content_bounds,
+ child_pass_rect,
+ child_pass_clip_rect,
+ child_pass_clipped,
+ 1.f);
+
+ scoped_ptr<SolidColorDrawQuad> color_quad;
+ color_quad = SolidColorDrawQuad::Create();
+ color_quad->SetNew(shared_quad_state, gfx::Rect(20, 20, 3, 7), 1u, false);
+ quad_sink.Append(color_quad.PassAs<DrawQuad>(), &data);
+
+ color_quad = SolidColorDrawQuad::Create();
+ color_quad->SetNew(shared_quad_state, gfx::Rect(23, 20, 4, 7), 1u, false);
+ quad_sink.Append(color_quad.PassAs<DrawQuad>(), &data);
+ }
+
+ gfx::Size root_pass_content_bounds(50, 50);
+ gfx::Rect root_pass_rect(0, 0, 50, 50);
+ gfx::Transform root_pass_transform;
+ gfx::Rect root_pass_clip_rect(5, 5, 40, 40);
+ bool root_pass_clipped = root_delegated_render_pass_is_clipped_;
+
+ TestRenderPass* pass = AddRenderPass(
+ &delegated_render_passes,
+ RenderPass::Id(9, 6),
+ root_pass_rect,
+ gfx::Transform());
+ MockQuadCuller quad_sink(&pass->quad_list, &pass->shared_quad_state_list);
+ AppendQuadsData data(pass->id);
+ SharedQuadState* shared_quad_state =
+ quad_sink.UseSharedQuadState(SharedQuadState::Create());
+ shared_quad_state->SetAll(root_pass_transform,
+ root_pass_content_bounds,
+ root_pass_rect,
+ root_pass_clip_rect,
+ root_pass_clipped,
+ 1.f);
+
+ scoped_ptr<RenderPassDrawQuad> render_pass_quad =
+ RenderPassDrawQuad::Create();
+ render_pass_quad->SetNew(
+ shared_quad_state,
+ gfx::Rect(5, 5, 7, 7), // rect
+ RenderPass::Id(10, 7), // render_pass_id
+ false, // is_replica
+ 0, // mask_resource_id
+ child_pass_rect, // contents_changed_since_last_frame
+ gfx::RectF(), // mask_uv_rect
+ FilterOperations(), // filters
+ skia::RefPtr<SkImageFilter>(), // filter
+ FilterOperations()); // background_filters
+ quad_sink.Append(render_pass_quad.PassAs<DrawQuad>(), &data);
+
+ scoped_ptr<SolidColorDrawQuad> color_quad;
+ color_quad = SolidColorDrawQuad::Create();
+ color_quad->SetNew(shared_quad_state, gfx::Rect(0, 0, 10, 10), 1u, false);
+ quad_sink.Append(color_quad.PassAs<DrawQuad>(), &data);
+
+ color_quad = SolidColorDrawQuad::Create();
+ color_quad->SetNew(shared_quad_state, gfx::Rect(0, 10, 10, 10), 2u, false);
+ quad_sink.Append(color_quad.PassAs<DrawQuad>(), &data);
+
+ color_quad = SolidColorDrawQuad::Create();
+ color_quad->SetNew(shared_quad_state, gfx::Rect(10, 0, 10, 10), 3u, false);
+ quad_sink.Append(color_quad.PassAs<DrawQuad>(), &data);
+
+ color_quad = SolidColorDrawQuad::Create();
+ color_quad->SetNew(shared_quad_state, gfx::Rect(10, 10, 10, 10), 4u, false);
+ quad_sink.Append(color_quad.PassAs<DrawQuad>(), &data);
+
+ delegated_renderer_layer->SetFrameDataForRenderPasses(
+ &delegated_render_passes);
+
+ // The RenderPasses should be taken by the layer.
+ EXPECT_EQ(0u, delegated_render_passes.size());
+
+ root_layer_ = root_layer.get();
+ delegated_renderer_layer_ = delegated_renderer_layer.get();
+
+ if (clip_delegated_renderer_layer_) {
+ gfx::Rect clip_rect(21, 27, 23, 21);
+
+ clip_layer->SetPosition(clip_rect.origin());
+ clip_layer->SetBounds(clip_rect.size());
+ clip_layer->SetContentBounds(clip_rect.size());
+ clip_layer->SetMasksToBounds(true);
+
+ origin_layer->SetPosition(
+ gfx::PointAtOffsetFromOrigin(-clip_rect.OffsetFromOrigin()));
+
+ origin_layer->AddChild(delegated_renderer_layer.PassAs<LayerImpl>());
+ clip_layer->AddChild(origin_layer.Pass());
+ root_layer->AddChild(clip_layer.Pass());
+ } else {
+ root_layer->AddChild(delegated_renderer_layer.PassAs<LayerImpl>());
+ }
+
+ host_impl_->active_tree()->SetRootLayer(root_layer.Pass());
+ }
+
+ protected:
+ LayerImpl* root_layer_;
+ DelegatedRendererLayerImpl* delegated_renderer_layer_;
+ bool root_delegated_render_pass_is_clipped_;
+ bool clip_delegated_renderer_layer_;
+};
+
+TEST_F(DelegatedRendererLayerImplTestClip,
+ QuadsUnclipped_LayerUnclipped_NoSurface) {
+ root_delegated_render_pass_is_clipped_ = false;
+ clip_delegated_renderer_layer_ = false;
+ SetUpTest();
+
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ ASSERT_EQ(2u, frame.render_passes.size());
+ const QuadList& contrib_delegated_quad_list =
+ frame.render_passes[0]->quad_list;
+ ASSERT_EQ(2u, contrib_delegated_quad_list.size());
+ const QuadList& root_delegated_quad_list = frame.render_passes[1]->quad_list;
+ ASSERT_EQ(5u, root_delegated_quad_list.size());
+ const SharedQuadState* root_delegated_shared_quad_state =
+ root_delegated_quad_list[0]->shared_quad_state;
+
+ // When the quads don't have a clip of their own, the clip rect is set to
+ // the drawable_content_rect of the delegated renderer layer.
+ EXPECT_EQ(gfx::Rect(20, 20, 50, 50).ToString(),
+ root_delegated_shared_quad_state->clip_rect.ToString());
+ // Quads are clipped to the delegated renderer layer.
+ EXPECT_TRUE(root_delegated_shared_quad_state->is_clipped);
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+}
+
+TEST_F(DelegatedRendererLayerImplTestClip,
+ QuadsClipped_LayerUnclipped_NoSurface) {
+ root_delegated_render_pass_is_clipped_ = true;
+ clip_delegated_renderer_layer_ = false;
+ SetUpTest();
+
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ ASSERT_EQ(2u, frame.render_passes.size());
+ const QuadList& contrib_delegated_quad_list =
+ frame.render_passes[0]->quad_list;
+ ASSERT_EQ(2u, contrib_delegated_quad_list.size());
+ const QuadList& root_delegated_quad_list =
+ frame.render_passes[1]->quad_list;
+ ASSERT_EQ(5u, root_delegated_quad_list.size());
+ const SharedQuadState* root_delegated_shared_quad_state =
+ root_delegated_quad_list[0]->shared_quad_state;
+
+ // When the quads have a clip of their own, it is used.
+ EXPECT_EQ(gfx::Rect(25, 25, 40, 40).ToString(),
+ root_delegated_shared_quad_state->clip_rect.ToString());
+ // Quads came with a clip rect.
+ EXPECT_TRUE(root_delegated_shared_quad_state->is_clipped);
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+}
+
+TEST_F(DelegatedRendererLayerImplTestClip,
+ QuadsUnclipped_LayerClipped_NoSurface) {
+ root_delegated_render_pass_is_clipped_ = false;
+ clip_delegated_renderer_layer_ = true;
+ SetUpTest();
+
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ ASSERT_EQ(2u, frame.render_passes.size());
+ const QuadList& contrib_delegated_quad_list =
+ frame.render_passes[0]->quad_list;
+ ASSERT_EQ(2u, contrib_delegated_quad_list.size());
+ const QuadList& root_delegated_quad_list = frame.render_passes[1]->quad_list;
+ ASSERT_EQ(5u, root_delegated_quad_list.size());
+ const SharedQuadState* root_delegated_shared_quad_state =
+ root_delegated_quad_list[0]->shared_quad_state;
+
+ // When the quads don't have a clip of their own, the clip rect is set to
+ // the drawable_content_rect of the delegated renderer layer. When the layer
+ // is clipped, that should be seen in the quads' clip_rect.
+ EXPECT_EQ(gfx::Rect(21, 27, 23, 21).ToString(),
+ root_delegated_shared_quad_state->clip_rect.ToString());
+ // Quads are clipped to the delegated renderer layer.
+ EXPECT_TRUE(root_delegated_shared_quad_state->is_clipped);
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+}
+
+TEST_F(DelegatedRendererLayerImplTestClip,
+ QuadsClipped_LayerClipped_NoSurface) {
+ root_delegated_render_pass_is_clipped_ = true;
+ clip_delegated_renderer_layer_ = true;
+ SetUpTest();
+
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ ASSERT_EQ(2u, frame.render_passes.size());
+ const QuadList& contrib_delegated_quad_list =
+ frame.render_passes[0]->quad_list;
+ ASSERT_EQ(2u, contrib_delegated_quad_list.size());
+ const QuadList& root_delegated_quad_list = frame.render_passes[1]->quad_list;
+ ASSERT_EQ(5u, root_delegated_quad_list.size());
+ const SharedQuadState* root_delegated_shared_quad_state =
+ root_delegated_quad_list[0]->shared_quad_state;
+
+ // When the quads have a clip of their own, it is used, but it is
+ // combined with the clip rect of the delegated renderer layer.
+ EXPECT_EQ(gfx::Rect(25, 27, 19, 21).ToString(),
+ root_delegated_shared_quad_state->clip_rect.ToString());
+ // Quads came with a clip rect.
+ EXPECT_TRUE(root_delegated_shared_quad_state->is_clipped);
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+}
+
+TEST_F(DelegatedRendererLayerImplTestClip,
+ QuadsUnclipped_LayerUnclipped_Surface) {
+ root_delegated_render_pass_is_clipped_ = false;
+ clip_delegated_renderer_layer_ = false;
+ SetUpTest();
+
+ delegated_renderer_layer_->SetForceRenderSurface(true);
+
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ ASSERT_EQ(3u, frame.render_passes.size());
+ const QuadList& contrib_delegated_quad_list =
+ frame.render_passes[0]->quad_list;
+ ASSERT_EQ(2u, contrib_delegated_quad_list.size());
+ const QuadList& root_delegated_quad_list = frame.render_passes[1]->quad_list;
+ ASSERT_EQ(5u, root_delegated_quad_list.size());
+ const SharedQuadState* root_delegated_shared_quad_state =
+ root_delegated_quad_list[0]->shared_quad_state;
+
+ // When the layer owns a surface, the quads don't need to be clipped
+ // further than they already specify. If they aren't clipped, then their
+ // clip rect is ignored, and they are not set as clipped.
+ EXPECT_FALSE(root_delegated_shared_quad_state->is_clipped);
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+}
+
+TEST_F(DelegatedRendererLayerImplTestClip,
+ QuadsClipped_LayerUnclipped_Surface) {
+ root_delegated_render_pass_is_clipped_ = true;
+ clip_delegated_renderer_layer_ = false;
+ SetUpTest();
+
+ delegated_renderer_layer_->SetForceRenderSurface(true);
+
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ ASSERT_EQ(3u, frame.render_passes.size());
+ const QuadList& contrib_delegated_quad_list =
+ frame.render_passes[0]->quad_list;
+ ASSERT_EQ(2u, contrib_delegated_quad_list.size());
+ const QuadList& root_delegated_quad_list = frame.render_passes[1]->quad_list;
+ ASSERT_EQ(5u, root_delegated_quad_list.size());
+ const SharedQuadState* root_delegated_shared_quad_state =
+ root_delegated_quad_list[0]->shared_quad_state;
+
+ // When the quads have a clip of their own, it is used.
+ EXPECT_EQ(gfx::Rect(5, 5, 40, 40).ToString(),
+ root_delegated_shared_quad_state->clip_rect.ToString());
+ // Quads came with a clip rect.
+ EXPECT_TRUE(root_delegated_shared_quad_state->is_clipped);
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+}
+
+TEST_F(DelegatedRendererLayerImplTestClip,
+ QuadsUnclipped_LayerClipped_Surface) {
+ root_delegated_render_pass_is_clipped_ = false;
+ clip_delegated_renderer_layer_ = true;
+ SetUpTest();
+
+ delegated_renderer_layer_->SetForceRenderSurface(true);
+
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ ASSERT_EQ(3u, frame.render_passes.size());
+ const QuadList& contrib_delegated_quad_list =
+ frame.render_passes[0]->quad_list;
+ ASSERT_EQ(2u, contrib_delegated_quad_list.size());
+ const QuadList& root_delegated_quad_list = frame.render_passes[1]->quad_list;
+ ASSERT_EQ(5u, root_delegated_quad_list.size());
+ const SharedQuadState* root_delegated_shared_quad_state =
+ root_delegated_quad_list[0]->shared_quad_state;
+
+ // When the layer owns a surface, the quads don't need to be clipped
+ // further than they already specify. If they aren't clipped, then their
+ // clip rect is ignored, and they are not set as clipped.
+ EXPECT_FALSE(root_delegated_shared_quad_state->is_clipped);
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+}
+
+TEST_F(DelegatedRendererLayerImplTestClip, QuadsClipped_LayerClipped_Surface) {
+ root_delegated_render_pass_is_clipped_ = true;
+ clip_delegated_renderer_layer_ = true;
+ SetUpTest();
+
+ delegated_renderer_layer_->SetForceRenderSurface(true);
+
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ ASSERT_EQ(3u, frame.render_passes.size());
+ const QuadList& contrib_delegated_quad_list =
+ frame.render_passes[0]->quad_list;
+ ASSERT_EQ(2u, contrib_delegated_quad_list.size());
+ const QuadList& root_delegated_quad_list = frame.render_passes[1]->quad_list;
+ ASSERT_EQ(5u, root_delegated_quad_list.size());
+ const SharedQuadState* root_delegated_shared_quad_state =
+ root_delegated_quad_list[0]->shared_quad_state;
+
+ // When the quads have a clip of their own, it is used, but it is
+ // combined with the clip rect of the delegated renderer layer. If the
+ // layer owns a surface, then it does not have a clip rect of its own.
+ EXPECT_EQ(gfx::Rect(5, 5, 40, 40).ToString(),
+ root_delegated_shared_quad_state->clip_rect.ToString());
+ // Quads came with a clip rect.
+ EXPECT_TRUE(root_delegated_shared_quad_state->is_clipped);
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/layers/draw_properties.h b/chromium/cc/layers/draw_properties.h
new file mode 100644
index 00000000000..bb80dce0adf
--- /dev/null
+++ b/chromium/cc/layers/draw_properties.h
@@ -0,0 +1,104 @@
+// 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.
+
+#ifndef CC_LAYERS_DRAW_PROPERTIES_H_
+#define CC_LAYERS_DRAW_PROPERTIES_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+
+// Container for properties that layers need to compute before they can be
+// drawn.
+template <typename LayerType, typename RenderSurfaceType>
+struct CC_EXPORT DrawProperties {
+ DrawProperties()
+ : opacity(0.f),
+ opacity_is_animating(false),
+ screen_space_opacity_is_animating(false),
+ target_space_transform_is_animating(false),
+ screen_space_transform_is_animating(false),
+ can_use_lcd_text(false),
+ is_clipped(false),
+ render_target(NULL),
+ contents_scale_x(1.f),
+ contents_scale_y(1.f),
+ num_descendants_that_draw_content(0),
+ descendants_can_clip_selves(false),
+ can_draw_directly_to_backbuffer(false),
+ layer_or_descendant_has_copy_request(false) {}
+
+ // Transforms objects from content space to target surface space, where
+ // this layer would be drawn.
+ gfx::Transform target_space_transform;
+
+ // Transforms objects from content space to screen space (viewport space).
+ gfx::Transform screen_space_transform;
+
+ // DrawProperties::opacity may be different than LayerType::opacity,
+ // particularly in the case when a RenderSurface re-parents the layer's
+ // opacity, or when opacity is compounded by the hierarchy.
+ float opacity;
+
+ // xxx_is_animating flags are used to indicate whether the DrawProperties
+ // are actually meaningful on the main thread. When the properties are
+ // animating, the main thread may not have the same values that are used
+ // to draw.
+ bool opacity_is_animating;
+ bool screen_space_opacity_is_animating;
+ bool target_space_transform_is_animating;
+ bool screen_space_transform_is_animating;
+
+ // True if the layer can use LCD text.
+ bool can_use_lcd_text;
+
+ // True if the layer needs to be clipped by clip_rect.
+ bool is_clipped;
+
+ // The layer whose coordinate space this layer draws into. This can be
+ // either the same layer (draw_properties_.render_target == this) or an
+ // ancestor of this layer.
+ LayerType* render_target;
+
+ // The surface that this layer and its subtree would contribute to.
+ scoped_ptr<RenderSurfaceType> render_surface;
+
+ // This rect is in the layer's content space.
+ gfx::Rect visible_content_rect;
+
+ // In target surface space, the rect that encloses the clipped, drawable
+ // content of the layer.
+ gfx::Rect drawable_content_rect;
+
+ // In target surface space, the original rect that clipped this layer. This
+ // value is used to avoid unnecessarily changing GL scissor state.
+ gfx::Rect clip_rect;
+
+ // The scale used to move between layer space and content space, and bounds
+ // of the space. One is always a function of the other, but which one
+ // depends on the layer type. For picture layers, this is an ideal scale,
+ // and not always the one used.
+ float contents_scale_x;
+ float contents_scale_y;
+ gfx::Size content_bounds;
+
+ // Does not include this layer itself, only its children and descendants.
+ int num_descendants_that_draw_content;
+
+ // If true, every descendant in the sub-tree can clip itself without the
+ // need to use hardware sissoring or a new render target.
+ bool descendants_can_clip_selves;
+
+ bool can_draw_directly_to_backbuffer;
+
+ // If true, the layer or some layer in its sub-tree has a CopyOutputRequest
+ // present on it.
+ bool layer_or_descendant_has_copy_request;
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_DRAW_PROPERTIES_H_
diff --git a/chromium/cc/layers/heads_up_display_layer.cc b/chromium/cc/layers/heads_up_display_layer.cc
new file mode 100644
index 00000000000..92486cc8515
--- /dev/null
+++ b/chromium/cc/layers/heads_up_display_layer.cc
@@ -0,0 +1,58 @@
+// 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 "cc/layers/heads_up_display_layer.h"
+
+#include <algorithm>
+
+#include "base/debug/trace_event.h"
+#include "cc/layers/heads_up_display_layer_impl.h"
+#include "cc/trees/layer_tree_host.h"
+
+namespace cc {
+
+scoped_refptr<HeadsUpDisplayLayer> HeadsUpDisplayLayer::Create() {
+ return make_scoped_refptr(new HeadsUpDisplayLayer());
+}
+
+HeadsUpDisplayLayer::HeadsUpDisplayLayer() {}
+
+HeadsUpDisplayLayer::~HeadsUpDisplayLayer() {}
+
+void HeadsUpDisplayLayer::PrepareForCalculateDrawProperties(
+ gfx::Size device_viewport, float device_scale_factor) {
+ gfx::Size device_viewport_in_layout_pixels = gfx::Size(
+ device_viewport.width() / device_scale_factor,
+ device_viewport.height() / device_scale_factor);
+
+ gfx::Size bounds;
+ gfx::Transform matrix;
+ matrix.MakeIdentity();
+
+ if (layer_tree_host()->debug_state().ShowHudRects()) {
+ int max_texture_size =
+ layer_tree_host()->GetRendererCapabilities().max_texture_size;
+ bounds.SetSize(std::min(max_texture_size,
+ device_viewport_in_layout_pixels.width()),
+ std::min(max_texture_size,
+ device_viewport_in_layout_pixels.height()));
+ } else {
+ int size = 256;
+ bounds.SetSize(size, size);
+ matrix.Translate(device_viewport_in_layout_pixels.width() - size, 0.0);
+ }
+
+ SetBounds(bounds);
+ SetTransform(matrix);
+}
+
+bool HeadsUpDisplayLayer::DrawsContent() const { return true; }
+
+scoped_ptr<LayerImpl> HeadsUpDisplayLayer::CreateLayerImpl(
+ LayerTreeImpl* tree_impl) {
+ return HeadsUpDisplayLayerImpl::Create(tree_impl, layer_id_).
+ PassAs<LayerImpl>();
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/heads_up_display_layer.h b/chromium/cc/layers/heads_up_display_layer.h
new file mode 100644
index 00000000000..7038cd480c3
--- /dev/null
+++ b/chromium/cc/layers/heads_up_display_layer.h
@@ -0,0 +1,37 @@
+// 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.
+
+#ifndef CC_LAYERS_HEADS_UP_DISPLAY_LAYER_H_
+#define CC_LAYERS_HEADS_UP_DISPLAY_LAYER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/layers/contents_scaling_layer.h"
+
+namespace cc {
+
+class CC_EXPORT HeadsUpDisplayLayer : public ContentsScalingLayer {
+ public:
+ static scoped_refptr<HeadsUpDisplayLayer> Create();
+
+ void PrepareForCalculateDrawProperties(
+ gfx::Size device_viewport, float device_scale_factor);
+
+ virtual bool DrawsContent() const OVERRIDE;
+
+ virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl)
+ OVERRIDE;
+
+ protected:
+ HeadsUpDisplayLayer();
+
+ private:
+ virtual ~HeadsUpDisplayLayer();
+
+ DISALLOW_COPY_AND_ASSIGN(HeadsUpDisplayLayer);
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_HEADS_UP_DISPLAY_LAYER_H_
diff --git a/chromium/cc/layers/heads_up_display_layer_impl.cc b/chromium/cc/layers/heads_up_display_layer_impl.cc
new file mode 100644
index 00000000000..8728d704ca4
--- /dev/null
+++ b/chromium/cc/layers/heads_up_display_layer_impl.cc
@@ -0,0 +1,696 @@
+// 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 "cc/layers/heads_up_display_layer_impl.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/strings/stringprintf.h"
+#include "cc/debug/debug_colors.h"
+#include "cc/debug/debug_rect_history.h"
+#include "cc/debug/frame_rate_counter.h"
+#include "cc/debug/paint_time_counter.h"
+#include "cc/layers/quad_sink.h"
+#include "cc/output/renderer.h"
+#include "cc/quads/texture_draw_quad.h"
+#include "cc/resources/memory_history.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "skia/ext/platform_canvas.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "third_party/khronos/GLES2/gl2ext.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkPaint.h"
+#include "third_party/skia/include/core/SkTypeface.h"
+#include "third_party/skia/include/effects/SkColorMatrixFilter.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/size.h"
+
+namespace cc {
+
+static inline SkPaint CreatePaint() {
+ SkPaint paint;
+#if (SK_R32_SHIFT || SK_B32_SHIFT != 16)
+ // The SkCanvas is in RGBA but the shader is expecting BGRA, so we need to
+ // swizzle our colors when drawing to the SkCanvas.
+ SkColorMatrix swizzle_matrix;
+ for (int i = 0; i < 20; ++i)
+ swizzle_matrix.fMat[i] = 0;
+ swizzle_matrix.fMat[0 + 5 * 2] = 1;
+ swizzle_matrix.fMat[1 + 5 * 1] = 1;
+ swizzle_matrix.fMat[2 + 5 * 0] = 1;
+ swizzle_matrix.fMat[3 + 5 * 3] = 1;
+
+ skia::RefPtr<SkColorMatrixFilter> filter =
+ skia::AdoptRef(new SkColorMatrixFilter(swizzle_matrix));
+ paint.setColorFilter(filter.get());
+#endif
+ return paint;
+}
+
+HeadsUpDisplayLayerImpl::Graph::Graph(double indicator_value,
+ double start_upper_bound)
+ : value(0.0),
+ min(0.0),
+ max(0.0),
+ current_upper_bound(start_upper_bound),
+ default_upper_bound(start_upper_bound),
+ indicator(indicator_value) {}
+
+double HeadsUpDisplayLayerImpl::Graph::UpdateUpperBound() {
+ double target_upper_bound = std::max(max, default_upper_bound);
+ current_upper_bound += (target_upper_bound - current_upper_bound) * 0.5;
+ return current_upper_bound;
+}
+
+HeadsUpDisplayLayerImpl::HeadsUpDisplayLayerImpl(LayerTreeImpl* tree_impl,
+ int id)
+ : LayerImpl(tree_impl, id),
+ typeface_(skia::AdoptRef(
+ SkTypeface::CreateFromName("monospace", SkTypeface::kBold))),
+ fps_graph_(60.0, 80.0),
+ paint_time_graph_(16.0, 48.0) {}
+
+HeadsUpDisplayLayerImpl::~HeadsUpDisplayLayerImpl() {}
+
+scoped_ptr<LayerImpl> HeadsUpDisplayLayerImpl::CreateLayerImpl(
+ LayerTreeImpl* tree_impl) {
+ return HeadsUpDisplayLayerImpl::Create(tree_impl, id()).PassAs<LayerImpl>();
+}
+
+bool HeadsUpDisplayLayerImpl::WillDraw(DrawMode draw_mode,
+ ResourceProvider* resource_provider) {
+ if (draw_mode == DRAW_MODE_RESOURCELESS_SOFTWARE)
+ return false;
+
+ if (!hud_resource_)
+ hud_resource_ = ScopedResource::create(resource_provider);
+
+ // TODO(danakj): The HUD could swap between two textures instead of creating a
+ // texture every frame in ubercompositor.
+ if (hud_resource_->size() != content_bounds() ||
+ resource_provider->InUseByConsumer(hud_resource_->id()))
+ hud_resource_->Free();
+
+ if (!hud_resource_->id()) {
+ hud_resource_->Allocate(
+ content_bounds(), GL_RGBA, ResourceProvider::TextureUsageAny);
+ }
+
+ return LayerImpl::WillDraw(draw_mode, resource_provider);
+}
+
+void HeadsUpDisplayLayerImpl::AppendQuads(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data) {
+ if (!hud_resource_->id())
+ return;
+
+ SharedQuadState* shared_quad_state =
+ quad_sink->UseSharedQuadState(CreateSharedQuadState());
+
+ gfx::Rect quad_rect(content_bounds());
+ gfx::Rect opaque_rect(contents_opaque() ? quad_rect : gfx::Rect());
+ bool premultiplied_alpha = true;
+ gfx::PointF uv_top_left(0.f, 0.f);
+ gfx::PointF uv_bottom_right(1.f, 1.f);
+ const float vertex_opacity[] = { 1.f, 1.f, 1.f, 1.f };
+ bool flipped = false;
+ scoped_ptr<TextureDrawQuad> quad = TextureDrawQuad::Create();
+ quad->SetNew(shared_quad_state,
+ quad_rect,
+ opaque_rect,
+ hud_resource_->id(),
+ premultiplied_alpha,
+ uv_top_left,
+ uv_bottom_right,
+ SK_ColorTRANSPARENT,
+ vertex_opacity,
+ flipped);
+ quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data);
+}
+
+void HeadsUpDisplayLayerImpl::UpdateHudTexture(
+ ResourceProvider* resource_provider) {
+ if (!hud_resource_->id())
+ return;
+
+ SkISize canvas_size;
+ if (hud_canvas_)
+ canvas_size = hud_canvas_->getDeviceSize();
+ else
+ canvas_size.set(0, 0);
+
+ if (canvas_size.width() != content_bounds().width() ||
+ canvas_size.width() != content_bounds().height() || !hud_canvas_) {
+ bool opaque = false;
+ hud_canvas_ = make_scoped_ptr(skia::CreateBitmapCanvas(
+ content_bounds().width(), content_bounds().height(), opaque));
+ }
+
+ UpdateHudContents();
+
+ hud_canvas_->clear(SkColorSetARGB(0, 0, 0, 0));
+ hud_canvas_->save();
+ hud_canvas_->scale(contents_scale_x(), contents_scale_y());
+
+ DrawHudContents(hud_canvas_.get());
+
+ hud_canvas_->restore();
+
+ const SkBitmap* bitmap = &hud_canvas_->getDevice()->accessBitmap(false);
+ SkAutoLockPixels locker(*bitmap);
+
+ gfx::Rect content_rect(content_bounds());
+ DCHECK(bitmap->config() == SkBitmap::kARGB_8888_Config);
+ resource_provider->SetPixels(hud_resource_->id(),
+ static_cast<const uint8_t*>(bitmap->getPixels()),
+ content_rect,
+ content_rect,
+ gfx::Vector2d());
+}
+
+void HeadsUpDisplayLayerImpl::DidLoseOutputSurface() { hud_resource_.reset(); }
+
+bool HeadsUpDisplayLayerImpl::LayerIsAlwaysDamaged() const { return true; }
+
+void HeadsUpDisplayLayerImpl::UpdateHudContents() {
+ const LayerTreeDebugState& debug_state = layer_tree_impl()->debug_state();
+
+ // Don't update numbers every frame so text is readable.
+ base::TimeTicks now = layer_tree_impl()->CurrentFrameTimeTicks();
+ if (base::TimeDelta(now - time_of_last_graph_update_).InSecondsF() > 0.25f) {
+ time_of_last_graph_update_ = now;
+
+ if (debug_state.show_fps_counter) {
+ FrameRateCounter* fps_counter = layer_tree_impl()->frame_rate_counter();
+ fps_graph_.value = fps_counter->GetAverageFPS();
+ fps_counter->GetMinAndMaxFPS(&fps_graph_.min, &fps_graph_.max);
+ }
+
+ if (debug_state.continuous_painting) {
+ PaintTimeCounter* paint_time_counter =
+ layer_tree_impl()->paint_time_counter();
+ base::TimeDelta latest, min, max;
+
+ if (paint_time_counter->End())
+ latest = **paint_time_counter->End();
+ paint_time_counter->GetMinAndMaxPaintTime(&min, &max);
+
+ paint_time_graph_.value = latest.InMillisecondsF();
+ paint_time_graph_.min = min.InMillisecondsF();
+ paint_time_graph_.max = max.InMillisecondsF();
+ }
+
+ if (debug_state.ShowMemoryStats()) {
+ MemoryHistory* memory_history = layer_tree_impl()->memory_history();
+ if (memory_history->End())
+ memory_entry_ = **memory_history->End();
+ else
+ memory_entry_ = MemoryHistory::Entry();
+ }
+ }
+
+ fps_graph_.UpdateUpperBound();
+ paint_time_graph_.UpdateUpperBound();
+}
+
+void HeadsUpDisplayLayerImpl::DrawHudContents(SkCanvas* canvas) const {
+ const LayerTreeDebugState& debug_state = layer_tree_impl()->debug_state();
+
+ if (debug_state.ShowHudRects())
+ DrawDebugRects(canvas, layer_tree_impl()->debug_rect_history());
+
+ SkRect area = SkRect::MakeEmpty();
+ if (debug_state.continuous_painting) {
+ area = DrawPaintTimeDisplay(
+ canvas, layer_tree_impl()->paint_time_counter(), 0, 0);
+ } else if (debug_state.show_fps_counter) {
+ // Don't show the FPS display when continuous painting is enabled, because
+ // it would show misleading numbers.
+ area =
+ DrawFPSDisplay(canvas, layer_tree_impl()->frame_rate_counter(), 0, 0);
+ }
+
+ if (debug_state.ShowMemoryStats())
+ DrawMemoryDisplay(canvas, 0, area.bottom(), SkMaxScalar(area.width(), 150));
+}
+
+void HeadsUpDisplayLayerImpl::DrawText(SkCanvas* canvas,
+ SkPaint* paint,
+ const std::string& text,
+ SkPaint::Align align,
+ int size,
+ int x,
+ int y) const {
+ const bool anti_alias = paint->isAntiAlias();
+ paint->setAntiAlias(true);
+
+ paint->setTextSize(size);
+ paint->setTextAlign(align);
+ paint->setTypeface(typeface_.get());
+ canvas->drawText(text.c_str(), text.length(), x, y, *paint);
+
+ paint->setAntiAlias(anti_alias);
+}
+
+void HeadsUpDisplayLayerImpl::DrawText(SkCanvas* canvas,
+ SkPaint* paint,
+ const std::string& text,
+ SkPaint::Align align,
+ int size,
+ const SkPoint& pos) const {
+ DrawText(canvas, paint, text, align, size, pos.x(), pos.y());
+}
+
+void HeadsUpDisplayLayerImpl::DrawGraphBackground(SkCanvas* canvas,
+ SkPaint* paint,
+ const SkRect& bounds) const {
+ paint->setColor(DebugColors::HUDBackgroundColor());
+ canvas->drawRect(bounds, *paint);
+}
+
+void HeadsUpDisplayLayerImpl::DrawGraphLines(SkCanvas* canvas,
+ SkPaint* paint,
+ const SkRect& bounds,
+ const Graph& graph) const {
+ // Draw top and bottom line.
+ paint->setColor(DebugColors::HUDSeparatorLineColor());
+ canvas->drawLine(bounds.left(),
+ bounds.top() - 1,
+ bounds.right(),
+ bounds.top() - 1,
+ *paint);
+ canvas->drawLine(
+ bounds.left(), bounds.bottom(), bounds.right(), bounds.bottom(), *paint);
+
+ // Draw indicator line (additive blend mode to increase contrast when drawn on
+ // top of graph).
+ paint->setColor(DebugColors::HUDIndicatorLineColor());
+ paint->setXfermodeMode(SkXfermode::kPlus_Mode);
+ const double indicator_top =
+ bounds.height() * (1.0 - graph.indicator / graph.current_upper_bound) -
+ 1.0;
+ canvas->drawLine(bounds.left(),
+ bounds.top() + indicator_top,
+ bounds.right(),
+ bounds.top() + indicator_top,
+ *paint);
+ paint->setXfermode(NULL);
+}
+
+SkRect HeadsUpDisplayLayerImpl::DrawFPSDisplay(
+ SkCanvas* canvas,
+ const FrameRateCounter* fps_counter,
+ int right,
+ int top) const {
+ const int kPadding = 4;
+ const int kGap = 6;
+
+ const int kFontHeight = 15;
+
+ const int kGraphWidth = fps_counter->time_stamp_history_size() - 2;
+ const int kGraphHeight = 40;
+
+ const int kHistogramWidth = 37;
+
+ int width = kGraphWidth + kHistogramWidth + 4 * kPadding;
+ int height = kFontHeight + kGraphHeight + 4 * kPadding + 2;
+ int left = bounds().width() - width - right;
+ SkRect area = SkRect::MakeXYWH(left, top, width, height);
+
+ SkPaint paint = CreatePaint();
+ DrawGraphBackground(canvas, &paint, area);
+
+ SkRect text_bounds =
+ SkRect::MakeXYWH(left + kPadding,
+ top + kPadding,
+ kGraphWidth + kHistogramWidth + kGap + 2,
+ kFontHeight);
+ SkRect graph_bounds = SkRect::MakeXYWH(left + kPadding,
+ text_bounds.bottom() + 2 * kPadding,
+ kGraphWidth,
+ kGraphHeight);
+ SkRect histogram_bounds = SkRect::MakeXYWH(graph_bounds.right() + kGap,
+ graph_bounds.top(),
+ kHistogramWidth,
+ kGraphHeight);
+
+ const std::string value_text =
+ base::StringPrintf("FPS:%5.1f", fps_graph_.value);
+ const std::string min_max_text =
+ base::StringPrintf("%.0f-%.0f", fps_graph_.min, fps_graph_.max);
+
+ paint.setColor(DebugColors::FPSDisplayTextAndGraphColor());
+ DrawText(canvas,
+ &paint,
+ value_text,
+ SkPaint::kLeft_Align,
+ kFontHeight,
+ text_bounds.left(),
+ text_bounds.bottom());
+ DrawText(canvas,
+ &paint,
+ min_max_text,
+ SkPaint::kRight_Align,
+ kFontHeight,
+ text_bounds.right(),
+ text_bounds.bottom());
+
+ DrawGraphLines(canvas, &paint, graph_bounds, fps_graph_);
+
+ // Collect graph and histogram data.
+ SkPath path;
+
+ const int kHistogramSize = 20;
+ double histogram[kHistogramSize] = { 1.0 };
+ double max_bucket_value = 1.0;
+
+ for (FrameRateCounter::RingBufferType::Iterator it = --fps_counter->end(); it;
+ --it) {
+ base::TimeDelta delta = fps_counter->RecentFrameInterval(it.index() + 1);
+
+ // Skip this particular instantaneous frame rate if it is not likely to have
+ // been valid.
+ if (!fps_counter->IsBadFrameInterval(delta)) {
+ double fps = 1.0 / delta.InSecondsF();
+
+ // Clamp the FPS to the range we want to plot visually.
+ double p = fps / fps_graph_.current_upper_bound;
+ if (p > 1.0)
+ p = 1.0;
+
+ // Plot this data point.
+ SkPoint cur =
+ SkPoint::Make(graph_bounds.left() + it.index(),
+ graph_bounds.bottom() - p * graph_bounds.height());
+ if (path.isEmpty())
+ path.moveTo(cur);
+ else
+ path.lineTo(cur);
+
+ // Use the fps value to find the right bucket in the histogram.
+ int bucket_index = floor(p * (kHistogramSize - 1));
+
+ // Add the delta time to take the time spent at that fps rate into
+ // account.
+ histogram[bucket_index] += delta.InSecondsF();
+ max_bucket_value = std::max(histogram[bucket_index], max_bucket_value);
+ }
+ }
+
+ // Draw FPS histogram.
+ paint.setColor(DebugColors::HUDSeparatorLineColor());
+ canvas->drawLine(histogram_bounds.left() - 1,
+ histogram_bounds.top() - 1,
+ histogram_bounds.left() - 1,
+ histogram_bounds.bottom() + 1,
+ paint);
+ canvas->drawLine(histogram_bounds.right() + 1,
+ histogram_bounds.top() - 1,
+ histogram_bounds.right() + 1,
+ histogram_bounds.bottom() + 1,
+ paint);
+
+ paint.setColor(DebugColors::FPSDisplayTextAndGraphColor());
+ const double bar_height = histogram_bounds.height() / kHistogramSize;
+
+ for (int i = kHistogramSize - 1; i >= 0; --i) {
+ if (histogram[i] > 0) {
+ double bar_width =
+ histogram[i] / max_bucket_value * histogram_bounds.width();
+ canvas->drawRect(
+ SkRect::MakeXYWH(histogram_bounds.left(),
+ histogram_bounds.bottom() - (i + 1) * bar_height,
+ bar_width,
+ 1),
+ paint);
+ }
+ }
+
+ // Draw FPS graph.
+ paint.setAntiAlias(true);
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setStrokeWidth(1);
+ canvas->drawPath(path, paint);
+
+ return area;
+}
+
+SkRect HeadsUpDisplayLayerImpl::DrawMemoryDisplay(SkCanvas* canvas,
+ int right,
+ int top,
+ int width) const {
+ if (!memory_entry_.bytes_total())
+ return SkRect::MakeEmpty();
+
+ const int kPadding = 4;
+ const int kFontHeight = 13;
+
+ const int height = 3 * kFontHeight + 4 * kPadding;
+ const int left = bounds().width() - width - right;
+ const SkRect area = SkRect::MakeXYWH(left, top, width, height);
+
+ const double megabyte = 1024.0 * 1024.0;
+
+ SkPaint paint = CreatePaint();
+ DrawGraphBackground(canvas, &paint, area);
+
+ SkPoint title_pos = SkPoint::Make(left + kPadding, top + kFontHeight);
+ SkPoint stat1_pos = SkPoint::Make(left + width - kPadding - 1,
+ top + kPadding + 2 * kFontHeight);
+ SkPoint stat2_pos = SkPoint::Make(left + width - kPadding - 1,
+ top + 2 * kPadding + 3 * kFontHeight);
+
+ paint.setColor(DebugColors::MemoryDisplayTextColor());
+ DrawText(canvas,
+ &paint,
+ "GPU memory",
+ SkPaint::kLeft_Align,
+ kFontHeight,
+ title_pos);
+
+ std::string text =
+ base::StringPrintf("%6.1f MB used",
+ (memory_entry_.bytes_unreleasable +
+ memory_entry_.bytes_allocated) / megabyte);
+ DrawText(canvas, &paint, text, SkPaint::kRight_Align, kFontHeight, stat1_pos);
+
+ if (memory_entry_.bytes_over) {
+ paint.setColor(SK_ColorRED);
+ text = base::StringPrintf("%6.1f MB over",
+ memory_entry_.bytes_over / megabyte);
+ } else {
+ text = base::StringPrintf("%6.1f MB max ",
+ memory_entry_.total_budget_in_bytes / megabyte);
+ }
+ DrawText(canvas, &paint, text, SkPaint::kRight_Align, kFontHeight, stat2_pos);
+
+ return area;
+}
+
+SkRect HeadsUpDisplayLayerImpl::DrawPaintTimeDisplay(
+ SkCanvas* canvas,
+ const PaintTimeCounter* paint_time_counter,
+ int right,
+ int top) const {
+ const int kPadding = 4;
+ const int kFontHeight = 15;
+
+ const int kGraphWidth = paint_time_counter->HistorySize();
+ const int kGraphHeight = 40;
+
+ const int width = kGraphWidth + 2 * kPadding;
+ const int height =
+ kFontHeight + kGraphHeight + 4 * kPadding + 2 + kFontHeight + kPadding;
+ const int left = bounds().width() - width - right;
+
+ const SkRect area = SkRect::MakeXYWH(left, top, width, height);
+
+ SkPaint paint = CreatePaint();
+ DrawGraphBackground(canvas, &paint, area);
+
+ SkRect text_bounds = SkRect::MakeXYWH(
+ left + kPadding, top + kPadding, kGraphWidth, kFontHeight);
+ SkRect text_bounds2 = SkRect::MakeXYWH(left + kPadding,
+ text_bounds.bottom() + kPadding,
+ kGraphWidth,
+ kFontHeight);
+ SkRect graph_bounds = SkRect::MakeXYWH(left + kPadding,
+ text_bounds2.bottom() + 2 * kPadding,
+ kGraphWidth,
+ kGraphHeight);
+
+ const std::string value_text =
+ base::StringPrintf("%.1f", paint_time_graph_.value);
+ const std::string min_max_text = base::StringPrintf(
+ "%.1f-%.1f", paint_time_graph_.min, paint_time_graph_.max);
+
+ paint.setColor(DebugColors::PaintTimeDisplayTextAndGraphColor());
+ DrawText(canvas,
+ &paint,
+ "Page paint time (ms)",
+ SkPaint::kLeft_Align,
+ kFontHeight,
+ text_bounds.left(),
+ text_bounds.bottom());
+ DrawText(canvas,
+ &paint,
+ value_text,
+ SkPaint::kLeft_Align,
+ kFontHeight,
+ text_bounds2.left(),
+ text_bounds2.bottom());
+ DrawText(canvas,
+ &paint,
+ min_max_text,
+ SkPaint::kRight_Align,
+ kFontHeight,
+ text_bounds2.right(),
+ text_bounds2.bottom());
+
+ paint.setColor(DebugColors::PaintTimeDisplayTextAndGraphColor());
+ for (PaintTimeCounter::RingBufferType::Iterator it =
+ paint_time_counter->End();
+ it;
+ --it) {
+ double pt = it->InMillisecondsF();
+
+ if (pt == 0.0)
+ continue;
+
+ double p = pt / paint_time_graph_.current_upper_bound;
+ if (p > 1.0)
+ p = 1.0;
+
+ canvas->drawRect(
+ SkRect::MakeXYWH(graph_bounds.left() + it.index(),
+ graph_bounds.bottom() - p * graph_bounds.height(),
+ 1,
+ p * graph_bounds.height()),
+ paint);
+ }
+
+ DrawGraphLines(canvas, &paint, graph_bounds, paint_time_graph_);
+
+ return area;
+}
+
+void HeadsUpDisplayLayerImpl::DrawDebugRects(
+ SkCanvas* canvas,
+ DebugRectHistory* debug_rect_history) const {
+ const std::vector<DebugRect>& debug_rects = debug_rect_history->debug_rects();
+ SkPaint paint = CreatePaint();
+
+ for (size_t i = 0; i < debug_rects.size(); ++i) {
+ SkColor stroke_color = 0;
+ SkColor fill_color = 0;
+ float stroke_width = 0.f;
+ std::string label_text;
+
+ switch (debug_rects[i].type) {
+ case PAINT_RECT_TYPE:
+ stroke_color = DebugColors::PaintRectBorderColor();
+ fill_color = DebugColors::PaintRectFillColor();
+ stroke_width = DebugColors::PaintRectBorderWidth();
+ break;
+ case PROPERTY_CHANGED_RECT_TYPE:
+ stroke_color = DebugColors::PropertyChangedRectBorderColor();
+ fill_color = DebugColors::PropertyChangedRectFillColor();
+ stroke_width = DebugColors::PropertyChangedRectBorderWidth();
+ break;
+ case SURFACE_DAMAGE_RECT_TYPE:
+ stroke_color = DebugColors::SurfaceDamageRectBorderColor();
+ fill_color = DebugColors::SurfaceDamageRectFillColor();
+ stroke_width = DebugColors::SurfaceDamageRectBorderWidth();
+ break;
+ case REPLICA_SCREEN_SPACE_RECT_TYPE:
+ stroke_color = DebugColors::ScreenSpaceSurfaceReplicaRectBorderColor();
+ fill_color = DebugColors::ScreenSpaceSurfaceReplicaRectFillColor();
+ stroke_width = DebugColors::ScreenSpaceSurfaceReplicaRectBorderWidth();
+ break;
+ case SCREEN_SPACE_RECT_TYPE:
+ stroke_color = DebugColors::ScreenSpaceLayerRectBorderColor();
+ fill_color = DebugColors::ScreenSpaceLayerRectFillColor();
+ stroke_width = DebugColors::ScreenSpaceLayerRectBorderWidth();
+ break;
+ case OCCLUDING_RECT_TYPE:
+ stroke_color = DebugColors::OccludingRectBorderColor();
+ fill_color = DebugColors::OccludingRectFillColor();
+ stroke_width = DebugColors::OccludingRectBorderWidth();
+ break;
+ case NONOCCLUDING_RECT_TYPE:
+ stroke_color = DebugColors::NonOccludingRectBorderColor();
+ fill_color = DebugColors::NonOccludingRectFillColor();
+ stroke_width = DebugColors::NonOccludingRectBorderWidth();
+ break;
+ case TOUCH_EVENT_HANDLER_RECT_TYPE:
+ stroke_color = DebugColors::TouchEventHandlerRectBorderColor();
+ fill_color = DebugColors::TouchEventHandlerRectFillColor();
+ stroke_width = DebugColors::TouchEventHandlerRectBorderWidth();
+ label_text = "touch event listener";
+ break;
+ case WHEEL_EVENT_HANDLER_RECT_TYPE:
+ stroke_color = DebugColors::WheelEventHandlerRectBorderColor();
+ fill_color = DebugColors::WheelEventHandlerRectFillColor();
+ stroke_width = DebugColors::WheelEventHandlerRectBorderWidth();
+ label_text = "mousewheel event listener";
+ break;
+ case NON_FAST_SCROLLABLE_RECT_TYPE:
+ stroke_color = DebugColors::NonFastScrollableRectBorderColor();
+ fill_color = DebugColors::NonFastScrollableRectFillColor();
+ stroke_width = DebugColors::NonFastScrollableRectBorderWidth();
+ label_text = "repaints on scroll";
+ break;
+ }
+
+ gfx::RectF debug_layer_rect = gfx::ScaleRect(debug_rects[i].rect,
+ 1.0 / contents_scale_x(),
+ 1.0 / contents_scale_y());
+ SkRect sk_rect = RectFToSkRect(debug_layer_rect);
+ paint.setColor(fill_color);
+ paint.setStyle(SkPaint::kFill_Style);
+ canvas->drawRect(sk_rect, paint);
+
+ paint.setColor(stroke_color);
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setStrokeWidth(SkFloatToScalar(stroke_width));
+ canvas->drawRect(sk_rect, paint);
+
+ if (label_text.length()) {
+ const int kFontHeight = 12;
+ const int kPadding = 3;
+
+ canvas->save();
+ canvas->clipRect(sk_rect);
+ canvas->translate(sk_rect.x(), sk_rect.y());
+
+ SkPaint label_paint = CreatePaint();
+ label_paint.setTextSize(kFontHeight);
+ label_paint.setTypeface(typeface_.get());
+ label_paint.setColor(stroke_color);
+
+ const SkScalar label_text_width =
+ label_paint.measureText(label_text.c_str(), label_text.length());
+ canvas->drawRect(SkRect::MakeWH(label_text_width + 2 * kPadding,
+ kFontHeight + 2 * kPadding),
+ label_paint);
+
+ label_paint.setAntiAlias(true);
+ label_paint.setColor(SkColorSetARGB(255, 50, 50, 50));
+ canvas->drawText(label_text.c_str(),
+ label_text.length(),
+ kPadding,
+ kFontHeight * 0.8f + kPadding,
+ label_paint);
+
+ canvas->restore();
+ }
+ }
+}
+
+const char* HeadsUpDisplayLayerImpl::LayerTypeAsString() const {
+ return "cc::HeadsUpDisplayLayerImpl";
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/heads_up_display_layer_impl.h b/chromium/cc/layers/heads_up_display_layer_impl.h
new file mode 100644
index 00000000000..32bf77e143d
--- /dev/null
+++ b/chromium/cc/layers/heads_up_display_layer_impl.h
@@ -0,0 +1,127 @@
+// 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.
+
+#ifndef CC_LAYERS_HEADS_UP_DISPLAY_LAYER_IMPL_H_
+#define CC_LAYERS_HEADS_UP_DISPLAY_LAYER_IMPL_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "cc/base/cc_export.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/resources/memory_history.h"
+#include "cc/resources/scoped_resource.h"
+
+class SkCanvas;
+class SkPaint;
+class SkTypeface;
+struct SkRect;
+
+namespace cc {
+
+class DebugRectHistory;
+class FrameRateCounter;
+class PaintTimeCounter;
+
+class CC_EXPORT HeadsUpDisplayLayerImpl : public LayerImpl {
+ public:
+ static scoped_ptr<HeadsUpDisplayLayerImpl> Create(LayerTreeImpl* tree_impl,
+ int id) {
+ return make_scoped_ptr(new HeadsUpDisplayLayerImpl(tree_impl, id));
+ }
+ virtual ~HeadsUpDisplayLayerImpl();
+
+ virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl)
+ OVERRIDE;
+
+ virtual bool WillDraw(DrawMode draw_mode,
+ ResourceProvider* resource_provider) OVERRIDE;
+ virtual void AppendQuads(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data) OVERRIDE;
+ void UpdateHudTexture(ResourceProvider* resource_provider);
+
+ virtual void DidLoseOutputSurface() OVERRIDE;
+
+ virtual bool LayerIsAlwaysDamaged() const OVERRIDE;
+
+ private:
+ class Graph {
+ public:
+ Graph(double indicator_value, double start_upper_bound);
+
+ // Eases the upper bound, which limits what is currently visible in the
+ // graph, so that the graph always scales to either it's max or
+ // default_upper_bound.
+ double UpdateUpperBound();
+
+ double value;
+ double min;
+ double max;
+
+ double current_upper_bound;
+ const double default_upper_bound;
+ const double indicator;
+ };
+
+ HeadsUpDisplayLayerImpl(LayerTreeImpl* tree_impl, int id);
+
+ virtual const char* LayerTypeAsString() const OVERRIDE;
+
+ void UpdateHudContents();
+ void DrawHudContents(SkCanvas* canvas) const;
+
+ void DrawText(SkCanvas* canvas,
+ SkPaint* paint,
+ const std::string& text,
+ SkPaint::Align align,
+ int size,
+ int x,
+ int y) const;
+ void DrawText(SkCanvas* canvas,
+ SkPaint* paint,
+ const std::string& text,
+ SkPaint::Align align,
+ int size,
+ const SkPoint& pos) const;
+ void DrawGraphBackground(SkCanvas* canvas,
+ SkPaint* paint,
+ const SkRect& bounds) const;
+ void DrawGraphLines(SkCanvas* canvas,
+ SkPaint* paint,
+ const SkRect& bounds,
+ const Graph& graph) const;
+
+ SkRect DrawFPSDisplay(SkCanvas* canvas,
+ const FrameRateCounter* fps_counter,
+ int right,
+ int top) const;
+ SkRect DrawMemoryDisplay(SkCanvas* canvas,
+ int top,
+ int right,
+ int width) const;
+ SkRect DrawPaintTimeDisplay(SkCanvas* canvas,
+ const PaintTimeCounter* paint_time_counter,
+ int top,
+ int right) const;
+ void DrawDebugRects(SkCanvas* canvas,
+ DebugRectHistory* debug_rect_history) const;
+
+ scoped_ptr<ScopedResource> hud_resource_;
+ scoped_ptr<SkCanvas> hud_canvas_;
+
+ skia::RefPtr<SkTypeface> typeface_;
+
+ Graph fps_graph_;
+ Graph paint_time_graph_;
+ MemoryHistory::Entry memory_entry_;
+
+ base::TimeTicks time_of_last_graph_update_;
+
+ DISALLOW_COPY_AND_ASSIGN(HeadsUpDisplayLayerImpl);
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_HEADS_UP_DISPLAY_LAYER_IMPL_H_
diff --git a/chromium/cc/layers/heads_up_display_unittest.cc b/chromium/cc/layers/heads_up_display_unittest.cc
new file mode 100644
index 00000000000..b9bd5db266a
--- /dev/null
+++ b/chromium/cc/layers/heads_up_display_unittest.cc
@@ -0,0 +1,100 @@
+// 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 "cc/layers/heads_up_display_layer.h"
+#include "cc/layers/layer.h"
+#include "cc/test/layer_tree_test.h"
+#include "cc/trees/layer_tree_host.h"
+
+namespace cc {
+namespace {
+
+class HeadsUpDisplayTest : public LayerTreeTest {
+ protected:
+ virtual void InitializeSettings(LayerTreeSettings* settings) OVERRIDE {
+ // Enable the HUD without requiring text.
+ settings->initial_debug_state.show_property_changed_rects = true;
+ }
+};
+
+class DrawsContentLayer : public Layer {
+ public:
+ static scoped_refptr<DrawsContentLayer> Create() {
+ return make_scoped_refptr(new DrawsContentLayer());
+ }
+ virtual bool DrawsContent() const OVERRIDE { return true; }
+
+ private:
+ DrawsContentLayer() : Layer() {}
+ virtual ~DrawsContentLayer() {}
+};
+
+class HudWithRootLayerChange : public HeadsUpDisplayTest {
+ public:
+ HudWithRootLayerChange()
+ : root_layer1_(DrawsContentLayer::Create()),
+ root_layer2_(DrawsContentLayer::Create()),
+ num_commits_(0) {}
+
+ virtual void BeginTest() OVERRIDE {
+ root_layer1_->SetBounds(gfx::Size(30, 30));
+ root_layer2_->SetBounds(gfx::Size(30, 30));
+
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommit() OVERRIDE {
+ ++num_commits_;
+
+ ASSERT_TRUE(layer_tree_host()->hud_layer());
+
+ switch (num_commits_) {
+ case 1:
+ // Change directly to a new root layer.
+ layer_tree_host()->SetRootLayer(root_layer1_);
+ break;
+ case 2:
+ EXPECT_EQ(root_layer1_.get(), layer_tree_host()->hud_layer()->parent());
+ // Unset the root layer.
+ layer_tree_host()->SetRootLayer(NULL);
+ break;
+ case 3:
+ EXPECT_EQ(0, layer_tree_host()->hud_layer()->parent());
+ // Change back to the previous root layer.
+ layer_tree_host()->SetRootLayer(root_layer1_);
+ break;
+ case 4:
+ EXPECT_EQ(root_layer1_.get(), layer_tree_host()->hud_layer()->parent());
+ // Unset the root layer.
+ layer_tree_host()->SetRootLayer(NULL);
+ break;
+ case 5:
+ EXPECT_EQ(0, layer_tree_host()->hud_layer()->parent());
+ // Change to a new root layer from a null root.
+ layer_tree_host()->SetRootLayer(root_layer2_);
+ break;
+ case 6:
+ EXPECT_EQ(root_layer2_.get(), layer_tree_host()->hud_layer()->parent());
+ // Change directly back to the last root layer/
+ layer_tree_host()->SetRootLayer(root_layer1_);
+ break;
+ case 7:
+ EXPECT_EQ(root_layer1_.get(), layer_tree_host()->hud_layer()->parent());
+ EndTest();
+ break;
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ scoped_refptr<DrawsContentLayer> root_layer1_;
+ scoped_refptr<DrawsContentLayer> root_layer2_;
+ int num_commits_;
+};
+
+MULTI_THREAD_TEST_F(HudWithRootLayerChange);
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/layers/image_layer.cc b/chromium/cc/layers/image_layer.cc
new file mode 100644
index 00000000000..8ba1908131d
--- /dev/null
+++ b/chromium/cc/layers/image_layer.cc
@@ -0,0 +1,96 @@
+// Copyright 2010 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 "cc/layers/image_layer.h"
+
+#include "base/compiler_specific.h"
+#include "cc/resources/image_layer_updater.h"
+#include "cc/resources/layer_updater.h"
+#include "cc/resources/prioritized_resource.h"
+#include "cc/resources/resource_update_queue.h"
+#include "cc/trees/layer_tree_host.h"
+
+namespace cc {
+
+scoped_refptr<ImageLayer> ImageLayer::Create() {
+ return make_scoped_refptr(new ImageLayer());
+}
+
+ImageLayer::ImageLayer() : TiledLayer() {}
+
+ImageLayer::~ImageLayer() {}
+
+void ImageLayer::SetBitmap(const SkBitmap& bitmap) {
+ // SetBitmap() currently gets called whenever there is any
+ // style change that affects the layer even if that change doesn't
+ // affect the actual contents of the image (e.g. a CSS animation).
+ // With this check in place we avoid unecessary texture uploads.
+ if (bitmap.pixelRef() && bitmap.pixelRef() == bitmap_.pixelRef())
+ return;
+
+ bitmap_ = bitmap;
+ SetNeedsDisplay();
+}
+
+void ImageLayer::SetTexturePriorities(const PriorityCalculator& priority_calc) {
+ // Update the tile data before creating all the layer's tiles.
+ UpdateTileSizeAndTilingOption();
+
+ TiledLayer::SetTexturePriorities(priority_calc);
+}
+
+bool ImageLayer::Update(ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion) {
+ CreateUpdaterIfNeeded();
+ if (!updater_->UsingBitmap(bitmap_)) {
+ updater_->SetBitmap(bitmap_);
+ UpdateTileSizeAndTilingOption();
+ InvalidateContentRect(gfx::Rect(content_bounds()));
+ }
+ return TiledLayer::Update(queue, occlusion);
+}
+
+void ImageLayer::CreateUpdaterIfNeeded() {
+ if (updater_.get())
+ return;
+
+ updater_ = ImageLayerUpdater::Create();
+ GLenum texture_format =
+ layer_tree_host()->GetRendererCapabilities().best_texture_format;
+ SetTextureFormat(texture_format);
+}
+
+LayerUpdater* ImageLayer::Updater() const {
+ return updater_.get();
+}
+
+void ImageLayer::CalculateContentsScale(float ideal_contents_scale,
+ float device_scale_factor,
+ float page_scale_factor,
+ bool animating_transform_to_screen,
+ float* contents_scale_x,
+ float* contents_scale_y,
+ gfx::Size* content_bounds) {
+ *contents_scale_x = ImageContentsScaleX();
+ *contents_scale_y = ImageContentsScaleY();
+ *content_bounds = gfx::Size(bitmap_.width(), bitmap_.height());
+}
+
+bool ImageLayer::DrawsContent() const {
+ return !bitmap_.isNull() && TiledLayer::DrawsContent();
+}
+
+float ImageLayer::ImageContentsScaleX() const {
+ if (bounds().IsEmpty() || bitmap_.width() == 0)
+ return 1;
+ return static_cast<float>(bitmap_.width()) / bounds().width();
+}
+
+float ImageLayer::ImageContentsScaleY() const {
+ if (bounds().IsEmpty() || bitmap_.height() == 0)
+ return 1;
+ return static_cast<float>(bitmap_.height()) / bounds().height();
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/image_layer.h b/chromium/cc/layers/image_layer.h
new file mode 100644
index 00000000000..ae8383a22c3
--- /dev/null
+++ b/chromium/cc/layers/image_layer.h
@@ -0,0 +1,57 @@
+// Copyright 2010 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.
+
+#ifndef CC_LAYERS_IMAGE_LAYER_H_
+#define CC_LAYERS_IMAGE_LAYER_H_
+
+#include "cc/base/cc_export.h"
+#include "cc/layers/content_layer.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+namespace cc {
+
+class ImageLayerUpdater;
+
+// A Layer that contains only an Image element.
+class CC_EXPORT ImageLayer : public TiledLayer {
+ public:
+ static scoped_refptr<ImageLayer> Create();
+
+ // Layer implementation.
+ virtual bool DrawsContent() const OVERRIDE;
+ virtual void SetTexturePriorities(const PriorityCalculator& priority_calc)
+ OVERRIDE;
+ virtual bool Update(ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion) OVERRIDE;
+ virtual void CalculateContentsScale(float ideal_contents_scale,
+ float device_scale_factor,
+ float page_scale_factor,
+ bool animating_transform_to_screen,
+ float* contents_scale_x,
+ float* contents_scale_y,
+ gfx::Size* content_bounds) OVERRIDE;
+
+ void SetBitmap(const SkBitmap& image);
+
+ private:
+ ImageLayer();
+ virtual ~ImageLayer();
+
+ // TiledLayer Implementation.
+ virtual LayerUpdater* Updater() const OVERRIDE;
+ virtual void CreateUpdaterIfNeeded() OVERRIDE;
+
+ float ImageContentsScaleX() const;
+ float ImageContentsScaleY() const;
+
+ SkBitmap bitmap_;
+
+ scoped_refptr<ImageLayerUpdater> updater_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImageLayer);
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_IMAGE_LAYER_H_
diff --git a/chromium/cc/layers/io_surface_layer.cc b/chromium/cc/layers/io_surface_layer.cc
new file mode 100644
index 00000000000..1e945150a0c
--- /dev/null
+++ b/chromium/cc/layers/io_surface_layer.cc
@@ -0,0 +1,52 @@
+// 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 "cc/layers/io_surface_layer.h"
+
+#include "cc/layers/io_surface_layer_impl.h"
+
+namespace cc {
+
+scoped_refptr<IOSurfaceLayer> IOSurfaceLayer::Create() {
+ return make_scoped_refptr(new IOSurfaceLayer());
+}
+
+IOSurfaceLayer::IOSurfaceLayer() : Layer(), io_surface_id_(0) {}
+
+IOSurfaceLayer::~IOSurfaceLayer() {}
+
+void IOSurfaceLayer::SetIOSurfaceProperties(uint32_t io_surface_id,
+ gfx::Size size) {
+ io_surface_id_ = io_surface_id;
+ io_surface_size_ = size;
+ SetNeedsCommit();
+}
+
+scoped_ptr<LayerImpl> IOSurfaceLayer::CreateLayerImpl(
+ LayerTreeImpl* tree_impl) {
+ return IOSurfaceLayerImpl::Create(tree_impl, layer_id_).PassAs<LayerImpl>();
+}
+
+bool IOSurfaceLayer::DrawsContent() const {
+ return io_surface_id_ && Layer::DrawsContent();
+}
+
+void IOSurfaceLayer::PushPropertiesTo(LayerImpl* layer) {
+ Layer::PushPropertiesTo(layer);
+
+ IOSurfaceLayerImpl* io_surface_layer =
+ static_cast<IOSurfaceLayerImpl*>(layer);
+ io_surface_layer->SetIOSurfaceProperties(io_surface_id_, io_surface_size_);
+}
+
+bool IOSurfaceLayer::Update(ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion) {
+ bool updated = Layer::Update(queue, occlusion);
+
+ // This layer doesn't update any resources from the main thread side,
+ // but repaint rects need to be sent to the layer impl via commit.
+ return updated || !update_rect_.IsEmpty();
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/io_surface_layer.h b/chromium/cc/layers/io_surface_layer.h
new file mode 100644
index 00000000000..e2e831a7e87
--- /dev/null
+++ b/chromium/cc/layers/io_surface_layer.h
@@ -0,0 +1,39 @@
+// 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.
+
+#ifndef CC_LAYERS_IO_SURFACE_LAYER_H_
+#define CC_LAYERS_IO_SURFACE_LAYER_H_
+
+#include "cc/base/cc_export.h"
+#include "cc/layers/layer.h"
+
+namespace cc {
+
+class CC_EXPORT IOSurfaceLayer : public Layer {
+ public:
+ static scoped_refptr<IOSurfaceLayer> Create();
+
+ void SetIOSurfaceProperties(uint32_t io_surface_id, gfx::Size size);
+
+ virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl)
+ OVERRIDE;
+ virtual bool DrawsContent() const OVERRIDE;
+ virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE;
+ virtual bool Update(ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion) OVERRIDE;
+
+ protected:
+ IOSurfaceLayer();
+
+ private:
+ virtual ~IOSurfaceLayer();
+
+ uint32_t io_surface_id_;
+ gfx::Size io_surface_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(IOSurfaceLayer);
+};
+
+} // namespace cc
+#endif // CC_LAYERS_IO_SURFACE_LAYER_H_
diff --git a/chromium/cc/layers/io_surface_layer_impl.cc b/chromium/cc/layers/io_surface_layer_impl.cc
new file mode 100644
index 00000000000..f8577a37765
--- /dev/null
+++ b/chromium/cc/layers/io_surface_layer_impl.cc
@@ -0,0 +1,145 @@
+// 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 "cc/layers/io_surface_layer_impl.h"
+
+#include "base/strings/stringprintf.h"
+#include "cc/layers/quad_sink.h"
+#include "cc/output/gl_renderer.h" // For the GLC() macro.
+#include "cc/output/output_surface.h"
+#include "cc/quads/io_surface_draw_quad.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "gpu/GLES2/gl2extchromium.h"
+#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "third_party/khronos/GLES2/gl2ext.h"
+
+namespace cc {
+
+IOSurfaceLayerImpl::IOSurfaceLayerImpl(LayerTreeImpl* tree_impl, int id)
+ : LayerImpl(tree_impl, id),
+ io_surface_id_(0),
+ io_surface_changed_(false),
+ io_surface_texture_id_(0) {}
+
+IOSurfaceLayerImpl::~IOSurfaceLayerImpl() {
+ if (!io_surface_texture_id_)
+ return;
+
+ DestroyTexture();
+}
+
+void IOSurfaceLayerImpl::DestroyTexture() {
+ if (io_surface_resource_id_) {
+ ResourceProvider* resource_provider =
+ layer_tree_impl()->resource_provider();
+ resource_provider->DeleteResource(io_surface_resource_id_);
+ io_surface_resource_id_ = 0;
+ }
+
+ if (io_surface_texture_id_) {
+ OutputSurface* output_surface = layer_tree_impl()->output_surface();
+ // TODO(skaslev): Implement this path for software compositing.
+ WebKit::WebGraphicsContext3D* context3d = output_surface->context3d();
+ if (context3d)
+ context3d->deleteTexture(io_surface_texture_id_);
+ io_surface_texture_id_ = 0;
+ }
+}
+
+scoped_ptr<LayerImpl> IOSurfaceLayerImpl::CreateLayerImpl(
+ LayerTreeImpl* tree_impl) {
+ return IOSurfaceLayerImpl::Create(tree_impl, id()).PassAs<LayerImpl>();
+}
+
+void IOSurfaceLayerImpl::PushPropertiesTo(LayerImpl* layer) {
+ LayerImpl::PushPropertiesTo(layer);
+
+ IOSurfaceLayerImpl* io_surface_layer =
+ static_cast<IOSurfaceLayerImpl*>(layer);
+ io_surface_layer->SetIOSurfaceProperties(io_surface_id_, io_surface_size_);
+}
+
+bool IOSurfaceLayerImpl::WillDraw(DrawMode draw_mode,
+ ResourceProvider* resource_provider) {
+ if (draw_mode == DRAW_MODE_RESOURCELESS_SOFTWARE)
+ return false;
+
+ if (io_surface_changed_) {
+ WebKit::WebGraphicsContext3D* context3d =
+ resource_provider->GraphicsContext3D();
+ if (!context3d) {
+ // TODO(skaslev): Implement this path for software compositing.
+ return false;
+ }
+
+ // TODO(ernstm): Do this in a way that we can track memory usage.
+ if (!io_surface_texture_id_) {
+ io_surface_texture_id_ = context3d->createTexture();
+ io_surface_resource_id_ =
+ resource_provider->CreateResourceFromExternalTexture(
+ GL_TEXTURE_RECTANGLE_ARB,
+ io_surface_texture_id_);
+ }
+
+ GLC(context3d,
+ context3d->bindTexture(GL_TEXTURE_RECTANGLE_ARB,
+ io_surface_texture_id_));
+ context3d->texImageIOSurface2DCHROMIUM(GL_TEXTURE_RECTANGLE_ARB,
+ io_surface_size_.width(),
+ io_surface_size_.height(),
+ io_surface_id_,
+ 0);
+ // Do not check for error conditions. texImageIOSurface2DCHROMIUM() is
+ // supposed to hold on to the last good IOSurface if the new one is already
+ // closed. This is only a possibility during live resizing of plugins.
+ // However, it seems that this is not sufficient to completely guard against
+ // garbage being drawn. If this is found to be a significant issue, it may
+ // be necessary to explicitly tell the embedder when to free the surfaces it
+ // has allocated.
+ io_surface_changed_ = false;
+ }
+
+ return LayerImpl::WillDraw(draw_mode, resource_provider);
+}
+
+void IOSurfaceLayerImpl::AppendQuads(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data) {
+ SharedQuadState* shared_quad_state =
+ quad_sink->UseSharedQuadState(CreateSharedQuadState());
+ AppendDebugBorderQuad(quad_sink, shared_quad_state, append_quads_data);
+
+ gfx::Rect quad_rect(content_bounds());
+ gfx::Rect opaque_rect(contents_opaque() ? quad_rect : gfx::Rect());
+ scoped_ptr<IOSurfaceDrawQuad> quad = IOSurfaceDrawQuad::Create();
+ quad->SetNew(shared_quad_state,
+ quad_rect,
+ opaque_rect,
+ io_surface_size_,
+ io_surface_resource_id_,
+ IOSurfaceDrawQuad::FLIPPED);
+ quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data);
+}
+
+void IOSurfaceLayerImpl::DidLoseOutputSurface() {
+ // We don't have a valid texture ID in the new context; however,
+ // the IOSurface is still valid.
+ DestroyTexture();
+ io_surface_changed_ = true;
+}
+
+void IOSurfaceLayerImpl::SetIOSurfaceProperties(unsigned io_surface_id,
+ gfx::Size size) {
+ if (io_surface_id_ != io_surface_id)
+ io_surface_changed_ = true;
+
+ io_surface_id_ = io_surface_id;
+ io_surface_size_ = size;
+}
+
+const char* IOSurfaceLayerImpl::LayerTypeAsString() const {
+ return "cc::IOSurfaceLayerImpl";
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/io_surface_layer_impl.h b/chromium/cc/layers/io_surface_layer_impl.h
new file mode 100644
index 00000000000..356f5f299a4
--- /dev/null
+++ b/chromium/cc/layers/io_surface_layer_impl.h
@@ -0,0 +1,55 @@
+// 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.
+
+#ifndef CC_LAYERS_IO_SURFACE_LAYER_IMPL_H_
+#define CC_LAYERS_IO_SURFACE_LAYER_IMPL_H_
+
+#include <string>
+
+#include "cc/base/cc_export.h"
+#include "cc/layers/layer_impl.h"
+#include "ui/gfx/size.h"
+
+namespace cc {
+
+class CC_EXPORT IOSurfaceLayerImpl : public LayerImpl {
+ public:
+ static scoped_ptr<IOSurfaceLayerImpl> Create(LayerTreeImpl* tree_impl,
+ int id) {
+ return make_scoped_ptr(new IOSurfaceLayerImpl(tree_impl, id));
+ }
+ virtual ~IOSurfaceLayerImpl();
+
+ void SetIOSurfaceProperties(unsigned io_surface_id, gfx::Size size);
+
+ virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl)
+ OVERRIDE;
+ virtual void PushPropertiesTo(LayerImpl* layer_tree_impl) OVERRIDE;
+
+ virtual void AppendQuads(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data) OVERRIDE;
+
+ virtual bool WillDraw(DrawMode draw_mode,
+ ResourceProvider* resource_provider) OVERRIDE;
+ virtual void DidLoseOutputSurface() OVERRIDE;
+
+ private:
+ IOSurfaceLayerImpl(LayerTreeImpl* tree_impl, int id);
+
+ void DestroyTexture();
+
+ virtual const char* LayerTypeAsString() const OVERRIDE;
+
+ unsigned io_surface_id_;
+ gfx::Size io_surface_size_;
+ bool io_surface_changed_;
+ unsigned io_surface_texture_id_;
+ unsigned io_surface_resource_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(IOSurfaceLayerImpl);
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_IO_SURFACE_LAYER_IMPL_H_
diff --git a/chromium/cc/layers/layer.cc b/chromium/cc/layers/layer.cc
new file mode 100644
index 00000000000..aa4a002dff4
--- /dev/null
+++ b/chromium/cc/layers/layer.cc
@@ -0,0 +1,982 @@
+// Copyright 2010 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 "cc/layers/layer.h"
+
+#include <algorithm>
+
+#include "base/location.h"
+#include "base/metrics/histogram.h"
+#include "base/single_thread_task_runner.h"
+#include "cc/animation/animation.h"
+#include "cc/animation/animation_events.h"
+#include "cc/animation/layer_animation_controller.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/output/copy_output_request.h"
+#include "cc/output/copy_output_result.h"
+#include "cc/trees/layer_tree_host.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "third_party/skia/include/core/SkImageFilter.h"
+#include "ui/gfx/rect_conversions.h"
+
+namespace cc {
+
+static int s_next_layer_id = 1;
+
+scoped_refptr<Layer> Layer::Create() {
+ return make_scoped_refptr(new Layer());
+}
+
+Layer::Layer()
+ : needs_push_properties_(false),
+ num_dependents_need_push_properties_(false),
+ stacking_order_changed_(false),
+ layer_id_(s_next_layer_id++),
+ ignore_set_needs_commit_(false),
+ parent_(NULL),
+ layer_tree_host_(NULL),
+ scrollable_(false),
+ should_scroll_on_main_thread_(false),
+ have_wheel_event_handlers_(false),
+ anchor_point_(0.5f, 0.5f),
+ background_color_(0),
+ compositing_reasons_(kCompositingReasonUnknown),
+ opacity_(1.f),
+ anchor_point_z_(0.f),
+ is_container_for_fixed_position_layers_(false),
+ is_drawable_(false),
+ hide_layer_and_subtree_(false),
+ masks_to_bounds_(false),
+ contents_opaque_(false),
+ double_sided_(true),
+ preserves_3d_(false),
+ use_parent_backface_visibility_(false),
+ draw_checkerboard_for_missing_tiles_(false),
+ force_render_surface_(false),
+ replica_layer_(NULL),
+ raster_scale_(0.f) {
+ if (layer_id_ < 0) {
+ s_next_layer_id = 1;
+ layer_id_ = s_next_layer_id++;
+ }
+
+ layer_animation_controller_ = LayerAnimationController::Create(layer_id_);
+ layer_animation_controller_->AddValueObserver(this);
+}
+
+Layer::~Layer() {
+ // Our parent should be holding a reference to us so there should be no
+ // way for us to be destroyed while we still have a parent.
+ DCHECK(!parent());
+ // Similarly we shouldn't have a layer tree host since it also keeps a
+ // reference to us.
+ DCHECK(!layer_tree_host());
+
+ layer_animation_controller_->RemoveValueObserver(this);
+
+ // Remove the parent reference from all children and dependents.
+ RemoveAllChildren();
+ if (mask_layer_.get()) {
+ DCHECK_EQ(this, mask_layer_->parent());
+ mask_layer_->RemoveFromParent();
+ }
+ if (replica_layer_.get()) {
+ DCHECK_EQ(this, replica_layer_->parent());
+ replica_layer_->RemoveFromParent();
+ }
+}
+
+void Layer::SetLayerTreeHost(LayerTreeHost* host) {
+ if (layer_tree_host_ == host)
+ return;
+
+ layer_tree_host_ = host;
+
+ // When changing hosts, the layer needs to commit its properties to the impl
+ // side for the new host.
+ SetNeedsPushProperties();
+
+ for (size_t i = 0; i < children_.size(); ++i)
+ children_[i]->SetLayerTreeHost(host);
+
+ if (mask_layer_.get())
+ mask_layer_->SetLayerTreeHost(host);
+ if (replica_layer_.get())
+ replica_layer_->SetLayerTreeHost(host);
+
+ if (host) {
+ layer_animation_controller_->SetAnimationRegistrar(
+ host->animation_registrar());
+
+ if (host->settings().layer_transforms_should_scale_layer_contents)
+ reset_raster_scale_to_unknown();
+ }
+
+ if (host && layer_animation_controller_->has_any_animation())
+ host->SetNeedsCommit();
+ if (host &&
+ (!filters_.IsEmpty() || !background_filters_.IsEmpty() || filter_))
+ layer_tree_host_->set_needs_filter_context();
+}
+
+void Layer::SetNeedsUpdate() {
+ if (layer_tree_host_ && !ignore_set_needs_commit_)
+ layer_tree_host_->SetNeedsUpdateLayers();
+}
+
+void Layer::SetNeedsCommit() {
+ if (!layer_tree_host_)
+ return;
+
+ SetNeedsPushProperties();
+
+ if (ignore_set_needs_commit_)
+ return;
+
+ layer_tree_host_->SetNeedsCommit();
+}
+
+void Layer::SetNeedsFullTreeSync() {
+ if (!layer_tree_host_)
+ return;
+
+ layer_tree_host_->SetNeedsFullTreeSync();
+}
+
+bool Layer::IsPropertyChangeAllowed() const {
+ if (!layer_tree_host_)
+ return true;
+
+ if (!layer_tree_host_->settings().strict_layer_property_change_checking)
+ return true;
+
+ return !layer_tree_host_->in_paint_layer_contents();
+}
+
+void Layer::SetNeedsPushProperties() {
+ if (needs_push_properties_)
+ return;
+ if (!parent_should_know_need_push_properties() && parent_)
+ parent_->AddDependentNeedsPushProperties();
+ needs_push_properties_ = true;
+}
+
+void Layer::AddDependentNeedsPushProperties() {
+ DCHECK_GE(num_dependents_need_push_properties_, 0);
+
+ if (!parent_should_know_need_push_properties() && parent_)
+ parent_->AddDependentNeedsPushProperties();
+
+ num_dependents_need_push_properties_++;
+}
+
+void Layer::RemoveDependentNeedsPushProperties() {
+ num_dependents_need_push_properties_--;
+ DCHECK_GE(num_dependents_need_push_properties_, 0);
+
+ if (!parent_should_know_need_push_properties() && parent_)
+ parent_->RemoveDependentNeedsPushProperties();
+}
+
+gfx::Rect Layer::LayerRectToContentRect(const gfx::RectF& layer_rect) const {
+ gfx::RectF content_rect =
+ gfx::ScaleRect(layer_rect, contents_scale_x(), contents_scale_y());
+ // Intersect with content rect to avoid the extra pixel because for some
+ // values x and y, ceil((x / y) * y) may be x + 1.
+ content_rect.Intersect(gfx::Rect(content_bounds()));
+ return gfx::ToEnclosingRect(content_rect);
+}
+
+bool Layer::BlocksPendingCommit() const {
+ return false;
+}
+
+bool Layer::CanClipSelf() const {
+ return false;
+}
+
+bool Layer::BlocksPendingCommitRecursive() const {
+ if (BlocksPendingCommit())
+ return true;
+ if (mask_layer() && mask_layer()->BlocksPendingCommitRecursive())
+ return true;
+ if (replica_layer() && replica_layer()->BlocksPendingCommitRecursive())
+ return true;
+ for (size_t i = 0; i < children_.size(); ++i) {
+ if (children_[i]->BlocksPendingCommitRecursive())
+ return true;
+ }
+ return false;
+}
+
+void Layer::SetParent(Layer* layer) {
+ DCHECK(!layer || !layer->HasAncestor(this));
+
+ if (parent_should_know_need_push_properties()) {
+ if (parent_)
+ parent_->RemoveDependentNeedsPushProperties();
+ if (layer)
+ layer->AddDependentNeedsPushProperties();
+ }
+
+ parent_ = layer;
+ SetLayerTreeHost(parent_ ? parent_->layer_tree_host() : NULL);
+
+ if (!layer_tree_host_)
+ return;
+ const LayerTreeSettings& settings = layer_tree_host_->settings();
+ if (!settings.layer_transforms_should_scale_layer_contents)
+ return;
+
+ reset_raster_scale_to_unknown();
+ if (mask_layer_.get())
+ mask_layer_->reset_raster_scale_to_unknown();
+ if (replica_layer_.get() && replica_layer_->mask_layer_.get())
+ replica_layer_->mask_layer_->reset_raster_scale_to_unknown();
+}
+
+void Layer::AddChild(scoped_refptr<Layer> child) {
+ InsertChild(child, children_.size());
+}
+
+void Layer::InsertChild(scoped_refptr<Layer> child, size_t index) {
+ DCHECK(IsPropertyChangeAllowed());
+ child->RemoveFromParent();
+ child->SetParent(this);
+ child->stacking_order_changed_ = true;
+
+ index = std::min(index, children_.size());
+ children_.insert(children_.begin() + index, child);
+ SetNeedsFullTreeSync();
+}
+
+void Layer::RemoveFromParent() {
+ DCHECK(IsPropertyChangeAllowed());
+ if (parent_)
+ parent_->RemoveChildOrDependent(this);
+}
+
+void Layer::RemoveChildOrDependent(Layer* child) {
+ if (mask_layer_.get() == child) {
+ mask_layer_->SetParent(NULL);
+ mask_layer_ = NULL;
+ SetNeedsFullTreeSync();
+ return;
+ }
+ if (replica_layer_.get() == child) {
+ replica_layer_->SetParent(NULL);
+ replica_layer_ = NULL;
+ SetNeedsFullTreeSync();
+ return;
+ }
+
+ for (LayerList::iterator iter = children_.begin();
+ iter != children_.end();
+ ++iter) {
+ if (iter->get() != child)
+ continue;
+
+ child->SetParent(NULL);
+ children_.erase(iter);
+ SetNeedsFullTreeSync();
+ return;
+ }
+}
+
+void Layer::ReplaceChild(Layer* reference, scoped_refptr<Layer> new_layer) {
+ DCHECK(reference);
+ DCHECK_EQ(reference->parent(), this);
+ DCHECK(IsPropertyChangeAllowed());
+
+ if (reference == new_layer.get())
+ return;
+
+ int reference_index = IndexOfChild(reference);
+ if (reference_index == -1) {
+ NOTREACHED();
+ return;
+ }
+
+ reference->RemoveFromParent();
+
+ if (new_layer.get()) {
+ new_layer->RemoveFromParent();
+ InsertChild(new_layer, reference_index);
+ }
+}
+
+int Layer::IndexOfChild(const Layer* reference) {
+ for (size_t i = 0; i < children_.size(); ++i) {
+ if (children_[i].get() == reference)
+ return i;
+ }
+ return -1;
+}
+
+void Layer::SetBounds(gfx::Size size) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (bounds() == size)
+ return;
+
+ bounds_ = size;
+ SetNeedsCommit();
+}
+
+Layer* Layer::RootLayer() {
+ Layer* layer = this;
+ while (layer->parent())
+ layer = layer->parent();
+ return layer;
+}
+
+void Layer::RemoveAllChildren() {
+ DCHECK(IsPropertyChangeAllowed());
+ while (children_.size()) {
+ Layer* layer = children_[0].get();
+ DCHECK_EQ(this, layer->parent());
+ layer->RemoveFromParent();
+ }
+}
+
+void Layer::SetChildren(const LayerList& children) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (children == children_)
+ return;
+
+ RemoveAllChildren();
+ for (size_t i = 0; i < children.size(); ++i)
+ AddChild(children[i]);
+}
+
+bool Layer::HasAncestor(const Layer* ancestor) const {
+ for (const Layer* layer = parent(); layer; layer = layer->parent()) {
+ if (layer == ancestor)
+ return true;
+ }
+ return false;
+}
+
+void Layer::RequestCopyOfOutput(
+ scoped_ptr<CopyOutputRequest> request) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (request->IsEmpty())
+ return;
+ copy_requests_.push_back(request.Pass());
+ SetNeedsCommit();
+}
+
+void Layer::SetAnchorPoint(gfx::PointF anchor_point) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (anchor_point_ == anchor_point)
+ return;
+ anchor_point_ = anchor_point;
+ SetNeedsCommit();
+}
+
+void Layer::SetAnchorPointZ(float anchor_point_z) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (anchor_point_z_ == anchor_point_z)
+ return;
+ anchor_point_z_ = anchor_point_z;
+ SetNeedsCommit();
+}
+
+void Layer::SetBackgroundColor(SkColor background_color) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (background_color_ == background_color)
+ return;
+ background_color_ = background_color;
+ SetNeedsCommit();
+}
+
+SkColor Layer::SafeOpaqueBackgroundColor() const {
+ SkColor color = background_color();
+ if (SkColorGetA(color) == 255 && !contents_opaque()) {
+ color = SK_ColorTRANSPARENT;
+ } else if (SkColorGetA(color) != 255 && contents_opaque()) {
+ for (const Layer* layer = parent(); layer;
+ layer = layer->parent()) {
+ color = layer->background_color();
+ if (SkColorGetA(color) == 255)
+ break;
+ }
+ if (SkColorGetA(color) != 255)
+ color = layer_tree_host_->background_color();
+ if (SkColorGetA(color) != 255)
+ color = SkColorSetA(color, 255);
+ }
+ return color;
+}
+
+void Layer::CalculateContentsScale(
+ float ideal_contents_scale,
+ float device_scale_factor,
+ float page_scale_factor,
+ bool animating_transform_to_screen,
+ float* contents_scale_x,
+ float* contents_scale_y,
+ gfx::Size* content_bounds) {
+ DCHECK(layer_tree_host_);
+
+ *contents_scale_x = 1;
+ *contents_scale_y = 1;
+ *content_bounds = bounds();
+}
+
+void Layer::SetMasksToBounds(bool masks_to_bounds) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (masks_to_bounds_ == masks_to_bounds)
+ return;
+ masks_to_bounds_ = masks_to_bounds;
+ SetNeedsCommit();
+}
+
+void Layer::SetMaskLayer(Layer* mask_layer) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (mask_layer_.get() == mask_layer)
+ return;
+ if (mask_layer_.get()) {
+ DCHECK_EQ(this, mask_layer_->parent());
+ mask_layer_->RemoveFromParent();
+ }
+ mask_layer_ = mask_layer;
+ if (mask_layer_.get()) {
+ DCHECK(!mask_layer_->parent());
+ mask_layer_->RemoveFromParent();
+ mask_layer_->SetParent(this);
+ mask_layer_->SetIsMask(true);
+ }
+ SetNeedsFullTreeSync();
+}
+
+void Layer::SetReplicaLayer(Layer* layer) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (replica_layer_.get() == layer)
+ return;
+ if (replica_layer_.get()) {
+ DCHECK_EQ(this, replica_layer_->parent());
+ replica_layer_->RemoveFromParent();
+ }
+ replica_layer_ = layer;
+ if (replica_layer_.get()) {
+ DCHECK(!replica_layer_->parent());
+ replica_layer_->RemoveFromParent();
+ replica_layer_->SetParent(this);
+ }
+ SetNeedsFullTreeSync();
+}
+
+void Layer::SetFilters(const FilterOperations& filters) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (filters_ == filters)
+ return;
+ DCHECK(!filter_);
+ filters_ = filters;
+ SetNeedsCommit();
+ if (!filters.IsEmpty() && layer_tree_host_)
+ layer_tree_host_->set_needs_filter_context();
+}
+
+void Layer::SetFilter(const skia::RefPtr<SkImageFilter>& filter) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (filter_.get() == filter.get())
+ return;
+ DCHECK(filters_.IsEmpty());
+ filter_ = filter;
+ SetNeedsCommit();
+ if (filter && layer_tree_host_)
+ layer_tree_host_->set_needs_filter_context();
+}
+
+void Layer::SetBackgroundFilters(const FilterOperations& filters) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (background_filters_ == filters)
+ return;
+ background_filters_ = filters;
+ SetNeedsCommit();
+ if (!filters.IsEmpty() && layer_tree_host_)
+ layer_tree_host_->set_needs_filter_context();
+}
+
+void Layer::SetOpacity(float opacity) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (opacity_ == opacity)
+ return;
+ opacity_ = opacity;
+ SetNeedsCommit();
+}
+
+bool Layer::OpacityIsAnimating() const {
+ return layer_animation_controller_->IsAnimatingProperty(Animation::Opacity);
+}
+
+bool Layer::OpacityCanAnimateOnImplThread() const {
+ return false;
+}
+
+void Layer::SetContentsOpaque(bool opaque) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (contents_opaque_ == opaque)
+ return;
+ contents_opaque_ = opaque;
+ SetNeedsCommit();
+}
+
+void Layer::SetPosition(gfx::PointF position) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (position_ == position)
+ return;
+ position_ = position;
+ SetNeedsCommit();
+}
+
+bool Layer::IsContainerForFixedPositionLayers() const {
+ if (!transform_.IsIdentityOrTranslation())
+ return true;
+ if (parent_ && !parent_->sublayer_transform_.IsIdentityOrTranslation())
+ return true;
+ return is_container_for_fixed_position_layers_;
+}
+
+void Layer::SetSublayerTransform(const gfx::Transform& sublayer_transform) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (sublayer_transform_ == sublayer_transform)
+ return;
+ sublayer_transform_ = sublayer_transform;
+ SetNeedsCommit();
+}
+
+void Layer::SetTransform(const gfx::Transform& transform) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (transform_ == transform)
+ return;
+ transform_ = transform;
+ SetNeedsCommit();
+}
+
+bool Layer::TransformIsAnimating() const {
+ return layer_animation_controller_->IsAnimatingProperty(Animation::Transform);
+}
+
+void Layer::SetScrollOffset(gfx::Vector2d scroll_offset) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (scroll_offset_ == scroll_offset)
+ return;
+ scroll_offset_ = scroll_offset;
+ SetNeedsCommit();
+}
+
+void Layer::SetScrollOffsetFromImplSide(gfx::Vector2d scroll_offset) {
+ DCHECK(IsPropertyChangeAllowed());
+ // This function only gets called during a begin frame, so there
+ // is no need to call SetNeedsUpdate here.
+ DCHECK(layer_tree_host_ && layer_tree_host_->CommitRequested());
+ if (scroll_offset_ == scroll_offset)
+ return;
+ scroll_offset_ = scroll_offset;
+ SetNeedsPushProperties();
+ if (!did_scroll_callback_.is_null())
+ did_scroll_callback_.Run();
+ // The callback could potentially change the layer structure:
+ // "this" may have been destroyed during the process.
+}
+
+void Layer::SetMaxScrollOffset(gfx::Vector2d max_scroll_offset) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (max_scroll_offset_ == max_scroll_offset)
+ return;
+ max_scroll_offset_ = max_scroll_offset;
+ SetNeedsCommit();
+}
+
+void Layer::SetScrollable(bool scrollable) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (scrollable_ == scrollable)
+ return;
+ scrollable_ = scrollable;
+ SetNeedsCommit();
+}
+
+void Layer::SetShouldScrollOnMainThread(bool should_scroll_on_main_thread) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (should_scroll_on_main_thread_ == should_scroll_on_main_thread)
+ return;
+ should_scroll_on_main_thread_ = should_scroll_on_main_thread;
+ SetNeedsCommit();
+}
+
+void Layer::SetHaveWheelEventHandlers(bool have_wheel_event_handlers) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (have_wheel_event_handlers_ == have_wheel_event_handlers)
+ return;
+ have_wheel_event_handlers_ = have_wheel_event_handlers;
+ SetNeedsCommit();
+}
+
+void Layer::SetNonFastScrollableRegion(const Region& region) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (non_fast_scrollable_region_ == region)
+ return;
+ non_fast_scrollable_region_ = region;
+ SetNeedsCommit();
+}
+
+void Layer::SetTouchEventHandlerRegion(const Region& region) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (touch_event_handler_region_ == region)
+ return;
+ touch_event_handler_region_ = region;
+}
+
+void Layer::SetDrawCheckerboardForMissingTiles(bool checkerboard) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (draw_checkerboard_for_missing_tiles_ == checkerboard)
+ return;
+ draw_checkerboard_for_missing_tiles_ = checkerboard;
+ SetNeedsCommit();
+}
+
+void Layer::SetForceRenderSurface(bool force) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (force_render_surface_ == force)
+ return;
+ force_render_surface_ = force;
+ SetNeedsCommit();
+}
+
+void Layer::SetDoubleSided(bool double_sided) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (double_sided_ == double_sided)
+ return;
+ double_sided_ = double_sided;
+ SetNeedsCommit();
+}
+
+void Layer::SetIsDrawable(bool is_drawable) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (is_drawable_ == is_drawable)
+ return;
+
+ is_drawable_ = is_drawable;
+ SetNeedsCommit();
+}
+
+void Layer::SetHideLayerAndSubtree(bool hide) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (hide_layer_and_subtree_ == hide)
+ return;
+
+ hide_layer_and_subtree_ = hide;
+ SetNeedsCommit();
+}
+
+void Layer::SetNeedsDisplayRect(const gfx::RectF& dirty_rect) {
+ if (dirty_rect.IsEmpty())
+ return;
+
+ SetNeedsPushProperties();
+ update_rect_.Union(dirty_rect);
+
+ if (DrawsContent())
+ SetNeedsUpdate();
+}
+
+bool Layer::DescendantIsFixedToContainerLayer() const {
+ for (size_t i = 0; i < children_.size(); ++i) {
+ if (children_[i]->position_constraint_.is_fixed_position() ||
+ children_[i]->DescendantIsFixedToContainerLayer())
+ return true;
+ }
+ return false;
+}
+
+void Layer::SetIsContainerForFixedPositionLayers(bool container) {
+ if (is_container_for_fixed_position_layers_ == container)
+ return;
+ is_container_for_fixed_position_layers_ = container;
+
+ if (layer_tree_host_ && layer_tree_host_->CommitRequested())
+ return;
+
+ // Only request a commit if we have a fixed positioned descendant.
+ if (DescendantIsFixedToContainerLayer())
+ SetNeedsCommit();
+}
+
+void Layer::SetPositionConstraint(const LayerPositionConstraint& constraint) {
+ DCHECK(IsPropertyChangeAllowed());
+ if (position_constraint_ == constraint)
+ return;
+ position_constraint_ = constraint;
+ SetNeedsCommit();
+}
+
+static void RunCopyCallbackOnMainThread(scoped_ptr<CopyOutputRequest> request,
+ scoped_ptr<CopyOutputResult> result) {
+ request->SendResult(result.Pass());
+}
+
+static void PostCopyCallbackToMainThread(
+ scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner,
+ scoped_ptr<CopyOutputRequest> request,
+ scoped_ptr<CopyOutputResult> result) {
+ main_thread_task_runner->PostTask(FROM_HERE,
+ base::Bind(&RunCopyCallbackOnMainThread,
+ base::Passed(&request),
+ base::Passed(&result)));
+}
+
+void Layer::PushPropertiesTo(LayerImpl* layer) {
+ DCHECK(layer_tree_host_);
+
+ // If we did not SavePaintProperties() for the layer this frame, then push the
+ // real property values, not the paint property values.
+ bool use_paint_properties = paint_properties_.source_frame_number ==
+ layer_tree_host_->source_frame_number();
+
+ layer->SetAnchorPoint(anchor_point_);
+ layer->SetAnchorPointZ(anchor_point_z_);
+ layer->SetBackgroundColor(background_color_);
+ layer->SetBounds(use_paint_properties ? paint_properties_.bounds
+ : bounds_);
+ layer->SetContentBounds(content_bounds());
+ layer->SetContentsScale(contents_scale_x(), contents_scale_y());
+ layer->SetDebugName(debug_name_);
+ layer->SetCompositingReasons(compositing_reasons_);
+ layer->SetDoubleSided(double_sided_);
+ layer->SetDrawCheckerboardForMissingTiles(
+ draw_checkerboard_for_missing_tiles_);
+ layer->SetForceRenderSurface(force_render_surface_);
+ layer->SetDrawsContent(DrawsContent());
+ layer->SetHideLayerAndSubtree(hide_layer_and_subtree_);
+ layer->SetFilters(filters());
+ layer->SetFilter(filter());
+ layer->SetBackgroundFilters(background_filters());
+ layer->SetMasksToBounds(masks_to_bounds_);
+ layer->SetShouldScrollOnMainThread(should_scroll_on_main_thread_);
+ layer->SetHaveWheelEventHandlers(have_wheel_event_handlers_);
+ layer->SetNonFastScrollableRegion(non_fast_scrollable_region_);
+ layer->SetTouchEventHandlerRegion(touch_event_handler_region_);
+ layer->SetContentsOpaque(contents_opaque_);
+ if (!layer->OpacityIsAnimatingOnImplOnly() && !OpacityIsAnimating())
+ layer->SetOpacity(opacity_);
+ DCHECK(!(OpacityIsAnimating() && layer->OpacityIsAnimatingOnImplOnly()));
+ layer->SetPosition(position_);
+ layer->SetIsContainerForFixedPositionLayers(
+ IsContainerForFixedPositionLayers());
+ layer->SetFixedContainerSizeDelta(gfx::Vector2dF());
+ layer->SetPositionConstraint(position_constraint_);
+ layer->SetPreserves3d(preserves_3d());
+ layer->SetUseParentBackfaceVisibility(use_parent_backface_visibility_);
+ layer->SetSublayerTransform(sublayer_transform_);
+ if (!layer->TransformIsAnimatingOnImplOnly() && !TransformIsAnimating())
+ layer->SetTransform(transform_);
+ DCHECK(!(TransformIsAnimating() && layer->TransformIsAnimatingOnImplOnly()));
+
+ layer->SetScrollable(scrollable_);
+ layer->SetScrollOffset(scroll_offset_);
+ layer->SetMaxScrollOffset(max_scroll_offset_);
+
+ // Wrap the copy_requests_ in a PostTask to the main thread.
+ ScopedPtrVector<CopyOutputRequest> main_thread_copy_requests;
+ for (ScopedPtrVector<CopyOutputRequest>::iterator it = copy_requests_.begin();
+ it != copy_requests_.end();
+ ++it) {
+ scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner =
+ layer_tree_host()->proxy()->MainThreadTaskRunner();
+ scoped_ptr<CopyOutputRequest> original_request = copy_requests_.take(it);
+ const CopyOutputRequest& original_request_ref = *original_request;
+ scoped_ptr<CopyOutputRequest> main_thread_request =
+ CopyOutputRequest::CreateRelayRequest(
+ original_request_ref,
+ base::Bind(&PostCopyCallbackToMainThread,
+ main_thread_task_runner,
+ base::Passed(&original_request)));
+ main_thread_copy_requests.push_back(main_thread_request.Pass());
+ }
+ copy_requests_.clear();
+ layer->PassCopyRequests(&main_thread_copy_requests);
+
+ // If the main thread commits multiple times before the impl thread actually
+ // draws, then damage tracking will become incorrect if we simply clobber the
+ // update_rect here. The LayerImpl's update_rect needs to accumulate (i.e.
+ // union) any update changes that have occurred on the main thread.
+ update_rect_.Union(layer->update_rect());
+ layer->set_update_rect(update_rect_);
+
+ if (layer->layer_tree_impl()->settings().impl_side_painting) {
+ DCHECK(layer->layer_tree_impl()->IsPendingTree());
+ LayerImpl* active_twin =
+ layer->layer_tree_impl()->FindActiveTreeLayerById(id());
+ // Update the scroll delta from the active layer, which may have
+ // adjusted its scroll delta prior to this pending layer being created.
+ // This code is identical to that in LayerImpl::SetScrollDelta.
+ if (active_twin) {
+ DCHECK(layer->sent_scroll_delta().IsZero());
+ layer->SetScrollDelta(active_twin->ScrollDelta() -
+ active_twin->sent_scroll_delta());
+ }
+ } else {
+ layer->SetScrollDelta(layer->ScrollDelta() - layer->sent_scroll_delta());
+ layer->SetSentScrollDelta(gfx::Vector2d());
+ }
+
+ layer->SetStackingOrderChanged(stacking_order_changed_);
+
+ layer_animation_controller_->PushAnimationUpdatesTo(
+ layer->layer_animation_controller());
+
+ // Reset any state that should be cleared for the next update.
+ stacking_order_changed_ = false;
+ update_rect_ = gfx::RectF();
+
+ // Animating layers require further push properties to clean up the animation.
+ // crbug.com/259088
+ needs_push_properties_ = layer_animation_controller_->has_any_animation();
+ num_dependents_need_push_properties_ = 0;
+}
+
+scoped_ptr<LayerImpl> Layer::CreateLayerImpl(LayerTreeImpl* tree_impl) {
+ return LayerImpl::Create(tree_impl, layer_id_);
+}
+
+bool Layer::DrawsContent() const {
+ return is_drawable_;
+}
+
+void Layer::SavePaintProperties() {
+ DCHECK(layer_tree_host_);
+
+ // TODO(reveman): Save all layer properties that we depend on not
+ // changing until PushProperties() has been called. crbug.com/231016
+ paint_properties_.bounds = bounds_;
+ paint_properties_.source_frame_number =
+ layer_tree_host_->source_frame_number();
+}
+
+bool Layer::Update(ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion) {
+ DCHECK(layer_tree_host_);
+ DCHECK_EQ(layer_tree_host_->source_frame_number(),
+ paint_properties_.source_frame_number) <<
+ "SavePaintProperties must be called for any layer that is painted.";
+ return false;
+}
+
+bool Layer::NeedMoreUpdates() {
+ return false;
+}
+
+void Layer::SetDebugName(const std::string& debug_name) {
+ debug_name_ = debug_name;
+ SetNeedsCommit();
+}
+
+void Layer::SetCompositingReasons(CompositingReasons reasons) {
+ compositing_reasons_ = reasons;
+}
+
+void Layer::CreateRenderSurface() {
+ DCHECK(!draw_properties_.render_surface);
+ draw_properties_.render_surface = make_scoped_ptr(new RenderSurface(this));
+ draw_properties_.render_target = this;
+}
+
+void Layer::ClearRenderSurface() {
+ draw_properties_.render_surface.reset();
+}
+
+void Layer::OnOpacityAnimated(float opacity) {
+ // This is called due to an ongoing accelerated animation. Since this
+ // animation is also being run on the impl thread, there is no need to request
+ // a commit to push this value over, so set the value directly rather than
+ // calling SetOpacity.
+ opacity_ = opacity;
+}
+
+void Layer::OnTransformAnimated(const gfx::Transform& transform) {
+ // This is called due to an ongoing accelerated animation. Since this
+ // animation is also being run on the impl thread, there is no need to request
+ // a commit to push this value over, so set this value directly rather than
+ // calling SetTransform.
+ transform_ = transform;
+}
+
+bool Layer::IsActive() const {
+ return true;
+}
+
+bool Layer::AddAnimation(scoped_ptr <Animation> animation) {
+ if (!layer_animation_controller_->animation_registrar())
+ return false;
+
+ UMA_HISTOGRAM_BOOLEAN("Renderer.AnimationAddedToOrphanLayer",
+ !layer_tree_host_);
+ layer_animation_controller_->AddAnimation(animation.Pass());
+ SetNeedsCommit();
+ return true;
+}
+
+void Layer::PauseAnimation(int animation_id, double time_offset) {
+ layer_animation_controller_->PauseAnimation(animation_id, time_offset);
+ SetNeedsCommit();
+}
+
+void Layer::RemoveAnimation(int animation_id) {
+ layer_animation_controller_->RemoveAnimation(animation_id);
+ SetNeedsCommit();
+}
+
+void Layer::SuspendAnimations(double monotonic_time) {
+ layer_animation_controller_->SuspendAnimations(monotonic_time);
+ SetNeedsCommit();
+}
+
+void Layer::ResumeAnimations(double monotonic_time) {
+ layer_animation_controller_->ResumeAnimations(monotonic_time);
+ SetNeedsCommit();
+}
+
+void Layer::SetLayerAnimationControllerForTest(
+ scoped_refptr<LayerAnimationController> controller) {
+ layer_animation_controller_->RemoveValueObserver(this);
+ layer_animation_controller_ = controller;
+ layer_animation_controller_->set_force_sync();
+ layer_animation_controller_->AddValueObserver(this);
+ SetNeedsCommit();
+}
+
+bool Layer::HasActiveAnimation() const {
+ return layer_animation_controller_->HasActiveAnimation();
+}
+
+void Layer::AddLayerAnimationEventObserver(
+ LayerAnimationEventObserver* animation_observer) {
+ layer_animation_controller_->AddEventObserver(animation_observer);
+}
+
+void Layer::RemoveLayerAnimationEventObserver(
+ LayerAnimationEventObserver* animation_observer) {
+ layer_animation_controller_->RemoveEventObserver(animation_observer);
+}
+
+Region Layer::VisibleContentOpaqueRegion() const {
+ if (contents_opaque())
+ return visible_content_rect();
+ return Region();
+}
+
+ScrollbarLayer* Layer::ToScrollbarLayer() {
+ return NULL;
+}
+
+RenderingStatsInstrumentation* Layer::rendering_stats_instrumentation() const {
+ return layer_tree_host_->rendering_stats_instrumentation();
+}
+
+bool Layer::SupportsLCDText() const {
+ return false;
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/layer.h b/chromium/cc/layers/layer.h
new file mode 100644
index 00000000000..035e071a5fd
--- /dev/null
+++ b/chromium/cc/layers/layer.h
@@ -0,0 +1,530 @@
+// Copyright 2010 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.
+
+#ifndef CC_LAYERS_LAYER_H_
+#define CC_LAYERS_LAYER_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/observer_list.h"
+#include "cc/animation/layer_animation_controller.h"
+#include "cc/animation/layer_animation_value_observer.h"
+#include "cc/base/cc_export.h"
+#include "cc/base/region.h"
+#include "cc/base/scoped_ptr_vector.h"
+#include "cc/layers/compositing_reasons.h"
+#include "cc/layers/draw_properties.h"
+#include "cc/layers/layer_lists.h"
+#include "cc/layers/layer_position_constraint.h"
+#include "cc/layers/paint_properties.h"
+#include "cc/layers/render_surface.h"
+#include "cc/output/filter_operations.h"
+#include "cc/trees/occlusion_tracker.h"
+#include "skia/ext/refptr.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/skia/include/core/SkImageFilter.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/rect_f.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+
+class Animation;
+class AnimationDelegate;
+struct AnimationEvent;
+class CopyOutputRequest;
+class LayerAnimationDelegate;
+class LayerAnimationEventObserver;
+class LayerImpl;
+class LayerTreeHost;
+class LayerTreeImpl;
+class PriorityCalculator;
+class RenderingStatsInstrumentation;
+class ResourceUpdateQueue;
+class ScrollbarLayer;
+struct AnimationEvent;
+
+// Base class for composited layers. Special layer types are derived from
+// this class.
+class CC_EXPORT Layer : public base::RefCounted<Layer>,
+ public LayerAnimationValueObserver {
+ public:
+ enum LayerIdLabels {
+ INVALID_ID = -1,
+ };
+
+ static scoped_refptr<Layer> Create();
+
+ int id() const { return layer_id_; }
+
+ Layer* RootLayer();
+ Layer* parent() { return parent_; }
+ const Layer* parent() const { return parent_; }
+ void AddChild(scoped_refptr<Layer> child);
+ void InsertChild(scoped_refptr<Layer> child, size_t index);
+ void ReplaceChild(Layer* reference, scoped_refptr<Layer> new_layer);
+ void RemoveFromParent();
+ void RemoveAllChildren();
+ void SetChildren(const LayerList& children);
+ bool HasAncestor(const Layer* ancestor) const;
+
+ const LayerList& children() const { return children_; }
+ Layer* child_at(size_t index) { return children_[index].get(); }
+
+ // This requests the layer and its subtree be rendered and given to the
+ // callback. If the copy is unable to be produced (the layer is destroyed
+ // first), then the callback is called with a NULL/empty result.
+ void RequestCopyOfOutput(scoped_ptr<CopyOutputRequest> request);
+ bool HasCopyRequest() const {
+ return !copy_requests_.empty();
+ }
+
+ void SetAnchorPoint(gfx::PointF anchor_point);
+ gfx::PointF anchor_point() const { return anchor_point_; }
+
+ void SetAnchorPointZ(float anchor_point_z);
+ float anchor_point_z() const { return anchor_point_z_; }
+
+ virtual void SetBackgroundColor(SkColor background_color);
+ SkColor background_color() const { return background_color_; }
+ // If contents_opaque(), return an opaque color else return a
+ // non-opaque color. Tries to return background_color(), if possible.
+ SkColor SafeOpaqueBackgroundColor() const;
+
+ // A layer's bounds are in logical, non-page-scaled pixels (however, the
+ // root layer's bounds are in physical pixels).
+ void SetBounds(gfx::Size bounds);
+ gfx::Size bounds() const { return bounds_; }
+
+ void SetMasksToBounds(bool masks_to_bounds);
+ bool masks_to_bounds() const { return masks_to_bounds_; }
+
+ void SetMaskLayer(Layer* mask_layer);
+ Layer* mask_layer() { return mask_layer_.get(); }
+ const Layer* mask_layer() const { return mask_layer_.get(); }
+
+ virtual void SetNeedsDisplayRect(const gfx::RectF& dirty_rect);
+ void SetNeedsDisplay() { SetNeedsDisplayRect(gfx::RectF(bounds())); }
+
+ void SetOpacity(float opacity);
+ float opacity() const { return opacity_; }
+ bool OpacityIsAnimating() const;
+ virtual bool OpacityCanAnimateOnImplThread() const;
+
+ void SetFilters(const FilterOperations& filters);
+ const FilterOperations& filters() const { return filters_; }
+
+ void SetFilter(const skia::RefPtr<SkImageFilter>& filter);
+ skia::RefPtr<SkImageFilter> filter() const { return filter_; }
+
+ // Background filters are filters applied to what is behind this layer, when
+ // they are viewed through non-opaque regions in this layer. They are used
+ // through the WebLayer interface, and are not exposed to HTML.
+ void SetBackgroundFilters(const FilterOperations& filters);
+ const FilterOperations& background_filters() const {
+ return background_filters_;
+ }
+
+ virtual void SetContentsOpaque(bool opaque);
+ bool contents_opaque() const { return contents_opaque_; }
+
+ void SetPosition(gfx::PointF position);
+ gfx::PointF position() const { return position_; }
+
+ void SetIsContainerForFixedPositionLayers(bool container);
+ bool IsContainerForFixedPositionLayers() const;
+
+ void SetPositionConstraint(const LayerPositionConstraint& constraint);
+ const LayerPositionConstraint& position_constraint() const {
+ return position_constraint_;
+ }
+
+ void SetSublayerTransform(const gfx::Transform& sublayer_transform);
+ const gfx::Transform& sublayer_transform() const {
+ return sublayer_transform_;
+ }
+
+ void SetTransform(const gfx::Transform& transform);
+ const gfx::Transform& transform() const { return transform_; }
+ bool TransformIsAnimating() const;
+
+ DrawProperties<Layer, RenderSurface>& draw_properties() {
+ return draw_properties_;
+ }
+ const DrawProperties<Layer, RenderSurface>& draw_properties() const {
+ return draw_properties_;
+ }
+
+ // The following are shortcut accessors to get various information from
+ // draw_properties_
+ const gfx::Transform& draw_transform() const {
+ return draw_properties_.target_space_transform;
+ }
+ const gfx::Transform& screen_space_transform() const {
+ return draw_properties_.screen_space_transform;
+ }
+ float draw_opacity() const { return draw_properties_.opacity; }
+ bool draw_opacity_is_animating() const {
+ return draw_properties_.opacity_is_animating;
+ }
+ bool draw_transform_is_animating() const {
+ return draw_properties_.target_space_transform_is_animating;
+ }
+ bool screen_space_transform_is_animating() const {
+ return draw_properties_.screen_space_transform_is_animating;
+ }
+ bool screen_space_opacity_is_animating() const {
+ return draw_properties_.screen_space_opacity_is_animating;
+ }
+ bool can_use_lcd_text() const { return draw_properties_.can_use_lcd_text; }
+ bool is_clipped() const { return draw_properties_.is_clipped; }
+ gfx::Rect clip_rect() const { return draw_properties_.clip_rect; }
+ gfx::Rect drawable_content_rect() const {
+ return draw_properties_.drawable_content_rect;
+ }
+ gfx::Rect visible_content_rect() const {
+ return draw_properties_.visible_content_rect;
+ }
+ Layer* render_target() {
+ DCHECK(!draw_properties_.render_target ||
+ draw_properties_.render_target->render_surface());
+ return draw_properties_.render_target;
+ }
+ const Layer* render_target() const {
+ DCHECK(!draw_properties_.render_target ||
+ draw_properties_.render_target->render_surface());
+ return draw_properties_.render_target;
+ }
+ RenderSurface* render_surface() const {
+ return draw_properties_.render_surface.get();
+ }
+
+ void SetScrollOffset(gfx::Vector2d scroll_offset);
+ gfx::Vector2d scroll_offset() const { return scroll_offset_; }
+ void SetScrollOffsetFromImplSide(gfx::Vector2d scroll_offset);
+
+ void SetMaxScrollOffset(gfx::Vector2d max_scroll_offset);
+ gfx::Vector2d max_scroll_offset() const { return max_scroll_offset_; }
+
+ void SetScrollable(bool scrollable);
+ bool scrollable() const { return scrollable_; }
+
+ void SetShouldScrollOnMainThread(bool should_scroll_on_main_thread);
+ bool should_scroll_on_main_thread() const {
+ return should_scroll_on_main_thread_;
+ }
+
+ void SetHaveWheelEventHandlers(bool have_wheel_event_handlers);
+ bool have_wheel_event_handlers() const { return have_wheel_event_handlers_; }
+
+ void SetNonFastScrollableRegion(const Region& non_fast_scrollable_region);
+ const Region& non_fast_scrollable_region() const {
+ return non_fast_scrollable_region_;
+ }
+
+ void SetTouchEventHandlerRegion(const Region& touch_event_handler_region);
+ const Region& touch_event_handler_region() const {
+ return touch_event_handler_region_;
+ }
+
+ void set_did_scroll_callback(const base::Closure& callback) {
+ did_scroll_callback_ = callback;
+ }
+
+ void SetDrawCheckerboardForMissingTiles(bool checkerboard);
+ bool DrawCheckerboardForMissingTiles() const {
+ return draw_checkerboard_for_missing_tiles_;
+ }
+
+ void SetForceRenderSurface(bool force_render_surface);
+ bool force_render_surface() const { return force_render_surface_; }
+
+ gfx::Vector2d ScrollDelta() const { return gfx::Vector2d(); }
+ gfx::Vector2dF TotalScrollOffset() const {
+ // Floating point to match the LayerImpl version.
+ return scroll_offset() + ScrollDelta();
+ }
+
+ void SetDoubleSided(bool double_sided);
+ bool double_sided() const { return double_sided_; }
+
+ void SetPreserves3d(bool preserves_3d) { preserves_3d_ = preserves_3d; }
+ bool preserves_3d() const { return preserves_3d_; }
+
+ void set_use_parent_backface_visibility(bool use) {
+ use_parent_backface_visibility_ = use;
+ }
+ bool use_parent_backface_visibility() const {
+ return use_parent_backface_visibility_;
+ }
+
+ virtual void SetLayerTreeHost(LayerTreeHost* host);
+
+ bool HasDelegatedContent() const { return false; }
+ bool HasContributingDelegatedRenderPasses() const { return false; }
+
+ void SetIsDrawable(bool is_drawable);
+
+ void SetHideLayerAndSubtree(bool hide);
+ bool hide_layer_and_subtree() const { return hide_layer_and_subtree_; }
+
+ void SetReplicaLayer(Layer* layer);
+ Layer* replica_layer() { return replica_layer_.get(); }
+ const Layer* replica_layer() const { return replica_layer_.get(); }
+
+ bool has_mask() const { return !!mask_layer_.get(); }
+ bool has_replica() const { return !!replica_layer_.get(); }
+ bool replica_has_mask() const {
+ return replica_layer_.get() &&
+ (mask_layer_.get() || replica_layer_->mask_layer_.get());
+ }
+
+ // These methods typically need to be overwritten by derived classes.
+ virtual bool DrawsContent() const;
+ virtual void SavePaintProperties();
+ // Returns true iff any resources were updated that need to be committed.
+ virtual bool Update(ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion);
+ virtual bool NeedMoreUpdates();
+ virtual void SetIsMask(bool is_mask) {}
+ virtual void ReduceMemoryUsage() {}
+
+ void SetDebugName(const std::string& debug_name);
+ void SetCompositingReasons(CompositingReasons reasons);
+
+ virtual void PushPropertiesTo(LayerImpl* layer);
+
+ void CreateRenderSurface();
+ void ClearRenderSurface();
+
+ // The contents scale converts from logical, non-page-scaled pixels to target
+ // pixels. The contents scale is 1 for the root layer as it is already in
+ // physical pixels. By default contents scale is forced to be 1 except for
+ // subclasses of ContentsScalingLayer.
+ float contents_scale_x() const { return draw_properties_.contents_scale_x; }
+ float contents_scale_y() const { return draw_properties_.contents_scale_y; }
+ gfx::Size content_bounds() const { return draw_properties_.content_bounds; }
+
+ virtual void CalculateContentsScale(float ideal_contents_scale,
+ float device_scale_factor,
+ float page_scale_factor,
+ bool animating_transform_to_screen,
+ float* contents_scale_x,
+ float* contents_scale_y,
+ gfx::Size* content_bounds);
+
+ LayerTreeHost* layer_tree_host() const { return layer_tree_host_; }
+
+ // Set the priority of all desired textures in this layer.
+ virtual void SetTexturePriorities(const PriorityCalculator& priority_calc) {}
+
+ bool AddAnimation(scoped_ptr<Animation> animation);
+ void PauseAnimation(int animation_id, double time_offset);
+ void RemoveAnimation(int animation_id);
+
+ void SuspendAnimations(double monotonic_time);
+ void ResumeAnimations(double monotonic_time);
+
+ LayerAnimationController* layer_animation_controller() {
+ return layer_animation_controller_.get();
+ }
+ void SetLayerAnimationControllerForTest(
+ scoped_refptr<LayerAnimationController> controller);
+
+ void set_layer_animation_delegate(AnimationDelegate* delegate) {
+ layer_animation_controller_->set_layer_animation_delegate(delegate);
+ }
+
+ bool HasActiveAnimation() const;
+
+ void AddLayerAnimationEventObserver(
+ LayerAnimationEventObserver* animation_observer);
+ void RemoveLayerAnimationEventObserver(
+ LayerAnimationEventObserver* animation_observer);
+
+ virtual Region VisibleContentOpaqueRegion() const;
+
+ virtual ScrollbarLayer* ToScrollbarLayer();
+
+ gfx::Rect LayerRectToContentRect(const gfx::RectF& layer_rect) const;
+
+ // In impl-side painting, this returns true if this layer type is not
+ // compatible with the main thread running freely, such as a double-buffered
+ // canvas that doesn't want to be triple-buffered across all three trees.
+ virtual bool BlocksPendingCommit() const;
+ // Returns true if anything in this tree blocksPendingCommit.
+ bool BlocksPendingCommitRecursive() const;
+
+ virtual bool CanClipSelf() const;
+
+ // Constructs a LayerImpl of the correct runtime type for this Layer type.
+ virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl);
+
+ bool NeedsDisplayForTesting() const { return !update_rect_.IsEmpty(); }
+ void ResetNeedsDisplayForTesting() { update_rect_ = gfx::RectF(); }
+
+ RenderingStatsInstrumentation* rendering_stats_instrumentation() const;
+
+ const PaintProperties& paint_properties() const {
+ return paint_properties_;
+ }
+
+ // The scale at which contents should be rastered, to match the scale at
+ // which they will drawn to the screen. This scale is a component of the
+ // contents scale but does not include page/device scale factors.
+ // TODO(danakj): This goes away when TiledLayer goes away.
+ void set_raster_scale(float scale) { raster_scale_ = scale; }
+ float raster_scale() const { return raster_scale_; }
+ bool raster_scale_is_unknown() const { return raster_scale_ == 0.f; }
+
+ virtual bool SupportsLCDText() const;
+
+ bool needs_push_properties() const { return needs_push_properties_; }
+ bool descendant_needs_push_properties() const {
+ return num_dependents_need_push_properties_ > 0;
+ }
+
+ protected:
+ friend class LayerImpl;
+ friend class TreeSynchronizer;
+ virtual ~Layer();
+
+ Layer();
+
+ // These SetNeeds functions are in order of severity of update:
+ //
+ // Called when this layer has been modified in some way, but isn't sure
+ // that it needs a commit yet. It needs CalcDrawProperties and UpdateLayers
+ // before it knows whether or not a commit is required.
+ void SetNeedsUpdate();
+ // Called when a property has been modified in a way that the layer
+ // knows immediately that a commit is required. This implies SetNeedsUpdate
+ // as well as SetNeedsPushProperties to push that property.
+ void SetNeedsCommit();
+ // Called when there's been a change in layer structure. Implies both
+ // SetNeedsUpdate and SetNeedsCommit, but not SetNeedsPushProperties.
+ void SetNeedsFullTreeSync();
+ bool IsPropertyChangeAllowed() const;
+
+ void SetNeedsPushProperties();
+ void AddDependentNeedsPushProperties();
+ void RemoveDependentNeedsPushProperties();
+ bool parent_should_know_need_push_properties() const {
+ return needs_push_properties() || descendant_needs_push_properties();
+ }
+
+ void reset_raster_scale_to_unknown() { raster_scale_ = 0.f; }
+
+ // This flag is set when the layer needs to push properties to the impl
+ // side.
+ bool needs_push_properties_;
+
+ // The number of direct children or dependent layers that need to be recursed
+ // to in order for them or a descendent of them to push properties to the impl
+ // side.
+ int num_dependents_need_push_properties_;
+
+ // Tracks whether this layer may have changed stacking order with its
+ // siblings.
+ bool stacking_order_changed_;
+
+ // The update rect is the region of the compositor resource that was
+ // actually updated by the compositor. For layers that may do updating
+ // outside the compositor's control (i.e. plugin layers), this information
+ // is not available and the update rect will remain empty.
+ // Note this rect is in layer space (not content space).
+ gfx::RectF update_rect_;
+
+ scoped_refptr<Layer> mask_layer_;
+
+ int layer_id_;
+
+ // When true, the layer is about to perform an update. Any commit requests
+ // will be handled implicitly after the update completes.
+ bool ignore_set_needs_commit_;
+
+ private:
+ friend class base::RefCounted<Layer>;
+
+ void SetParent(Layer* layer);
+ bool DescendantIsFixedToContainerLayer() const;
+
+ // Returns the index of the child or -1 if not found.
+ int IndexOfChild(const Layer* reference);
+
+ // This should only be called from RemoveFromParent().
+ void RemoveChildOrDependent(Layer* child);
+
+ // LayerAnimationValueObserver implementation.
+ virtual void OnOpacityAnimated(float opacity) OVERRIDE;
+ virtual void OnTransformAnimated(const gfx::Transform& transform) OVERRIDE;
+ virtual bool IsActive() const OVERRIDE;
+
+ LayerList children_;
+ Layer* parent_;
+
+ // Layer instances have a weak pointer to their LayerTreeHost.
+ // This pointer value is nil when a Layer is not in a tree and is
+ // updated via SetLayerTreeHost() if a layer moves between trees.
+ LayerTreeHost* layer_tree_host_;
+
+ scoped_refptr<LayerAnimationController> layer_animation_controller_;
+
+ // Layer properties.
+ gfx::Size bounds_;
+
+ gfx::Vector2d scroll_offset_;
+ gfx::Vector2d max_scroll_offset_;
+ bool scrollable_;
+ bool should_scroll_on_main_thread_;
+ bool have_wheel_event_handlers_;
+ Region non_fast_scrollable_region_;
+ Region touch_event_handler_region_;
+ gfx::PointF position_;
+ gfx::PointF anchor_point_;
+ SkColor background_color_;
+ std::string debug_name_;
+ CompositingReasons compositing_reasons_;
+ float opacity_;
+ skia::RefPtr<SkImageFilter> filter_;
+ FilterOperations filters_;
+ FilterOperations background_filters_;
+ float anchor_point_z_;
+ bool is_container_for_fixed_position_layers_;
+ LayerPositionConstraint position_constraint_;
+ bool is_drawable_;
+ bool hide_layer_and_subtree_;
+ bool masks_to_bounds_;
+ bool contents_opaque_;
+ bool double_sided_;
+ bool preserves_3d_;
+ bool use_parent_backface_visibility_;
+ bool draw_checkerboard_for_missing_tiles_;
+ bool force_render_surface_;
+
+ gfx::Transform transform_;
+ gfx::Transform sublayer_transform_;
+
+ // Replica layer used for reflections.
+ scoped_refptr<Layer> replica_layer_;
+
+ // Transient properties.
+ float raster_scale_;
+
+ ScopedPtrVector<CopyOutputRequest> copy_requests_;
+
+ base::Closure did_scroll_callback_;
+
+ DrawProperties<Layer, RenderSurface> draw_properties_;
+
+ PaintProperties paint_properties_;
+
+ DISALLOW_COPY_AND_ASSIGN(Layer);
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_LAYER_H_
diff --git a/chromium/cc/layers/layer_impl.cc b/chromium/cc/layers/layer_impl.cc
new file mode 100644
index 00000000000..b78cf1a45a8
--- /dev/null
+++ b/chromium/cc/layers/layer_impl.cc
@@ -0,0 +1,1150 @@
+// Copyright 2011 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 "cc/layers/layer_impl.h"
+
+#include "base/debug/trace_event.h"
+#include "base/strings/stringprintf.h"
+#include "cc/animation/animation_registrar.h"
+#include "cc/animation/scrollbar_animation_controller.h"
+#include "cc/animation/scrollbar_animation_controller_linear_fade.h"
+#include "cc/base/math_util.h"
+#include "cc/debug/debug_colors.h"
+#include "cc/debug/layer_tree_debug_state.h"
+#include "cc/debug/traced_value.h"
+#include "cc/input/layer_scroll_offset_delegate.h"
+#include "cc/layers/quad_sink.h"
+#include "cc/layers/scrollbar_layer_impl.h"
+#include "cc/output/copy_output_request.h"
+#include "cc/quads/debug_border_draw_quad.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "cc/trees/layer_tree_settings.h"
+#include "cc/trees/proxy.h"
+#include "ui/gfx/point_conversions.h"
+#include "ui/gfx/quad_f.h"
+#include "ui/gfx/rect_conversions.h"
+
+namespace cc {
+
+LayerImpl::LayerImpl(LayerTreeImpl* tree_impl, int id)
+ : parent_(NULL),
+ mask_layer_id_(-1),
+ replica_layer_id_(-1),
+ layer_id_(id),
+ layer_tree_impl_(tree_impl),
+ anchor_point_(0.5f, 0.5f),
+ anchor_point_z_(0.f),
+ scroll_offset_delegate_(NULL),
+ scrollable_(false),
+ should_scroll_on_main_thread_(false),
+ have_wheel_event_handlers_(false),
+ background_color_(0),
+ stacking_order_changed_(false),
+ double_sided_(true),
+ layer_property_changed_(false),
+ layer_surface_property_changed_(false),
+ masks_to_bounds_(false),
+ contents_opaque_(false),
+ opacity_(1.0),
+ preserves_3d_(false),
+ use_parent_backface_visibility_(false),
+ draw_checkerboard_for_missing_tiles_(false),
+ draws_content_(false),
+ hide_layer_and_subtree_(false),
+ force_render_surface_(false),
+ is_container_for_fixed_position_layers_(false),
+ draw_depth_(0.f),
+ compositing_reasons_(kCompositingReasonUnknown),
+ current_draw_mode_(DRAW_MODE_NONE),
+ horizontal_scrollbar_layer_(NULL),
+ vertical_scrollbar_layer_(NULL) {
+ DCHECK_GT(layer_id_, 0);
+ DCHECK(layer_tree_impl_);
+ layer_tree_impl_->RegisterLayer(this);
+ AnimationRegistrar* registrar = layer_tree_impl_->animationRegistrar();
+ layer_animation_controller_ =
+ registrar->GetAnimationControllerForId(layer_id_);
+ layer_animation_controller_->AddValueObserver(this);
+}
+
+LayerImpl::~LayerImpl() {
+ DCHECK_EQ(DRAW_MODE_NONE, current_draw_mode_);
+
+ layer_tree_impl_->UnregisterLayer(this);
+ layer_animation_controller_->RemoveValueObserver(this);
+}
+
+void LayerImpl::AddChild(scoped_ptr<LayerImpl> child) {
+ child->set_parent(this);
+ DCHECK_EQ(layer_tree_impl(), child->layer_tree_impl());
+ children_.push_back(child.Pass());
+ layer_tree_impl()->set_needs_update_draw_properties();
+}
+
+scoped_ptr<LayerImpl> LayerImpl::RemoveChild(LayerImpl* child) {
+ for (OwnedLayerImplList::iterator it = children_.begin();
+ it != children_.end();
+ ++it) {
+ if (*it == child) {
+ scoped_ptr<LayerImpl> ret = children_.take(it);
+ children_.erase(it);
+ layer_tree_impl()->set_needs_update_draw_properties();
+ return ret.Pass();
+ }
+ }
+ return scoped_ptr<LayerImpl>();
+}
+
+void LayerImpl::ClearChildList() {
+ if (children_.empty())
+ return;
+
+ children_.clear();
+ layer_tree_impl()->set_needs_update_draw_properties();
+}
+
+void LayerImpl::PassCopyRequests(ScopedPtrVector<CopyOutputRequest>* requests) {
+ if (requests->empty())
+ return;
+
+ bool was_empty = copy_requests_.empty();
+ copy_requests_.insert_and_take(copy_requests_.end(), *requests);
+ requests->clear();
+
+ if (was_empty && layer_tree_impl()->IsActiveTree())
+ layer_tree_impl()->AddLayerWithCopyOutputRequest(this);
+ NoteLayerPropertyChangedForSubtree();
+}
+
+void LayerImpl::TakeCopyRequestsAndTransformToTarget(
+ ScopedPtrVector<CopyOutputRequest>* requests) {
+ if (copy_requests_.empty())
+ return;
+
+ size_t first_inserted_request = requests->size();
+ requests->insert_and_take(requests->end(), copy_requests_);
+ copy_requests_.clear();
+
+ for (size_t i = first_inserted_request; i < requests->size(); ++i) {
+ CopyOutputRequest* request = requests->at(i);
+ if (!request->has_area())
+ continue;
+
+ gfx::Rect request_in_layer_space = request->area();
+ gfx::Rect request_in_content_space =
+ LayerRectToContentRect(request_in_layer_space);
+ request->set_area(
+ MathUtil::MapClippedRect(draw_properties_.target_space_transform,
+ request_in_content_space));
+ }
+
+ if (layer_tree_impl()->IsActiveTree())
+ layer_tree_impl()->RemoveLayerWithCopyOutputRequest(this);
+}
+
+void LayerImpl::CreateRenderSurface() {
+ DCHECK(!draw_properties_.render_surface);
+ draw_properties_.render_surface =
+ make_scoped_ptr(new RenderSurfaceImpl(this));
+ draw_properties_.render_target = this;
+}
+
+void LayerImpl::ClearRenderSurface() {
+ draw_properties_.render_surface.reset();
+}
+
+scoped_ptr<SharedQuadState> LayerImpl::CreateSharedQuadState() const {
+ scoped_ptr<SharedQuadState> state = SharedQuadState::Create();
+ state->SetAll(draw_properties_.target_space_transform,
+ draw_properties_.content_bounds,
+ draw_properties_.visible_content_rect,
+ draw_properties_.clip_rect,
+ draw_properties_.is_clipped,
+ draw_properties_.opacity);
+ return state.Pass();
+}
+
+bool LayerImpl::WillDraw(DrawMode draw_mode,
+ ResourceProvider* resource_provider) {
+ // WillDraw/DidDraw must be matched.
+ DCHECK_NE(DRAW_MODE_NONE, draw_mode);
+ DCHECK_EQ(DRAW_MODE_NONE, current_draw_mode_);
+ current_draw_mode_ = draw_mode;
+ return true;
+}
+
+void LayerImpl::DidDraw(ResourceProvider* resource_provider) {
+ DCHECK_NE(DRAW_MODE_NONE, current_draw_mode_);
+ current_draw_mode_ = DRAW_MODE_NONE;
+}
+
+bool LayerImpl::ShowDebugBorders() const {
+ return layer_tree_impl()->debug_state().show_debug_borders;
+}
+
+void LayerImpl::GetDebugBorderProperties(SkColor* color, float* width) const {
+ if (draws_content_) {
+ *color = DebugColors::ContentLayerBorderColor();
+ *width = DebugColors::ContentLayerBorderWidth(layer_tree_impl());
+ return;
+ }
+
+ if (masks_to_bounds_) {
+ *color = DebugColors::MaskingLayerBorderColor();
+ *width = DebugColors::MaskingLayerBorderWidth(layer_tree_impl());
+ return;
+ }
+
+ *color = DebugColors::ContainerLayerBorderColor();
+ *width = DebugColors::ContainerLayerBorderWidth(layer_tree_impl());
+}
+
+void LayerImpl::AppendDebugBorderQuad(
+ QuadSink* quad_sink,
+ const SharedQuadState* shared_quad_state,
+ AppendQuadsData* append_quads_data) const {
+ SkColor color;
+ float width;
+ GetDebugBorderProperties(&color, &width);
+ AppendDebugBorderQuad(
+ quad_sink, shared_quad_state, append_quads_data, color, width);
+}
+
+void LayerImpl::AppendDebugBorderQuad(QuadSink* quad_sink,
+ const SharedQuadState* shared_quad_state,
+ AppendQuadsData* append_quads_data,
+ SkColor color,
+ float width) const {
+ if (!ShowDebugBorders())
+ return;
+
+ gfx::Rect content_rect(content_bounds());
+ scoped_ptr<DebugBorderDrawQuad> debug_border_quad =
+ DebugBorderDrawQuad::Create();
+ debug_border_quad->SetNew(shared_quad_state, content_rect, color, width);
+ quad_sink->Append(debug_border_quad.PassAs<DrawQuad>(), append_quads_data);
+}
+
+bool LayerImpl::HasDelegatedContent() const {
+ return false;
+}
+
+bool LayerImpl::HasContributingDelegatedRenderPasses() const {
+ return false;
+}
+
+RenderPass::Id LayerImpl::FirstContributingRenderPassId() const {
+ return RenderPass::Id(0, 0);
+}
+
+RenderPass::Id LayerImpl::NextContributingRenderPassId(RenderPass::Id id)
+ const {
+ return RenderPass::Id(0, 0);
+}
+
+ResourceProvider::ResourceId LayerImpl::ContentsResourceId() const {
+ NOTREACHED();
+ return 0;
+}
+
+void LayerImpl::SetSentScrollDelta(gfx::Vector2d sent_scroll_delta) {
+ // Pending tree never has sent scroll deltas
+ DCHECK(layer_tree_impl()->IsActiveTree());
+
+ if (sent_scroll_delta_ == sent_scroll_delta)
+ return;
+
+ sent_scroll_delta_ = sent_scroll_delta;
+}
+
+gfx::Vector2dF LayerImpl::ScrollBy(gfx::Vector2dF scroll) {
+ DCHECK(scrollable());
+
+ gfx::Vector2dF min_delta = -scroll_offset_;
+ gfx::Vector2dF max_delta = max_scroll_offset_ - scroll_offset_;
+ // Clamp new_delta so that position + delta stays within scroll bounds.
+ gfx::Vector2dF new_delta = (ScrollDelta() + scroll);
+ new_delta.SetToMax(min_delta);
+ new_delta.SetToMin(max_delta);
+ gfx::Vector2dF unscrolled = ScrollDelta() + scroll - new_delta;
+ SetScrollDelta(new_delta);
+ return unscrolled;
+}
+
+void LayerImpl::ApplySentScrollDeltas() {
+ // Pending tree never has sent scroll deltas
+ DCHECK(layer_tree_impl()->IsActiveTree());
+
+ // Apply sent scroll deltas to scroll position / scroll delta as if the
+ // main thread had applied them and then committed those values.
+ //
+ // This function should not change the total scroll offset; it just shifts
+ // some of the scroll delta to the scroll offset. Therefore, adjust these
+ // variables directly rather than calling the scroll offset delegate to
+ // avoid sending it multiple spurious calls.
+ //
+ // Because of the way scroll delta is calculated with a delegate, this will
+ // leave the total scroll offset unchanged on this layer regardless of
+ // whether a delegate is being used.
+ scroll_offset_ += sent_scroll_delta_;
+ scroll_delta_ -= sent_scroll_delta_;
+ sent_scroll_delta_ = gfx::Vector2d();
+}
+
+InputHandler::ScrollStatus LayerImpl::TryScroll(
+ gfx::PointF screen_space_point,
+ InputHandler::ScrollInputType type) const {
+ if (should_scroll_on_main_thread()) {
+ TRACE_EVENT0("cc", "LayerImpl::TryScroll: Failed ShouldScrollOnMainThread");
+ return InputHandler::ScrollOnMainThread;
+ }
+
+ if (!screen_space_transform().IsInvertible()) {
+ TRACE_EVENT0("cc", "LayerImpl::TryScroll: Ignored NonInvertibleTransform");
+ return InputHandler::ScrollIgnored;
+ }
+
+ if (!non_fast_scrollable_region().IsEmpty()) {
+ bool clipped = false;
+ gfx::Transform inverse_screen_space_transform(
+ gfx::Transform::kSkipInitialization);
+ if (!screen_space_transform().GetInverse(&inverse_screen_space_transform)) {
+ // TODO(shawnsingh): We shouldn't be applying a projection if screen space
+ // transform is uninvertible here. Perhaps we should be returning
+ // ScrollOnMainThread in this case?
+ }
+
+ gfx::PointF hit_test_point_in_content_space =
+ MathUtil::ProjectPoint(inverse_screen_space_transform,
+ screen_space_point,
+ &clipped);
+ gfx::PointF hit_test_point_in_layer_space =
+ gfx::ScalePoint(hit_test_point_in_content_space,
+ 1.f / contents_scale_x(),
+ 1.f / contents_scale_y());
+ if (!clipped &&
+ non_fast_scrollable_region().Contains(
+ gfx::ToRoundedPoint(hit_test_point_in_layer_space))) {
+ TRACE_EVENT0("cc",
+ "LayerImpl::tryScroll: Failed NonFastScrollableRegion");
+ return InputHandler::ScrollOnMainThread;
+ }
+ }
+
+ if (type == InputHandler::Wheel && have_wheel_event_handlers()) {
+ TRACE_EVENT0("cc", "LayerImpl::tryScroll: Failed WheelEventHandlers");
+ return InputHandler::ScrollOnMainThread;
+ }
+
+ if (!scrollable()) {
+ TRACE_EVENT0("cc", "LayerImpl::tryScroll: Ignored not scrollable");
+ return InputHandler::ScrollIgnored;
+ }
+
+ if (max_scroll_offset_.x() <= 0 && max_scroll_offset_.y() <= 0) {
+ TRACE_EVENT0("cc",
+ "LayerImpl::tryScroll: Ignored. Technically scrollable,"
+ " but has no affordance in either direction.");
+ return InputHandler::ScrollIgnored;
+ }
+
+ return InputHandler::ScrollStarted;
+}
+
+bool LayerImpl::DrawCheckerboardForMissingTiles() const {
+ return draw_checkerboard_for_missing_tiles_ &&
+ !layer_tree_impl()->settings().background_color_instead_of_checkerboard;
+}
+
+gfx::Rect LayerImpl::LayerRectToContentRect(
+ const gfx::RectF& layer_rect) const {
+ gfx::RectF content_rect =
+ gfx::ScaleRect(layer_rect, contents_scale_x(), contents_scale_y());
+ // Intersect with content rect to avoid the extra pixel because for some
+ // values x and y, ceil((x / y) * y) may be x + 1.
+ content_rect.Intersect(gfx::Rect(content_bounds()));
+ return gfx::ToEnclosingRect(content_rect);
+}
+
+skia::RefPtr<SkPicture> LayerImpl::GetPicture() {
+ return skia::RefPtr<SkPicture>();
+}
+
+bool LayerImpl::CanClipSelf() const {
+ return false;
+}
+
+bool LayerImpl::AreVisibleResourcesReady() const {
+ return true;
+}
+
+scoped_ptr<LayerImpl> LayerImpl::CreateLayerImpl(LayerTreeImpl* tree_impl) {
+ return LayerImpl::Create(tree_impl, layer_id_);
+}
+
+void LayerImpl::PushPropertiesTo(LayerImpl* layer) {
+ layer->SetAnchorPoint(anchor_point_);
+ layer->SetAnchorPointZ(anchor_point_z_);
+ layer->SetBackgroundColor(background_color_);
+ layer->SetBounds(bounds_);
+ layer->SetContentBounds(content_bounds());
+ layer->SetContentsScale(contents_scale_x(), contents_scale_y());
+ layer->SetDebugName(debug_name_);
+ layer->SetCompositingReasons(compositing_reasons_);
+ layer->SetDoubleSided(double_sided_);
+ layer->SetDrawCheckerboardForMissingTiles(
+ draw_checkerboard_for_missing_tiles_);
+ layer->SetForceRenderSurface(force_render_surface_);
+ layer->SetDrawsContent(DrawsContent());
+ layer->SetHideLayerAndSubtree(hide_layer_and_subtree_);
+ layer->SetFilters(filters());
+ layer->SetFilter(filter());
+ layer->SetBackgroundFilters(background_filters());
+ layer->SetMasksToBounds(masks_to_bounds_);
+ layer->SetShouldScrollOnMainThread(should_scroll_on_main_thread_);
+ layer->SetHaveWheelEventHandlers(have_wheel_event_handlers_);
+ layer->SetNonFastScrollableRegion(non_fast_scrollable_region_);
+ layer->SetTouchEventHandlerRegion(touch_event_handler_region_);
+ layer->SetContentsOpaque(contents_opaque_);
+ layer->SetOpacity(opacity_);
+ layer->SetPosition(position_);
+ layer->SetIsContainerForFixedPositionLayers(
+ is_container_for_fixed_position_layers_);
+ layer->SetFixedContainerSizeDelta(fixed_container_size_delta_);
+ layer->SetPositionConstraint(position_constraint_);
+ layer->SetPreserves3d(preserves_3d());
+ layer->SetUseParentBackfaceVisibility(use_parent_backface_visibility_);
+ layer->SetSublayerTransform(sublayer_transform_);
+ layer->SetTransform(transform_);
+
+ layer->SetScrollable(scrollable_);
+ layer->SetScrollOffset(scroll_offset_);
+ layer->SetMaxScrollOffset(max_scroll_offset_);
+
+ layer->PassCopyRequests(&copy_requests_);
+
+ // If the main thread commits multiple times before the impl thread actually
+ // draws, then damage tracking will become incorrect if we simply clobber the
+ // update_rect here. The LayerImpl's update_rect needs to accumulate (i.e.
+ // union) any update changes that have occurred on the main thread.
+ update_rect_.Union(layer->update_rect());
+ layer->set_update_rect(update_rect_);
+
+ layer->SetScrollDelta(layer->ScrollDelta() - layer->sent_scroll_delta());
+ layer->SetSentScrollDelta(gfx::Vector2d());
+
+ layer->SetStackingOrderChanged(stacking_order_changed_);
+
+ // Reset any state that should be cleared for the next update.
+ stacking_order_changed_ = false;
+ update_rect_ = gfx::RectF();
+}
+
+base::DictionaryValue* LayerImpl::LayerTreeAsJson() const {
+ base::DictionaryValue* result = new base::DictionaryValue;
+ result->SetString("LayerType", LayerTypeAsString());
+
+ base::ListValue* list = new base::ListValue;
+ list->AppendInteger(bounds().width());
+ list->AppendInteger(bounds().height());
+ result->Set("Bounds", list);
+
+ list = new base::ListValue;
+ list->AppendDouble(position_.x());
+ list->AppendDouble(position_.y());
+ result->Set("Position", list);
+
+ const gfx::Transform& gfx_transform = draw_properties_.target_space_transform;
+ double transform[16];
+ gfx_transform.matrix().asColMajord(transform);
+ list = new base::ListValue;
+ for (int i = 0; i < 16; ++i)
+ list->AppendDouble(transform[i]);
+ result->Set("DrawTransform", list);
+
+ result->SetBoolean("DrawsContent", draws_content_);
+ result->SetDouble("Opacity", opacity());
+ result->SetBoolean("ContentsOpaque", contents_opaque_);
+
+ if (scrollable_)
+ result->SetBoolean("Scrollable", scrollable_);
+
+ list = new base::ListValue;
+ for (size_t i = 0; i < children_.size(); ++i)
+ list->Append(children_[i]->LayerTreeAsJson());
+ result->Set("Children", list);
+
+ return result;
+}
+
+void LayerImpl::SetStackingOrderChanged(bool stacking_order_changed) {
+ if (stacking_order_changed) {
+ stacking_order_changed_ = true;
+ NoteLayerPropertyChangedForSubtree();
+ }
+}
+
+bool LayerImpl::LayerSurfacePropertyChanged() const {
+ if (layer_surface_property_changed_)
+ return true;
+
+ // If this layer's surface property hasn't changed, we want to see if
+ // some layer above us has changed this property. This is done for the
+ // case when such parent layer does not draw content, and therefore will
+ // not be traversed by the damage tracker. We need to make sure that
+ // property change on such layer will be caught by its descendants.
+ LayerImpl* current = this->parent_;
+ while (current && !current->draw_properties_.render_surface) {
+ if (current->layer_surface_property_changed_)
+ return true;
+ current = current->parent_;
+ }
+
+ return false;
+}
+
+void LayerImpl::NoteLayerSurfacePropertyChanged() {
+ layer_surface_property_changed_ = true;
+ layer_tree_impl()->set_needs_update_draw_properties();
+}
+
+void LayerImpl::NoteLayerPropertyChanged() {
+ layer_property_changed_ = true;
+ layer_tree_impl()->set_needs_update_draw_properties();
+}
+
+void LayerImpl::NoteLayerPropertyChangedForSubtree() {
+ NoteLayerPropertyChanged();
+ NoteLayerPropertyChangedForDescendants();
+}
+
+void LayerImpl::NoteLayerPropertyChangedForDescendants() {
+ layer_tree_impl()->set_needs_update_draw_properties();
+ for (size_t i = 0; i < children_.size(); ++i)
+ children_[i]->NoteLayerPropertyChangedForSubtree();
+}
+
+const char* LayerImpl::LayerTypeAsString() const {
+ return "cc::LayerImpl";
+}
+
+void LayerImpl::ResetAllChangeTrackingForSubtree() {
+ layer_property_changed_ = false;
+ layer_surface_property_changed_ = false;
+
+ update_rect_ = gfx::RectF();
+
+ if (draw_properties_.render_surface)
+ draw_properties_.render_surface->ResetPropertyChangedFlag();
+
+ if (mask_layer_)
+ mask_layer_->ResetAllChangeTrackingForSubtree();
+
+ if (replica_layer_) {
+ // This also resets the replica mask, if it exists.
+ replica_layer_->ResetAllChangeTrackingForSubtree();
+ }
+
+ for (size_t i = 0; i < children_.size(); ++i)
+ children_[i]->ResetAllChangeTrackingForSubtree();
+}
+
+bool LayerImpl::LayerIsAlwaysDamaged() const {
+ return false;
+}
+
+void LayerImpl::OnOpacityAnimated(float opacity) {
+ SetOpacity(opacity);
+}
+
+void LayerImpl::OnTransformAnimated(const gfx::Transform& transform) {
+ SetTransform(transform);
+}
+
+bool LayerImpl::IsActive() const {
+ return layer_tree_impl_->IsActiveTree();
+}
+
+void LayerImpl::SetBounds(gfx::Size bounds) {
+ if (bounds_ == bounds)
+ return;
+
+ bounds_ = bounds;
+
+ if (masks_to_bounds())
+ NoteLayerPropertyChangedForSubtree();
+ else
+ NoteLayerPropertyChanged();
+}
+
+void LayerImpl::SetMaskLayer(scoped_ptr<LayerImpl> mask_layer) {
+ int new_layer_id = mask_layer ? mask_layer->id() : -1;
+
+ if (mask_layer) {
+ DCHECK_EQ(layer_tree_impl(), mask_layer->layer_tree_impl());
+ DCHECK_NE(new_layer_id, mask_layer_id_);
+ } else if (new_layer_id == mask_layer_id_) {
+ return;
+ }
+
+ mask_layer_ = mask_layer.Pass();
+ mask_layer_id_ = new_layer_id;
+ if (mask_layer_)
+ mask_layer_->set_parent(this);
+ NoteLayerPropertyChangedForSubtree();
+}
+
+scoped_ptr<LayerImpl> LayerImpl::TakeMaskLayer() {
+ mask_layer_id_ = -1;
+ return mask_layer_.Pass();
+}
+
+void LayerImpl::SetReplicaLayer(scoped_ptr<LayerImpl> replica_layer) {
+ int new_layer_id = replica_layer ? replica_layer->id() : -1;
+
+ if (replica_layer) {
+ DCHECK_EQ(layer_tree_impl(), replica_layer->layer_tree_impl());
+ DCHECK_NE(new_layer_id, replica_layer_id_);
+ } else if (new_layer_id == replica_layer_id_) {
+ return;
+ }
+
+ replica_layer_ = replica_layer.Pass();
+ replica_layer_id_ = new_layer_id;
+ if (replica_layer_)
+ replica_layer_->set_parent(this);
+ NoteLayerPropertyChangedForSubtree();
+}
+
+scoped_ptr<LayerImpl> LayerImpl::TakeReplicaLayer() {
+ replica_layer_id_ = -1;
+ return replica_layer_.Pass();
+}
+
+ScrollbarLayerImpl* LayerImpl::ToScrollbarLayer() {
+ return NULL;
+}
+
+void LayerImpl::SetDrawsContent(bool draws_content) {
+ if (draws_content_ == draws_content)
+ return;
+
+ draws_content_ = draws_content;
+ NoteLayerPropertyChanged();
+}
+
+void LayerImpl::SetHideLayerAndSubtree(bool hide) {
+ if (hide_layer_and_subtree_ == hide)
+ return;
+
+ hide_layer_and_subtree_ = hide;
+ NoteLayerPropertyChangedForSubtree();
+}
+
+void LayerImpl::SetAnchorPoint(gfx::PointF anchor_point) {
+ if (anchor_point_ == anchor_point)
+ return;
+
+ anchor_point_ = anchor_point;
+ NoteLayerPropertyChangedForSubtree();
+}
+
+void LayerImpl::SetAnchorPointZ(float anchor_point_z) {
+ if (anchor_point_z_ == anchor_point_z)
+ return;
+
+ anchor_point_z_ = anchor_point_z;
+ NoteLayerPropertyChangedForSubtree();
+}
+
+void LayerImpl::SetBackgroundColor(SkColor background_color) {
+ if (background_color_ == background_color)
+ return;
+
+ background_color_ = background_color;
+ NoteLayerPropertyChanged();
+}
+
+SkColor LayerImpl::SafeOpaqueBackgroundColor() const {
+ SkColor color = background_color();
+ if (SkColorGetA(color) == 255 && !contents_opaque()) {
+ color = SK_ColorTRANSPARENT;
+ } else if (SkColorGetA(color) != 255 && contents_opaque()) {
+ for (const LayerImpl* layer = parent(); layer;
+ layer = layer->parent()) {
+ color = layer->background_color();
+ if (SkColorGetA(color) == 255)
+ break;
+ }
+ if (SkColorGetA(color) != 255)
+ color = layer_tree_impl()->background_color();
+ if (SkColorGetA(color) != 255)
+ color = SkColorSetA(color, 255);
+ }
+ return color;
+}
+
+void LayerImpl::SetFilters(const FilterOperations& filters) {
+ if (filters_ == filters)
+ return;
+
+ DCHECK(!filter_);
+ filters_ = filters;
+ NoteLayerPropertyChangedForSubtree();
+}
+
+void LayerImpl::SetBackgroundFilters(
+ const FilterOperations& filters) {
+ if (background_filters_ == filters)
+ return;
+
+ background_filters_ = filters;
+ NoteLayerPropertyChanged();
+}
+
+void LayerImpl::SetFilter(const skia::RefPtr<SkImageFilter>& filter) {
+ if (filter_.get() == filter.get())
+ return;
+
+ DCHECK(filters_.IsEmpty());
+ filter_ = filter;
+ NoteLayerPropertyChangedForSubtree();
+}
+
+void LayerImpl::SetMasksToBounds(bool masks_to_bounds) {
+ if (masks_to_bounds_ == masks_to_bounds)
+ return;
+
+ masks_to_bounds_ = masks_to_bounds;
+ NoteLayerPropertyChangedForSubtree();
+}
+
+void LayerImpl::SetContentsOpaque(bool opaque) {
+ if (contents_opaque_ == opaque)
+ return;
+
+ contents_opaque_ = opaque;
+ NoteLayerPropertyChangedForSubtree();
+}
+
+void LayerImpl::SetOpacity(float opacity) {
+ if (opacity_ == opacity)
+ return;
+
+ opacity_ = opacity;
+ NoteLayerSurfacePropertyChanged();
+}
+
+bool LayerImpl::OpacityIsAnimating() const {
+ return layer_animation_controller_->IsAnimatingProperty(Animation::Opacity);
+}
+
+bool LayerImpl::OpacityIsAnimatingOnImplOnly() const {
+ Animation* opacity_animation =
+ layer_animation_controller_->GetAnimation(Animation::Opacity);
+ return opacity_animation && opacity_animation->is_impl_only();
+}
+
+void LayerImpl::SetPosition(gfx::PointF position) {
+ if (position_ == position)
+ return;
+
+ position_ = position;
+ NoteLayerPropertyChangedForSubtree();
+}
+
+void LayerImpl::SetPreserves3d(bool preserves3_d) {
+ if (preserves_3d_ == preserves3_d)
+ return;
+
+ preserves_3d_ = preserves3_d;
+ NoteLayerPropertyChangedForSubtree();
+}
+
+void LayerImpl::SetSublayerTransform(const gfx::Transform& sublayer_transform) {
+ if (sublayer_transform_ == sublayer_transform)
+ return;
+
+ sublayer_transform_ = sublayer_transform;
+ // Sublayer transform does not affect the current layer; it affects only its
+ // children.
+ NoteLayerPropertyChangedForDescendants();
+}
+
+void LayerImpl::SetTransform(const gfx::Transform& transform) {
+ if (transform_ == transform)
+ return;
+
+ transform_ = transform;
+ NoteLayerSurfacePropertyChanged();
+}
+
+bool LayerImpl::TransformIsAnimating() const {
+ return layer_animation_controller_->IsAnimatingProperty(Animation::Transform);
+}
+
+bool LayerImpl::TransformIsAnimatingOnImplOnly() const {
+ Animation* transform_animation =
+ layer_animation_controller_->GetAnimation(Animation::Transform);
+ return transform_animation && transform_animation->is_impl_only();
+}
+
+void LayerImpl::SetContentBounds(gfx::Size content_bounds) {
+ if (this->content_bounds() == content_bounds)
+ return;
+
+ draw_properties_.content_bounds = content_bounds;
+ NoteLayerPropertyChanged();
+}
+
+void LayerImpl::SetContentsScale(float contents_scale_x,
+ float contents_scale_y) {
+ if (this->contents_scale_x() == contents_scale_x &&
+ this->contents_scale_y() == contents_scale_y)
+ return;
+
+ draw_properties_.contents_scale_x = contents_scale_x;
+ draw_properties_.contents_scale_y = contents_scale_y;
+ NoteLayerPropertyChanged();
+}
+
+void LayerImpl::CalculateContentsScale(
+ float ideal_contents_scale,
+ float device_scale_factor,
+ float page_scale_factor,
+ bool animating_transform_to_screen,
+ float* contents_scale_x,
+ float* contents_scale_y,
+ gfx::Size* content_bounds) {
+ // Base LayerImpl has all of its content scales and content bounds pushed
+ // from its Layer during commit and just reuses those values as-is.
+ *contents_scale_x = this->contents_scale_x();
+ *contents_scale_y = this->contents_scale_y();
+ *content_bounds = this->content_bounds();
+}
+
+void LayerImpl::UpdateScrollbarPositions() {
+ gfx::Vector2dF current_offset = scroll_offset_ + ScrollDelta();
+
+ gfx::RectF viewport(PointAtOffsetFromOrigin(current_offset), bounds_);
+ gfx::SizeF scrollable_size(max_scroll_offset_.x() + bounds_.width(),
+ max_scroll_offset_.y() + bounds_.height());
+ if (horizontal_scrollbar_layer_) {
+ horizontal_scrollbar_layer_->SetCurrentPos(current_offset.x());
+ horizontal_scrollbar_layer_->SetMaximum(max_scroll_offset_.x());
+ horizontal_scrollbar_layer_->SetVisibleToTotalLengthRatio(
+ viewport.width() / scrollable_size.width());
+ }
+ if (vertical_scrollbar_layer_) {
+ vertical_scrollbar_layer_->SetCurrentPos(current_offset.y());
+ vertical_scrollbar_layer_->SetMaximum(max_scroll_offset_.y());
+ vertical_scrollbar_layer_->SetVisibleToTotalLengthRatio(
+ viewport.height() / scrollable_size.height());
+ }
+
+ if (current_offset == last_scroll_offset_)
+ return;
+ last_scroll_offset_ = current_offset;
+
+ if (scrollbar_animation_controller_ &&
+ !scrollbar_animation_controller_->IsScrollGestureInProgress()) {
+ scrollbar_animation_controller_->DidProgrammaticallyUpdateScroll(
+ layer_tree_impl_->CurrentPhysicalTimeTicks());
+ }
+
+ // Get the current_offset_.y() value for a sanity-check on scrolling
+ // benchmark metrics. Specifically, we want to make sure
+ // BasicMouseWheelSmoothScrollGesture has proper scroll curves.
+ if (layer_tree_impl()->IsActiveTree()) {
+ TRACE_COUNTER_ID1("gpu", "scroll_offset_y", this->id(), current_offset.y());
+ }
+}
+
+void LayerImpl::SetScrollOffsetDelegate(
+ LayerScrollOffsetDelegate* scroll_offset_delegate) {
+ if (!scroll_offset_delegate && scroll_offset_delegate_) {
+ scroll_delta_ =
+ scroll_offset_delegate_->GetTotalScrollOffset() - scroll_offset_;
+ }
+ gfx::Vector2dF total_offset = TotalScrollOffset();
+ scroll_offset_delegate_ = scroll_offset_delegate;
+ if (scroll_offset_delegate_)
+ scroll_offset_delegate_->SetTotalScrollOffset(total_offset);
+}
+
+void LayerImpl::SetScrollOffset(gfx::Vector2d scroll_offset) {
+ if (scroll_offset_ == scroll_offset)
+ return;
+
+ scroll_offset_ = scroll_offset;
+
+ if (scroll_offset_delegate_)
+ scroll_offset_delegate_->SetTotalScrollOffset(TotalScrollOffset());
+
+ NoteLayerPropertyChangedForSubtree();
+ UpdateScrollbarPositions();
+}
+
+gfx::Vector2dF LayerImpl::ScrollDelta() const {
+ if (scroll_offset_delegate_)
+ return scroll_offset_delegate_->GetTotalScrollOffset() - scroll_offset_;
+ return scroll_delta_;
+}
+
+void LayerImpl::SetScrollDelta(gfx::Vector2dF scroll_delta) {
+ if (ScrollDelta() == scroll_delta)
+ return;
+
+ if (layer_tree_impl()->IsActiveTree()) {
+ LayerImpl* pending_twin = layer_tree_impl()->FindPendingTreeLayerById(id());
+ if (pending_twin) {
+ // The pending twin can't mirror the scroll delta of the active
+ // layer. Although the delta - sent scroll delta difference is
+ // identical for both twins, the sent scroll delta for the pending
+ // layer is zero, as anything that has been sent has been baked
+ // into the layer's position/scroll offset as a part of commit.
+ DCHECK(pending_twin->sent_scroll_delta().IsZero());
+ pending_twin->SetScrollDelta(scroll_delta - sent_scroll_delta());
+ }
+ }
+
+ if (scroll_offset_delegate_) {
+ scroll_offset_delegate_->SetTotalScrollOffset(
+ scroll_offset_ + scroll_delta);
+ } else {
+ scroll_delta_ = scroll_delta;
+ }
+
+ NoteLayerPropertyChangedForSubtree();
+ UpdateScrollbarPositions();
+}
+
+gfx::Vector2dF LayerImpl::TotalScrollOffset() const {
+ return scroll_offset_ + ScrollDelta();
+}
+
+void LayerImpl::SetDoubleSided(bool double_sided) {
+ if (double_sided_ == double_sided)
+ return;
+
+ double_sided_ = double_sided;
+ NoteLayerPropertyChangedForSubtree();
+}
+
+Region LayerImpl::VisibleContentOpaqueRegion() const {
+ if (contents_opaque())
+ return visible_content_rect();
+ return Region();
+}
+
+void LayerImpl::DidBeginTracing() {}
+
+void LayerImpl::DidLoseOutputSurface() {}
+
+void LayerImpl::SetMaxScrollOffset(gfx::Vector2d max_scroll_offset) {
+ if (max_scroll_offset_ == max_scroll_offset)
+ return;
+ max_scroll_offset_ = max_scroll_offset;
+
+ layer_tree_impl()->set_needs_update_draw_properties();
+ UpdateScrollbarPositions();
+}
+
+void LayerImpl::SetScrollbarOpacity(float opacity) {
+ if (horizontal_scrollbar_layer_)
+ horizontal_scrollbar_layer_->SetOpacity(opacity);
+ if (vertical_scrollbar_layer_)
+ vertical_scrollbar_layer_->SetOpacity(opacity);
+}
+
+void LayerImpl::DidBecomeActive() {
+ if (!layer_tree_impl_->settings().use_linear_fade_scrollbar_animator)
+ return;
+
+ bool need_scrollbar_animation_controller = horizontal_scrollbar_layer_ ||
+ vertical_scrollbar_layer_;
+ if (need_scrollbar_animation_controller) {
+ if (!scrollbar_animation_controller_) {
+ base::TimeDelta fadeout_delay = base::TimeDelta::FromMilliseconds(
+ layer_tree_impl_->settings().scrollbar_linear_fade_delay_ms);
+ base::TimeDelta fadeout_length = base::TimeDelta::FromMilliseconds(
+ layer_tree_impl_->settings().scrollbar_linear_fade_length_ms);
+ scrollbar_animation_controller_ =
+ ScrollbarAnimationControllerLinearFade::Create(
+ this, fadeout_delay, fadeout_length)
+ .PassAs<ScrollbarAnimationController>();
+ }
+ } else {
+ scrollbar_animation_controller_.reset();
+ }
+}
+void LayerImpl::SetHorizontalScrollbarLayer(
+ ScrollbarLayerImpl* scrollbar_layer) {
+ horizontal_scrollbar_layer_ = scrollbar_layer;
+ if (horizontal_scrollbar_layer_)
+ horizontal_scrollbar_layer_->set_scroll_layer_id(id());
+}
+
+void LayerImpl::SetVerticalScrollbarLayer(ScrollbarLayerImpl* scrollbar_layer) {
+ vertical_scrollbar_layer_ = scrollbar_layer;
+ if (vertical_scrollbar_layer_)
+ vertical_scrollbar_layer_->set_scroll_layer_id(id());
+}
+
+static scoped_ptr<base::Value>
+CompositingReasonsAsValue(CompositingReasons reasons) {
+ scoped_ptr<base::ListValue> reason_list(new base::ListValue());
+
+ if (reasons == kCompositingReasonUnknown) {
+ reason_list->AppendString("No reasons given");
+ return reason_list.PassAs<base::Value>();
+ }
+
+ if (reasons & kCompositingReason3DTransform)
+ reason_list->AppendString("Has a 3d Transform");
+
+ if (reasons & kCompositingReasonVideo)
+ reason_list->AppendString("Is accelerated video");
+
+ if (reasons & kCompositingReasonCanvas)
+ reason_list->AppendString("Is accelerated canvas");
+
+ if (reasons & kCompositingReasonPlugin)
+ reason_list->AppendString("Is accelerated plugin");
+
+ if (reasons & kCompositingReasonIFrame)
+ reason_list->AppendString("Is accelerated iframe");
+
+ if (reasons & kCompositingReasonBackfaceVisibilityHidden)
+ reason_list->AppendString("Has backface-visibility: hidden");
+
+ if (reasons & kCompositingReasonAnimation)
+ reason_list->AppendString("Has accelerated animation or transition");
+
+ if (reasons & kCompositingReasonFilters)
+ reason_list->AppendString("Has accelerated filters");
+
+ if (reasons & kCompositingReasonPositionFixed)
+ reason_list->AppendString("Is fixed position");
+
+ if (reasons & kCompositingReasonPositionSticky)
+ reason_list->AppendString("Is sticky position");
+
+ if (reasons & kCompositingReasonOverflowScrollingTouch)
+ reason_list->AppendString("Is a scrollable overflow element");
+
+ if (reasons & kCompositingReasonBlending)
+ reason_list->AppendString("Has a blend mode");
+
+ if (reasons & kCompositingReasonAssumedOverlap)
+ reason_list->AppendString("Might overlap a composited animation");
+
+ if (reasons & kCompositingReasonOverlap)
+ reason_list->AppendString("Overlaps other composited content");
+
+ if (reasons & kCompositingReasonNegativeZIndexChildren) {
+ reason_list->AppendString("Might overlap negative z-index "
+ "composited content");
+ }
+
+ if (reasons & kCompositingReasonTransformWithCompositedDescendants) {
+ reason_list->AppendString("Has transform needed by a "
+ "composited descendant");
+ }
+
+ if (reasons & kCompositingReasonOpacityWithCompositedDescendants)
+ reason_list->AppendString("Has opacity needed by a composited descendant");
+
+ if (reasons & kCompositingReasonMaskWithCompositedDescendants)
+ reason_list->AppendString("Has a mask needed by a composited descendant");
+
+ if (reasons & kCompositingReasonReflectionWithCompositedDescendants)
+ reason_list->AppendString("Has a reflection with a composited descendant");
+
+ if (reasons & kCompositingReasonFilterWithCompositedDescendants)
+ reason_list->AppendString("Has filter effect with a composited descendant");
+
+ if (reasons & kCompositingReasonBlendingWithCompositedDescendants)
+ reason_list->AppendString("Has a blend mode with a composited descendant");
+
+ if (reasons & kCompositingReasonClipsCompositingDescendants)
+ reason_list->AppendString("Clips a composited descendant");
+
+ if (reasons & kCompositingReasonPerspective) {
+ reason_list->AppendString("Has a perspective transform needed by a "
+ "composited 3d descendant");
+ }
+
+ if (reasons & kCompositingReasonPreserve3D) {
+ reason_list->AppendString("Has preserves-3d style with composited "
+ "3d descendant");
+ }
+
+ if (reasons & kCompositingReasonReflectionOfCompositedParent)
+ reason_list->AppendString("Is the reflection of a composited layer");
+
+ if (reasons & kCompositingReasonRoot)
+ reason_list->AppendString("Is the root");
+
+ if (reasons & kCompositingReasonLayerForClip)
+ reason_list->AppendString("Convenience layer, to clip subtree");
+
+ if (reasons & kCompositingReasonLayerForScrollbar)
+ reason_list->AppendString("Convenience layer for rendering scrollbar");
+
+ if (reasons & kCompositingReasonLayerForScrollingContainer)
+ reason_list->AppendString("Convenience layer, the scrolling container");
+
+ if (reasons & kCompositingReasonLayerForForeground) {
+ reason_list->AppendString("Convenience layer, foreground when main layer "
+ "has negative z-index composited content");
+ }
+
+ if (reasons & kCompositingReasonLayerForBackground) {
+ reason_list->AppendString("Convenience layer, background when main layer "
+ "has a composited background");
+ }
+
+ if (reasons & kCompositingReasonLayerForMask)
+ reason_list->AppendString("Is a mask layer");
+
+ return reason_list.PassAs<base::Value>();
+}
+
+void LayerImpl::AsValueInto(base::DictionaryValue* state) const {
+ TracedValue::MakeDictIntoImplicitSnapshot(state, LayerTypeAsString(), this);
+ state->SetInteger("layer_id", id());
+ state->Set("bounds", MathUtil::AsValue(bounds()).release());
+ state->SetInteger("draws_content", DrawsContent());
+ state->SetInteger("gpu_memory_usage", GPUMemoryUsageInBytes());
+ state->Set("compositing_reasons",
+ CompositingReasonsAsValue(compositing_reasons_).release());
+
+ bool clipped;
+ gfx::QuadF layer_quad = MathUtil::MapQuad(
+ screen_space_transform(),
+ gfx::QuadF(gfx::Rect(content_bounds())),
+ &clipped);
+ state->Set("layer_quad", MathUtil::AsValue(layer_quad).release());
+
+
+ scoped_ptr<base::ListValue> children_list(new base::ListValue());
+ for (size_t i = 0; i < children_.size(); ++i)
+ children_list->Append(children_[i]->AsValue().release());
+ state->Set("children", children_list.release());
+ if (mask_layer_)
+ state->Set("mask_layer", mask_layer_->AsValue().release());
+ if (replica_layer_)
+ state->Set("replica_layer", replica_layer_->AsValue().release());
+}
+
+size_t LayerImpl::GPUMemoryUsageInBytes() const { return 0; }
+
+scoped_ptr<base::Value> LayerImpl::AsValue() const {
+ scoped_ptr<base::DictionaryValue> state(new base::DictionaryValue());
+ AsValueInto(state.get());
+ return state.PassAs<base::Value>();
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/layer_impl.h b/chromium/cc/layers/layer_impl.h
new file mode 100644
index 00000000000..90da41a1c6c
--- /dev/null
+++ b/chromium/cc/layers/layer_impl.h
@@ -0,0 +1,584 @@
+// Copyright 2011 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.
+
+#ifndef CC_LAYERS_LAYER_IMPL_H_
+#define CC_LAYERS_LAYER_IMPL_H_
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "cc/animation/layer_animation_controller.h"
+#include "cc/animation/layer_animation_value_observer.h"
+#include "cc/base/cc_export.h"
+#include "cc/base/region.h"
+#include "cc/base/scoped_ptr_vector.h"
+#include "cc/input/input_handler.h"
+#include "cc/layers/compositing_reasons.h"
+#include "cc/layers/draw_properties.h"
+#include "cc/layers/layer_lists.h"
+#include "cc/layers/layer_position_constraint.h"
+#include "cc/layers/render_surface_impl.h"
+#include "cc/output/filter_operations.h"
+#include "cc/quads/render_pass.h"
+#include "cc/quads/shared_quad_state.h"
+#include "cc/resources/resource_provider.h"
+#include "skia/ext/refptr.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/skia/include/core/SkImageFilter.h"
+#include "third_party/skia/include/core/SkPicture.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/rect_f.h"
+#include "ui/gfx/transform.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace cc {
+
+class LayerTreeHostImpl;
+class LayerTreeImpl;
+class QuadSink;
+class Renderer;
+class ScrollbarAnimationController;
+class ScrollbarLayerImpl;
+class Layer;
+
+struct AppendQuadsData;
+
+enum DrawMode {
+ DRAW_MODE_NONE,
+ DRAW_MODE_HARDWARE,
+ DRAW_MODE_SOFTWARE,
+ DRAW_MODE_RESOURCELESS_SOFTWARE
+};
+
+class CC_EXPORT LayerImpl : LayerAnimationValueObserver {
+ public:
+ static scoped_ptr<LayerImpl> Create(LayerTreeImpl* tree_impl, int id) {
+ return make_scoped_ptr(new LayerImpl(tree_impl, id));
+ }
+
+ virtual ~LayerImpl();
+
+ int id() const { return layer_id_; }
+
+ // LayerAnimationValueObserver implementation.
+ virtual void OnOpacityAnimated(float opacity) OVERRIDE;
+ virtual void OnTransformAnimated(const gfx::Transform& transform) OVERRIDE;
+ virtual bool IsActive() const OVERRIDE;
+
+ // Tree structure.
+ LayerImpl* parent() { return parent_; }
+ const LayerImpl* parent() const { return parent_; }
+ const OwnedLayerImplList& children() const { return children_; }
+ OwnedLayerImplList& children() { return children_; }
+ LayerImpl* child_at(size_t index) const { return children_[index]; }
+ void AddChild(scoped_ptr<LayerImpl> child);
+ scoped_ptr<LayerImpl> RemoveChild(LayerImpl* child);
+ void set_parent(LayerImpl* parent) { parent_ = parent; }
+ // Warning: This does not preserve tree structure invariants.
+ void ClearChildList();
+
+ void PassCopyRequests(ScopedPtrVector<CopyOutputRequest>* requests);
+ void TakeCopyRequestsAndTransformToTarget(
+ ScopedPtrVector<CopyOutputRequest>* request);
+ bool HasCopyRequest() const { return !copy_requests_.empty(); }
+
+ void SetMaskLayer(scoped_ptr<LayerImpl> mask_layer);
+ LayerImpl* mask_layer() { return mask_layer_.get(); }
+ const LayerImpl* mask_layer() const { return mask_layer_.get(); }
+ scoped_ptr<LayerImpl> TakeMaskLayer();
+
+ void SetReplicaLayer(scoped_ptr<LayerImpl> replica_layer);
+ LayerImpl* replica_layer() { return replica_layer_.get(); }
+ const LayerImpl* replica_layer() const { return replica_layer_.get(); }
+ scoped_ptr<LayerImpl> TakeReplicaLayer();
+
+ bool has_mask() const { return mask_layer_; }
+ bool has_replica() const { return replica_layer_; }
+ bool replica_has_mask() const {
+ return replica_layer_ && (mask_layer_ || replica_layer_->mask_layer_);
+ }
+
+ LayerTreeImpl* layer_tree_impl() const { return layer_tree_impl_; }
+
+ scoped_ptr<SharedQuadState> CreateSharedQuadState() const;
+ // WillDraw must be called before AppendQuads. If WillDraw returns false,
+ // AppendQuads and DidDraw will not be called. If WillDraw returns true,
+ // DidDraw is guaranteed to be called before another WillDraw or before
+ // the layer is destroyed. To enforce this, any class that overrides
+ // WillDraw/DidDraw must call the base class version only if WillDraw
+ // returns true.
+ virtual bool WillDraw(DrawMode draw_mode,
+ ResourceProvider* resource_provider);
+ virtual void AppendQuads(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data) {}
+ virtual void DidDraw(ResourceProvider* resource_provider);
+
+ virtual ResourceProvider::ResourceId ContentsResourceId() const;
+
+ virtual bool HasDelegatedContent() const;
+ virtual bool HasContributingDelegatedRenderPasses() const;
+ virtual RenderPass::Id FirstContributingRenderPassId() const;
+ virtual RenderPass::Id NextContributingRenderPassId(RenderPass::Id id) const;
+
+ virtual void UpdateTilePriorities() {}
+
+ virtual ScrollbarLayerImpl* ToScrollbarLayer();
+
+ // Returns true if this layer has content to draw.
+ void SetDrawsContent(bool draws_content);
+ bool DrawsContent() const { return draws_content_; }
+
+ void SetHideLayerAndSubtree(bool hide);
+ bool hide_layer_and_subtree() const { return hide_layer_and_subtree_; }
+
+ bool force_render_surface() const { return force_render_surface_; }
+ void SetForceRenderSurface(bool force) { force_render_surface_ = force; }
+
+ void SetAnchorPoint(gfx::PointF anchor_point);
+ gfx::PointF anchor_point() const { return anchor_point_; }
+
+ void SetAnchorPointZ(float anchor_point_z);
+ float anchor_point_z() const { return anchor_point_z_; }
+
+ void SetBackgroundColor(SkColor background_color);
+ SkColor background_color() const { return background_color_; }
+ // If contents_opaque(), return an opaque color else return a
+ // non-opaque color. Tries to return background_color(), if possible.
+ SkColor SafeOpaqueBackgroundColor() const;
+
+ void SetFilters(const FilterOperations& filters);
+ const FilterOperations& filters() const { return filters_; }
+
+ void SetBackgroundFilters(const FilterOperations& filters);
+ const FilterOperations& background_filters() const {
+ return background_filters_;
+ }
+
+ void SetFilter(const skia::RefPtr<SkImageFilter>& filter);
+ skia::RefPtr<SkImageFilter> filter() const { return filter_; }
+
+ void SetMasksToBounds(bool masks_to_bounds);
+ bool masks_to_bounds() const { return masks_to_bounds_; }
+
+ void SetContentsOpaque(bool opaque);
+ bool contents_opaque() const { return contents_opaque_; }
+
+ void SetOpacity(float opacity);
+ float opacity() const { return opacity_; }
+ bool OpacityIsAnimating() const;
+ bool OpacityIsAnimatingOnImplOnly() const;
+
+ void SetPosition(gfx::PointF position);
+ gfx::PointF position() const { return position_; }
+
+ void SetIsContainerForFixedPositionLayers(bool container) {
+ is_container_for_fixed_position_layers_ = container;
+ }
+ // This is a non-trivial function in Layer.
+ bool IsContainerForFixedPositionLayers() const {
+ return is_container_for_fixed_position_layers_;
+ }
+
+ void SetFixedContainerSizeDelta(gfx::Vector2dF delta) {
+ fixed_container_size_delta_ = delta;
+ }
+ gfx::Vector2dF fixed_container_size_delta() const {
+ return fixed_container_size_delta_;
+ }
+
+ void SetPositionConstraint(const LayerPositionConstraint& constraint) {
+ position_constraint_ = constraint;
+ }
+ const LayerPositionConstraint& position_constraint() const {
+ return position_constraint_;
+ }
+
+ void SetPreserves3d(bool preserves_3d);
+ bool preserves_3d() const { return preserves_3d_; }
+
+ void SetUseParentBackfaceVisibility(bool use) {
+ use_parent_backface_visibility_ = use;
+ }
+ bool use_parent_backface_visibility() const {
+ return use_parent_backface_visibility_;
+ }
+
+ void SetSublayerTransform(const gfx::Transform& sublayer_transform);
+ const gfx::Transform& sublayer_transform() const {
+ return sublayer_transform_;
+ }
+
+ // Debug layer name.
+ void SetDebugName(const std::string& debug_name) { debug_name_ = debug_name; }
+ std::string debug_name() const { return debug_name_; }
+
+ void SetCompositingReasons(CompositingReasons reasons) {
+ compositing_reasons_ = reasons;
+ }
+
+ CompositingReasons compositing_reasons() const {
+ return compositing_reasons_;
+ }
+
+ bool ShowDebugBorders() const;
+
+ // These invalidate the host's render surface layer list. The caller
+ // is responsible for calling set_needs_update_draw_properties on the tree
+ // so that its list can be recreated.
+ void CreateRenderSurface();
+ void ClearRenderSurface();
+
+ DrawProperties<LayerImpl, RenderSurfaceImpl>& draw_properties() {
+ return draw_properties_;
+ }
+ const DrawProperties<LayerImpl, RenderSurfaceImpl>& draw_properties() const {
+ return draw_properties_;
+ }
+
+ // The following are shortcut accessors to get various information from
+ // draw_properties_
+ const gfx::Transform& draw_transform() const {
+ return draw_properties_.target_space_transform;
+ }
+ const gfx::Transform& screen_space_transform() const {
+ return draw_properties_.screen_space_transform;
+ }
+ float draw_opacity() const { return draw_properties_.opacity; }
+ bool draw_opacity_is_animating() const {
+ return draw_properties_.opacity_is_animating;
+ }
+ bool draw_transform_is_animating() const {
+ return draw_properties_.target_space_transform_is_animating;
+ }
+ bool screen_space_transform_is_animating() const {
+ return draw_properties_.screen_space_transform_is_animating;
+ }
+ bool screen_space_opacity_is_animating() const {
+ return draw_properties_.screen_space_opacity_is_animating;
+ }
+ bool can_use_lcd_text() const { return draw_properties_.can_use_lcd_text; }
+ bool is_clipped() const { return draw_properties_.is_clipped; }
+ gfx::Rect clip_rect() const { return draw_properties_.clip_rect; }
+ gfx::Rect drawable_content_rect() const {
+ return draw_properties_.drawable_content_rect;
+ }
+ gfx::Rect visible_content_rect() const {
+ return draw_properties_.visible_content_rect;
+ }
+ LayerImpl* render_target() {
+ DCHECK(!draw_properties_.render_target ||
+ draw_properties_.render_target->render_surface());
+ return draw_properties_.render_target;
+ }
+ const LayerImpl* render_target() const {
+ DCHECK(!draw_properties_.render_target ||
+ draw_properties_.render_target->render_surface());
+ return draw_properties_.render_target;
+ }
+ RenderSurfaceImpl* render_surface() const {
+ return draw_properties_.render_surface.get();
+ }
+
+ // The client should be responsible for setting bounds, content bounds and
+ // contents scale to appropriate values. LayerImpl doesn't calculate any of
+ // them from the other values.
+
+ void SetBounds(gfx::Size bounds);
+ gfx::Size bounds() const { return bounds_; }
+
+ void SetContentBounds(gfx::Size content_bounds);
+ gfx::Size content_bounds() const { return draw_properties_.content_bounds; }
+
+ float contents_scale_x() const { return draw_properties_.contents_scale_x; }
+ float contents_scale_y() const { return draw_properties_.contents_scale_y; }
+ void SetContentsScale(float contents_scale_x, float contents_scale_y);
+
+ virtual void CalculateContentsScale(float ideal_contents_scale,
+ float device_scale_factor,
+ float page_scale_factor,
+ bool animating_transform_to_screen,
+ float* contents_scale_x,
+ float* contents_scale_y,
+ gfx::Size* content_bounds);
+
+ void SetScrollOffsetDelegate(
+ LayerScrollOffsetDelegate* scroll_offset_delegate);
+ void SetScrollOffset(gfx::Vector2d scroll_offset);
+ gfx::Vector2d scroll_offset() const { return scroll_offset_; }
+
+ void SetMaxScrollOffset(gfx::Vector2d max_scroll_offset);
+ gfx::Vector2d max_scroll_offset() const { return max_scroll_offset_; }
+
+ void SetScrollDelta(gfx::Vector2dF scroll_delta);
+ gfx::Vector2dF ScrollDelta() const;
+
+ gfx::Vector2dF TotalScrollOffset() const;
+
+ void SetSentScrollDelta(gfx::Vector2d sent_scroll_delta);
+ gfx::Vector2d sent_scroll_delta() const { return sent_scroll_delta_; }
+
+ // Returns the delta of the scroll that was outside of the bounds of the
+ // initial scroll
+ gfx::Vector2dF ScrollBy(gfx::Vector2dF scroll);
+
+ void SetScrollable(bool scrollable) { scrollable_ = scrollable; }
+ bool scrollable() const { return scrollable_; }
+
+ void ApplySentScrollDeltas();
+
+ void SetShouldScrollOnMainThread(bool should_scroll_on_main_thread) {
+ should_scroll_on_main_thread_ = should_scroll_on_main_thread;
+ }
+ bool should_scroll_on_main_thread() const {
+ return should_scroll_on_main_thread_;
+ }
+
+ void SetHaveWheelEventHandlers(bool have_wheel_event_handlers) {
+ have_wheel_event_handlers_ = have_wheel_event_handlers;
+ }
+ bool have_wheel_event_handlers() const { return have_wheel_event_handlers_; }
+
+ void SetNonFastScrollableRegion(const Region& region) {
+ non_fast_scrollable_region_ = region;
+ }
+ const Region& non_fast_scrollable_region() const {
+ return non_fast_scrollable_region_;
+ }
+
+ void SetTouchEventHandlerRegion(const Region& region) {
+ touch_event_handler_region_ = region;
+ }
+ const Region& touch_event_handler_region() const {
+ return touch_event_handler_region_;
+ }
+
+ void SetDrawCheckerboardForMissingTiles(bool checkerboard) {
+ draw_checkerboard_for_missing_tiles_ = checkerboard;
+ }
+ bool DrawCheckerboardForMissingTiles() const;
+
+ InputHandler::ScrollStatus TryScroll(
+ gfx::PointF screen_space_point,
+ InputHandler::ScrollInputType type) const;
+
+ void SetDoubleSided(bool double_sided);
+ bool double_sided() const { return double_sided_; }
+
+ void SetTransform(const gfx::Transform& transform);
+ const gfx::Transform& transform() const { return transform_; }
+ bool TransformIsAnimating() const;
+ bool TransformIsAnimatingOnImplOnly() const;
+
+ // Note this rect is in layer space (not content space).
+ void set_update_rect(const gfx::RectF& update_rect) {
+ update_rect_ = update_rect;
+ }
+ const gfx::RectF& update_rect() const { return update_rect_; }
+
+ virtual base::DictionaryValue* LayerTreeAsJson() const;
+
+ void SetStackingOrderChanged(bool stacking_order_changed);
+
+ bool LayerPropertyChanged() const {
+ return layer_property_changed_ || LayerIsAlwaysDamaged();
+ }
+ bool LayerSurfacePropertyChanged() const;
+
+ void ResetAllChangeTrackingForSubtree();
+
+ virtual bool LayerIsAlwaysDamaged() const;
+
+ LayerAnimationController* layer_animation_controller() {
+ return layer_animation_controller_.get();
+ }
+
+ virtual Region VisibleContentOpaqueRegion() const;
+
+ virtual void DidBecomeActive();
+
+ virtual void DidBeginTracing();
+
+ // Indicates that the surface previously used to render this layer
+ // was lost and that a new one has been created. Won't be called
+ // until the new surface has been created successfully.
+ virtual void DidLoseOutputSurface();
+
+ ScrollbarAnimationController* scrollbar_animation_controller() const {
+ return scrollbar_animation_controller_.get();
+ }
+
+ void SetScrollbarOpacity(float opacity);
+
+ void SetHorizontalScrollbarLayer(ScrollbarLayerImpl* scrollbar_layer);
+ ScrollbarLayerImpl* horizontal_scrollbar_layer() {
+ return horizontal_scrollbar_layer_;
+ }
+
+ void SetVerticalScrollbarLayer(ScrollbarLayerImpl* scrollbar_layer);
+ ScrollbarLayerImpl* vertical_scrollbar_layer() {
+ return vertical_scrollbar_layer_;
+ }
+
+ gfx::Rect LayerRectToContentRect(const gfx::RectF& layer_rect) const;
+
+ virtual skia::RefPtr<SkPicture> GetPicture();
+
+ virtual bool CanClipSelf() const;
+
+ virtual bool AreVisibleResourcesReady() const;
+
+ virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl);
+ virtual void PushPropertiesTo(LayerImpl* layer);
+
+ scoped_ptr<base::Value> AsValue() const;
+ virtual size_t GPUMemoryUsageInBytes() const;
+
+ // TODO(danakj): Be true only if needed. crbug.com/259511
+ bool needs_push_properties() const { return true; }
+ bool descendant_needs_push_properties() const { return true; }
+
+ protected:
+ LayerImpl(LayerTreeImpl* layer_impl, int id);
+
+ // Get the color and size of the layer's debug border.
+ virtual void GetDebugBorderProperties(SkColor* color, float* width) const;
+
+ void AppendDebugBorderQuad(QuadSink* quad_sink,
+ const SharedQuadState* shared_quad_state,
+ AppendQuadsData* append_quads_data) const;
+ void AppendDebugBorderQuad(QuadSink* quad_sink,
+ const SharedQuadState* shared_quad_state,
+ AppendQuadsData* append_quads_data,
+ SkColor color,
+ float width) const;
+
+ virtual void AsValueInto(base::DictionaryValue* dict) const;
+
+ void NoteLayerSurfacePropertyChanged();
+ void NoteLayerPropertyChanged();
+ void NoteLayerPropertyChangedForSubtree();
+
+ // Note carefully this does not affect the current layer.
+ void NoteLayerPropertyChangedForDescendants();
+
+ private:
+ void UpdateScrollbarPositions();
+
+ virtual const char* LayerTypeAsString() const;
+
+ // Properties internal to LayerImpl
+ LayerImpl* parent_;
+ OwnedLayerImplList children_;
+ // mask_layer_ can be temporarily stolen during tree sync, we need this ID to
+ // confirm newly assigned layer is still the previous one
+ int mask_layer_id_;
+ scoped_ptr<LayerImpl> mask_layer_;
+ int replica_layer_id_; // ditto
+ scoped_ptr<LayerImpl> replica_layer_;
+ int layer_id_;
+ LayerTreeImpl* layer_tree_impl_;
+
+ // Properties synchronized from the associated Layer.
+ gfx::PointF anchor_point_;
+ float anchor_point_z_;
+ gfx::Size bounds_;
+ gfx::Vector2d scroll_offset_;
+ LayerScrollOffsetDelegate* scroll_offset_delegate_;
+ bool scrollable_;
+ bool should_scroll_on_main_thread_;
+ bool have_wheel_event_handlers_;
+ Region non_fast_scrollable_region_;
+ Region touch_event_handler_region_;
+ SkColor background_color_;
+ bool stacking_order_changed_;
+
+ // Whether the "back" of this layer should draw.
+ bool double_sided_;
+
+ // Tracks if drawing-related properties have changed since last redraw.
+ bool layer_property_changed_;
+
+ // Indicates that a property has changed on this layer that would not
+ // affect the pixels on its target surface, but would require redrawing
+ // the target_surface onto its ancestor target_surface.
+ // For layers that do not own a surface this flag acts as
+ // layer_property_changed_.
+ bool layer_surface_property_changed_;
+
+ bool masks_to_bounds_;
+ bool contents_opaque_;
+ float opacity_;
+ gfx::PointF position_;
+ bool preserves_3d_;
+ bool use_parent_backface_visibility_;
+ bool draw_checkerboard_for_missing_tiles_;
+ gfx::Transform sublayer_transform_;
+ gfx::Transform transform_;
+
+ bool draws_content_;
+ bool hide_layer_and_subtree_;
+ bool force_render_surface_;
+
+ // Set for the layer that other layers are fixed to.
+ bool is_container_for_fixed_position_layers_;
+ // This property is effective when
+ // is_container_for_fixed_position_layers_ == true,
+ gfx::Vector2dF fixed_container_size_delta_;
+
+ LayerPositionConstraint position_constraint_;
+
+ gfx::Vector2dF scroll_delta_;
+ gfx::Vector2d sent_scroll_delta_;
+ gfx::Vector2d max_scroll_offset_;
+ gfx::Vector2dF last_scroll_offset_;
+
+ // The global depth value of the center of the layer. This value is used
+ // to sort layers from back to front.
+ float draw_depth_;
+
+ // Debug layer name.
+ std::string debug_name_;
+ CompositingReasons compositing_reasons_;
+
+ FilterOperations filters_;
+ FilterOperations background_filters_;
+ skia::RefPtr<SkImageFilter> filter_;
+
+ protected:
+ DrawMode current_draw_mode_;
+
+ private:
+ // Rect indicating what was repainted/updated during update.
+ // Note that plugin layers bypass this and leave it empty.
+ // Uses layer's content space.
+ gfx::RectF update_rect_;
+
+ // Manages animations for this layer.
+ scoped_refptr<LayerAnimationController> layer_animation_controller_;
+
+ // Manages scrollbars for this layer
+ scoped_ptr<ScrollbarAnimationController> scrollbar_animation_controller_;
+
+ // Weak pointers to this layer's scrollbars, if it has them. Updated during
+ // tree synchronization.
+ ScrollbarLayerImpl* horizontal_scrollbar_layer_;
+ ScrollbarLayerImpl* vertical_scrollbar_layer_;
+
+ ScopedPtrVector<CopyOutputRequest> copy_requests_;
+
+ // Group of properties that need to be computed based on the layer tree
+ // hierarchy before layers can be drawn.
+ DrawProperties<LayerImpl, RenderSurfaceImpl> draw_properties_;
+
+ DISALLOW_COPY_AND_ASSIGN(LayerImpl);
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_LAYER_IMPL_H_
diff --git a/chromium/cc/layers/layer_impl_unittest.cc b/chromium/cc/layers/layer_impl_unittest.cc
new file mode 100644
index 00000000000..9c6dbfe3790
--- /dev/null
+++ b/chromium/cc/layers/layer_impl_unittest.cc
@@ -0,0 +1,562 @@
+// Copyright 2011 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 "cc/layers/layer_impl.h"
+
+#include "cc/output/filter_operation.h"
+#include "cc/output/filter_operations.h"
+#include "cc/test/fake_impl_proxy.h"
+#include "cc/test/fake_layer_tree_host_impl.h"
+#include "cc/test/fake_output_surface.h"
+#include "cc/test/geometry_test_utils.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "cc/trees/single_thread_proxy.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/effects/SkBlurImageFilter.h"
+
+namespace cc {
+namespace {
+
+#define EXECUTE_AND_VERIFY_SUBTREE_CHANGED(code_to_test) \
+ root->ResetAllChangeTrackingForSubtree(); \
+ code_to_test; \
+ EXPECT_TRUE(root->LayerPropertyChanged()); \
+ EXPECT_TRUE(child->LayerPropertyChanged()); \
+ EXPECT_TRUE(grand_child->LayerPropertyChanged()); \
+ EXPECT_FALSE(root->LayerSurfacePropertyChanged())
+
+#define EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(code_to_test) \
+ root->ResetAllChangeTrackingForSubtree(); \
+ code_to_test; \
+ EXPECT_FALSE(root->LayerPropertyChanged()); \
+ EXPECT_FALSE(child->LayerPropertyChanged()); \
+ EXPECT_FALSE(grand_child->LayerPropertyChanged()); \
+ EXPECT_FALSE(root->LayerSurfacePropertyChanged())
+
+#define EXECUTE_AND_VERIFY_ONLY_LAYER_CHANGED(code_to_test) \
+ root->ResetAllChangeTrackingForSubtree(); \
+ code_to_test; \
+ EXPECT_TRUE(root->LayerPropertyChanged()); \
+ EXPECT_FALSE(child->LayerPropertyChanged()); \
+ EXPECT_FALSE(grand_child->LayerPropertyChanged()); \
+ EXPECT_FALSE(root->LayerSurfacePropertyChanged())
+
+#define EXECUTE_AND_VERIFY_ONLY_SURFACE_CHANGED(code_to_test) \
+ root->ResetAllChangeTrackingForSubtree(); \
+ code_to_test; \
+ EXPECT_FALSE(root->LayerPropertyChanged()); \
+ EXPECT_FALSE(child->LayerPropertyChanged()); \
+ EXPECT_FALSE(grand_child->LayerPropertyChanged()); \
+ EXPECT_TRUE(root->LayerSurfacePropertyChanged())
+
+#define VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(code_to_test) \
+ root->ResetAllChangeTrackingForSubtree(); \
+ host_impl.ForcePrepareToDraw(); \
+ EXPECT_FALSE(host_impl.active_tree()->needs_update_draw_properties()); \
+ code_to_test; \
+ EXPECT_TRUE(host_impl.active_tree()->needs_update_draw_properties());
+
+#define VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(code_to_test) \
+ root->ResetAllChangeTrackingForSubtree(); \
+ host_impl.ForcePrepareToDraw(); \
+ EXPECT_FALSE(host_impl.active_tree()->needs_update_draw_properties()); \
+ code_to_test; \
+ EXPECT_FALSE(host_impl.active_tree()->needs_update_draw_properties());
+
+TEST(LayerImplTest, VerifyLayerChangesAreTrackedProperly) {
+ //
+ // This test checks that layerPropertyChanged() has the correct behavior.
+ //
+
+ // The constructor on this will fake that we are on the correct thread.
+ // Create a simple LayerImpl tree:
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ EXPECT_TRUE(host_impl.InitializeRenderer(CreateFakeOutputSurface()));
+ scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl.active_tree(), 1);
+ root->AddChild(LayerImpl::Create(host_impl.active_tree(), 2));
+ LayerImpl* child = root->children()[0];
+ child->AddChild(LayerImpl::Create(host_impl.active_tree(), 3));
+ LayerImpl* grand_child = child->children()[0];
+
+ root->SetScrollable(true);
+
+ // Adding children is an internal operation and should not mark layers as
+ // changed.
+ EXPECT_FALSE(root->LayerPropertyChanged());
+ EXPECT_FALSE(child->LayerPropertyChanged());
+ EXPECT_FALSE(grand_child->LayerPropertyChanged());
+
+ gfx::PointF arbitrary_point_f = gfx::PointF(0.125f, 0.25f);
+ float arbitrary_number = 0.352f;
+ gfx::Size arbitrary_size = gfx::Size(111, 222);
+ gfx::Point arbitrary_point = gfx::Point(333, 444);
+ gfx::Vector2d arbitrary_vector2d = gfx::Vector2d(111, 222);
+ gfx::Rect arbitrary_rect = gfx::Rect(arbitrary_point, arbitrary_size);
+ gfx::RectF arbitrary_rect_f =
+ gfx::RectF(arbitrary_point_f, gfx::SizeF(1.234f, 5.678f));
+ SkColor arbitrary_color = SkColorSetRGB(10, 20, 30);
+ gfx::Transform arbitrary_transform;
+ arbitrary_transform.Scale3d(0.1, 0.2, 0.3);
+ FilterOperations arbitrary_filters;
+ arbitrary_filters.Append(FilterOperation::CreateOpacityFilter(0.5f));
+ skia::RefPtr<SkImageFilter> arbitrary_filter =
+ skia::AdoptRef(new SkBlurImageFilter(SK_Scalar1, SK_Scalar1));
+
+ // These properties are internal, and should not be considered "change" when
+ // they are used.
+ EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(
+ root->set_update_rect(arbitrary_rect_f));
+ EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(
+ root->SetMaxScrollOffset(arbitrary_vector2d));
+
+ // Changing these properties affects the entire subtree of layers.
+ EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetAnchorPoint(arbitrary_point_f));
+ EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetAnchorPointZ(arbitrary_number));
+ EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetFilters(arbitrary_filters));
+ EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetFilters(FilterOperations()));
+ EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetFilter(arbitrary_filter));
+ EXECUTE_AND_VERIFY_SUBTREE_CHANGED(
+ root->SetMaskLayer(LayerImpl::Create(host_impl.active_tree(), 4)));
+ EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetMasksToBounds(true));
+ EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetContentsOpaque(true));
+ EXECUTE_AND_VERIFY_SUBTREE_CHANGED(
+ root->SetReplicaLayer(LayerImpl::Create(host_impl.active_tree(), 5)));
+ EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetPosition(arbitrary_point_f));
+ EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetPreserves3d(true));
+ EXECUTE_AND_VERIFY_SUBTREE_CHANGED(
+ root->SetDoubleSided(false)); // constructor initializes it to "true".
+ EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->ScrollBy(arbitrary_vector2d));
+ EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetScrollDelta(gfx::Vector2d()));
+ EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetScrollOffset(arbitrary_vector2d));
+ EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetHideLayerAndSubtree(true));
+
+ // Changing these properties only affects the layer itself.
+ EXECUTE_AND_VERIFY_ONLY_LAYER_CHANGED(root->SetContentBounds(arbitrary_size));
+ EXECUTE_AND_VERIFY_ONLY_LAYER_CHANGED(
+ root->SetContentsScale(arbitrary_number, arbitrary_number));
+ EXECUTE_AND_VERIFY_ONLY_LAYER_CHANGED(root->SetDrawsContent(true));
+ EXECUTE_AND_VERIFY_ONLY_LAYER_CHANGED(
+ root->SetBackgroundColor(arbitrary_color));
+ EXECUTE_AND_VERIFY_ONLY_LAYER_CHANGED(
+ root->SetBackgroundFilters(arbitrary_filters));
+
+ // Changing these properties only affects how render surface is drawn
+ EXECUTE_AND_VERIFY_ONLY_SURFACE_CHANGED(root->SetOpacity(arbitrary_number));
+ EXECUTE_AND_VERIFY_ONLY_SURFACE_CHANGED(
+ root->SetTransform(arbitrary_transform));
+
+ // Special case: check that sublayer transform changes all layer's
+ // descendants, but not the layer itself.
+ root->ResetAllChangeTrackingForSubtree();
+ root->SetSublayerTransform(arbitrary_transform);
+ EXPECT_FALSE(root->LayerPropertyChanged());
+ EXPECT_TRUE(child->LayerPropertyChanged());
+ EXPECT_TRUE(grand_child->LayerPropertyChanged());
+
+ // Special case: check that SetBounds changes behavior depending on
+ // masksToBounds.
+ root->SetMasksToBounds(false);
+ EXECUTE_AND_VERIFY_ONLY_LAYER_CHANGED(root->SetBounds(gfx::Size(135, 246)));
+ root->SetMasksToBounds(true);
+ // Should be a different size than previous call, to ensure it marks tree
+ // changed.
+ EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetBounds(arbitrary_size));
+
+ // After setting all these properties already, setting to the exact same
+ // values again should not cause any change.
+ EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(
+ root->SetAnchorPoint(arbitrary_point_f));
+ EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(
+ root->SetAnchorPointZ(arbitrary_number));
+ EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetMasksToBounds(true));
+ EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(
+ root->SetPosition(arbitrary_point_f));
+ EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetPreserves3d(true));
+ EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(
+ root->SetTransform(arbitrary_transform));
+ EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(
+ root->SetDoubleSided(false)); // constructor initializes it to "true".
+ EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(
+ root->SetScrollDelta(gfx::Vector2d()));
+ EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(
+ root->SetScrollOffset(arbitrary_vector2d));
+ EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(
+ root->SetContentBounds(arbitrary_size));
+ EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(
+ root->SetContentsScale(arbitrary_number, arbitrary_number));
+ EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetContentsOpaque(true));
+ EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetOpacity(arbitrary_number));
+ EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetDrawsContent(true));
+ EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(
+ root->SetSublayerTransform(arbitrary_transform));
+ EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetBounds(arbitrary_size));
+}
+
+TEST(LayerImplTest, VerifyNeedsUpdateDrawProperties) {
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ EXPECT_TRUE(host_impl.InitializeRenderer(CreateFakeOutputSurface()));
+ scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl.active_tree(), 1);
+ root->SetScrollable(true);
+
+ gfx::PointF arbitrary_point_f = gfx::PointF(0.125f, 0.25f);
+ float arbitrary_number = 0.352f;
+ gfx::Size arbitrary_size = gfx::Size(111, 222);
+ gfx::Point arbitrary_point = gfx::Point(333, 444);
+ gfx::Vector2d arbitrary_vector2d = gfx::Vector2d(111, 222);
+ gfx::Vector2d large_vector2d = gfx::Vector2d(1000, 1000);
+ gfx::Rect arbitrary_rect = gfx::Rect(arbitrary_point, arbitrary_size);
+ gfx::RectF arbitrary_rect_f =
+ gfx::RectF(arbitrary_point_f, gfx::SizeF(1.234f, 5.678f));
+ SkColor arbitrary_color = SkColorSetRGB(10, 20, 30);
+ gfx::Transform arbitrary_transform;
+ arbitrary_transform.Scale3d(0.1, 0.2, 0.3);
+ FilterOperations arbitrary_filters;
+ arbitrary_filters.Append(FilterOperation::CreateOpacityFilter(0.5f));
+ skia::RefPtr<SkImageFilter> arbitrary_filter =
+ skia::AdoptRef(new SkBlurImageFilter(SK_Scalar1, SK_Scalar1));
+
+ // Related filter functions.
+ VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetFilters(arbitrary_filters));
+ VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetFilters(arbitrary_filters));
+ VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetFilters(FilterOperations()));
+ VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetFilter(arbitrary_filter));
+ VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetFilter(arbitrary_filter));
+
+ // Related scrolling functions.
+ VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetMaxScrollOffset(large_vector2d));
+ VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(
+ root->SetMaxScrollOffset(large_vector2d));
+ VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->ScrollBy(arbitrary_vector2d));
+ VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->ScrollBy(gfx::Vector2d()));
+ root->SetScrollDelta(gfx::Vector2d(0, 0));
+ host_impl.ForcePrepareToDraw();
+ VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetScrollDelta(arbitrary_vector2d));
+ VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(
+ root->SetScrollDelta(arbitrary_vector2d));
+ VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(
+ root->SetScrollOffset(arbitrary_vector2d));
+ VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(
+ root->SetScrollOffset(arbitrary_vector2d));
+
+ // Unrelated functions, always set to new values, always set needs update.
+ VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetAnchorPointZ(arbitrary_number));
+ VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(
+ root->SetMaskLayer(LayerImpl::Create(host_impl.active_tree(), 4)));
+ VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetMasksToBounds(true));
+ VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetContentsOpaque(true));
+ VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(
+ root->SetReplicaLayer(LayerImpl::Create(host_impl.active_tree(), 5)));
+ VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetPosition(arbitrary_point_f));
+ VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetPreserves3d(true));
+ VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(
+ root->SetDoubleSided(false)); // constructor initializes it to "true".
+ VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetContentBounds(arbitrary_size));
+ VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(
+ root->SetContentsScale(arbitrary_number, arbitrary_number));
+ VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetDrawsContent(true));
+ VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(
+ root->SetBackgroundColor(arbitrary_color));
+ VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(
+ root->SetBackgroundFilters(arbitrary_filters));
+ VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetOpacity(arbitrary_number));
+ VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetTransform(arbitrary_transform));
+ VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(
+ root->SetSublayerTransform(arbitrary_transform));
+ VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetBounds(arbitrary_size));
+
+ // Unrelated functions, set to the same values, no needs update.
+ VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(
+ root->SetAnchorPointZ(arbitrary_number));
+ VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetFilter(arbitrary_filter));
+ VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetMasksToBounds(true));
+ VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetContentsOpaque(true));
+ VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetPosition(arbitrary_point_f));
+ VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetPreserves3d(true));
+ VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(
+ root->SetDoubleSided(false)); // constructor initializes it to "true".
+ VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(
+ root->SetContentBounds(arbitrary_size));
+ VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(
+ root->SetContentsScale(arbitrary_number, arbitrary_number));
+ VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetDrawsContent(true));
+ VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(
+ root->SetBackgroundColor(arbitrary_color));
+ VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(
+ root->SetBackgroundFilters(arbitrary_filters));
+ VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetOpacity(arbitrary_number));
+ VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(
+ root->SetTransform(arbitrary_transform));
+ VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(
+ root->SetSublayerTransform(arbitrary_transform));
+ VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetBounds(arbitrary_size));
+}
+
+TEST(LayerImplTest, SafeOpaqueBackgroundColor) {
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ EXPECT_TRUE(host_impl.InitializeRenderer(CreateFakeOutputSurface()));
+ scoped_ptr<LayerImpl> layer = LayerImpl::Create(host_impl.active_tree(), 1);
+
+ for (int contents_opaque = 0; contents_opaque < 2; ++contents_opaque) {
+ for (int layer_opaque = 0; layer_opaque < 2; ++layer_opaque) {
+ for (int host_opaque = 0; host_opaque < 2; ++host_opaque) {
+ layer->SetContentsOpaque(!!contents_opaque);
+ layer->SetBackgroundColor(layer_opaque ? SK_ColorRED
+ : SK_ColorTRANSPARENT);
+ host_impl.active_tree()->set_background_color(
+ host_opaque ? SK_ColorRED : SK_ColorTRANSPARENT);
+
+ SkColor safe_color = layer->SafeOpaqueBackgroundColor();
+ if (contents_opaque) {
+ EXPECT_EQ(SkColorGetA(safe_color), 255u)
+ << "Flags: " << contents_opaque << ", " << layer_opaque << ", "
+ << host_opaque << "\n";
+ } else {
+ EXPECT_NE(SkColorGetA(safe_color), 255u)
+ << "Flags: " << contents_opaque << ", " << layer_opaque << ", "
+ << host_opaque << "\n";
+ }
+ }
+ }
+ }
+}
+
+class LayerImplScrollTest : public testing::Test {
+ public:
+ LayerImplScrollTest() : host_impl_(&proxy_), root_id_(7) {
+ host_impl_.active_tree()
+ ->SetRootLayer(LayerImpl::Create(host_impl_.active_tree(), root_id_));
+ host_impl_.active_tree()->root_layer()->SetScrollable(true);
+ }
+
+ LayerImpl* layer() { return host_impl_.active_tree()->root_layer(); }
+
+ private:
+ FakeImplProxy proxy_;
+ FakeLayerTreeHostImpl host_impl_;
+ int root_id_;
+};
+
+TEST_F(LayerImplScrollTest, ScrollByWithZeroOffset) {
+ // Test that LayerImpl::ScrollBy only affects ScrollDelta and total scroll
+ // offset is bounded by the range [0, max scroll offset].
+ gfx::Vector2d max_scroll_offset(50, 80);
+ layer()->SetMaxScrollOffset(max_scroll_offset);
+
+ EXPECT_VECTOR_EQ(gfx::Vector2dF(), layer()->TotalScrollOffset());
+ EXPECT_VECTOR_EQ(gfx::Vector2dF(), layer()->scroll_offset());
+ EXPECT_VECTOR_EQ(gfx::Vector2dF(), layer()->ScrollDelta());
+
+ layer()->ScrollBy(gfx::Vector2dF(-100, 100));
+ EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 80), layer()->TotalScrollOffset());
+
+ EXPECT_VECTOR_EQ(layer()->ScrollDelta(), layer()->TotalScrollOffset());
+ EXPECT_VECTOR_EQ(gfx::Vector2dF(), layer()->scroll_offset());
+
+ layer()->ScrollBy(gfx::Vector2dF(100, -100));
+ EXPECT_VECTOR_EQ(gfx::Vector2dF(50, 0), layer()->TotalScrollOffset());
+
+ EXPECT_VECTOR_EQ(layer()->ScrollDelta(), layer()->TotalScrollOffset());
+ EXPECT_VECTOR_EQ(gfx::Vector2dF(), layer()->scroll_offset());
+}
+
+TEST_F(LayerImplScrollTest, ScrollByWithNonZeroOffset) {
+ gfx::Vector2d max_scroll_offset(50, 80);
+ gfx::Vector2d scroll_offset(10, 5);
+ layer()->SetMaxScrollOffset(max_scroll_offset);
+ layer()->SetScrollOffset(scroll_offset);
+
+ EXPECT_VECTOR_EQ(scroll_offset, layer()->TotalScrollOffset());
+ EXPECT_VECTOR_EQ(scroll_offset, layer()->scroll_offset());
+ EXPECT_VECTOR_EQ(gfx::Vector2dF(), layer()->ScrollDelta());
+
+ layer()->ScrollBy(gfx::Vector2dF(-100, 100));
+ EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 80), layer()->TotalScrollOffset());
+
+ EXPECT_VECTOR_EQ(layer()->ScrollDelta() + scroll_offset,
+ layer()->TotalScrollOffset());
+ EXPECT_VECTOR_EQ(scroll_offset, layer()->scroll_offset());
+
+ layer()->ScrollBy(gfx::Vector2dF(100, -100));
+ EXPECT_VECTOR_EQ(gfx::Vector2dF(50, 0), layer()->TotalScrollOffset());
+
+ EXPECT_VECTOR_EQ(layer()->ScrollDelta() + scroll_offset,
+ layer()->TotalScrollOffset());
+ EXPECT_VECTOR_EQ(scroll_offset, layer()->scroll_offset());
+}
+
+class ScrollDelegateIgnore : public LayerScrollOffsetDelegate {
+ public:
+ virtual void SetTotalScrollOffset(gfx::Vector2dF new_value) OVERRIDE {}
+ virtual gfx::Vector2dF GetTotalScrollOffset() OVERRIDE {
+ return fixed_offset_;
+ }
+
+ void set_fixed_offset(gfx::Vector2dF fixed_offset) {
+ fixed_offset_ = fixed_offset;
+ }
+
+ private:
+ gfx::Vector2dF fixed_offset_;
+};
+
+TEST_F(LayerImplScrollTest, ScrollByWithIgnoringDelegate) {
+ gfx::Vector2d max_scroll_offset(50, 80);
+ gfx::Vector2d scroll_offset(10, 5);
+ layer()->SetMaxScrollOffset(max_scroll_offset);
+ layer()->SetScrollOffset(scroll_offset);
+
+ EXPECT_VECTOR_EQ(scroll_offset, layer()->TotalScrollOffset());
+ EXPECT_VECTOR_EQ(scroll_offset, layer()->scroll_offset());
+ EXPECT_VECTOR_EQ(gfx::Vector2dF(), layer()->ScrollDelta());
+
+ ScrollDelegateIgnore delegate;
+ gfx::Vector2dF fixed_offset(32, 12);
+ delegate.set_fixed_offset(fixed_offset);
+ layer()->SetScrollOffsetDelegate(&delegate);
+
+ EXPECT_VECTOR_EQ(fixed_offset, layer()->TotalScrollOffset());
+ EXPECT_VECTOR_EQ(scroll_offset, layer()->scroll_offset());
+
+ layer()->ScrollBy(gfx::Vector2dF(-100, 100));
+
+ EXPECT_VECTOR_EQ(fixed_offset, layer()->TotalScrollOffset());
+ EXPECT_VECTOR_EQ(scroll_offset, layer()->scroll_offset());
+
+ layer()->SetScrollOffsetDelegate(NULL);
+
+ EXPECT_VECTOR_EQ(fixed_offset, layer()->TotalScrollOffset());
+ EXPECT_VECTOR_EQ(scroll_offset, layer()->scroll_offset());
+
+ gfx::Vector2dF scroll_delta(1, 1);
+ layer()->ScrollBy(scroll_delta);
+
+ EXPECT_VECTOR_EQ(fixed_offset + scroll_delta, layer()->TotalScrollOffset());
+ EXPECT_VECTOR_EQ(scroll_offset, layer()->scroll_offset());
+}
+
+class ScrollDelegateAccept : public LayerScrollOffsetDelegate {
+ public:
+ virtual void SetTotalScrollOffset(gfx::Vector2dF new_value) OVERRIDE {
+ current_offset_ = new_value;
+ }
+ virtual gfx::Vector2dF GetTotalScrollOffset() OVERRIDE {
+ return current_offset_;
+ }
+
+ private:
+ gfx::Vector2dF current_offset_;
+};
+
+TEST_F(LayerImplScrollTest, ScrollByWithAcceptingDelegate) {
+ gfx::Vector2d max_scroll_offset(50, 80);
+ gfx::Vector2d scroll_offset(10, 5);
+ layer()->SetMaxScrollOffset(max_scroll_offset);
+ layer()->SetScrollOffset(scroll_offset);
+
+ EXPECT_VECTOR_EQ(scroll_offset, layer()->TotalScrollOffset());
+ EXPECT_VECTOR_EQ(scroll_offset, layer()->scroll_offset());
+ EXPECT_VECTOR_EQ(gfx::Vector2dF(), layer()->ScrollDelta());
+
+ ScrollDelegateAccept delegate;
+ layer()->SetScrollOffsetDelegate(&delegate);
+
+ EXPECT_VECTOR_EQ(scroll_offset, layer()->TotalScrollOffset());
+ EXPECT_VECTOR_EQ(scroll_offset, layer()->scroll_offset());
+ EXPECT_VECTOR_EQ(gfx::Vector2dF(), layer()->ScrollDelta());
+
+ layer()->ScrollBy(gfx::Vector2dF(-100, 100));
+
+ EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 80), layer()->TotalScrollOffset());
+ EXPECT_VECTOR_EQ(scroll_offset, layer()->scroll_offset());
+
+ layer()->SetScrollOffsetDelegate(NULL);
+
+ EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 80), layer()->TotalScrollOffset());
+ EXPECT_VECTOR_EQ(scroll_offset, layer()->scroll_offset());
+
+ gfx::Vector2dF scroll_delta(1, 1);
+ layer()->ScrollBy(scroll_delta);
+
+ EXPECT_VECTOR_EQ(gfx::Vector2dF(1, 80), layer()->TotalScrollOffset());
+ EXPECT_VECTOR_EQ(scroll_offset, layer()->scroll_offset());
+}
+
+TEST_F(LayerImplScrollTest, ApplySentScrollsNoDelegate) {
+ gfx::Vector2d max_scroll_offset(50, 80);
+ gfx::Vector2d scroll_offset(10, 5);
+ gfx::Vector2dF scroll_delta(20.5f, 8.5f);
+ gfx::Vector2d sent_scroll_delta(12, -3);
+
+ layer()->SetMaxScrollOffset(max_scroll_offset);
+ layer()->SetScrollOffset(scroll_offset);
+ layer()->ScrollBy(scroll_delta);
+ layer()->SetSentScrollDelta(sent_scroll_delta);
+
+ EXPECT_VECTOR_EQ(scroll_offset + scroll_delta, layer()->TotalScrollOffset());
+ EXPECT_VECTOR_EQ(scroll_delta, layer()->ScrollDelta());
+ EXPECT_VECTOR_EQ(scroll_offset, layer()->scroll_offset());
+ EXPECT_VECTOR_EQ(sent_scroll_delta, layer()->sent_scroll_delta());
+
+ layer()->ApplySentScrollDeltas();
+
+ EXPECT_VECTOR_EQ(scroll_offset + scroll_delta, layer()->TotalScrollOffset());
+ EXPECT_VECTOR_EQ(scroll_delta - sent_scroll_delta, layer()->ScrollDelta());
+ EXPECT_VECTOR_EQ(scroll_offset + sent_scroll_delta, layer()->scroll_offset());
+ EXPECT_VECTOR_EQ(gfx::Vector2d(), layer()->sent_scroll_delta());
+}
+
+TEST_F(LayerImplScrollTest, ApplySentScrollsWithIgnoringDelegate) {
+ gfx::Vector2d max_scroll_offset(50, 80);
+ gfx::Vector2d scroll_offset(10, 5);
+ gfx::Vector2d sent_scroll_delta(12, -3);
+ gfx::Vector2dF fixed_offset(32, 12);
+
+ layer()->SetMaxScrollOffset(max_scroll_offset);
+ layer()->SetScrollOffset(scroll_offset);
+ ScrollDelegateIgnore delegate;
+ delegate.set_fixed_offset(fixed_offset);
+ layer()->SetScrollOffsetDelegate(&delegate);
+ layer()->SetSentScrollDelta(sent_scroll_delta);
+
+ EXPECT_VECTOR_EQ(fixed_offset, layer()->TotalScrollOffset());
+ EXPECT_VECTOR_EQ(scroll_offset, layer()->scroll_offset());
+ EXPECT_VECTOR_EQ(sent_scroll_delta, layer()->sent_scroll_delta());
+
+ layer()->ApplySentScrollDeltas();
+
+ EXPECT_VECTOR_EQ(fixed_offset, layer()->TotalScrollOffset());
+ EXPECT_VECTOR_EQ(scroll_offset + sent_scroll_delta, layer()->scroll_offset());
+ EXPECT_VECTOR_EQ(gfx::Vector2d(), layer()->sent_scroll_delta());
+}
+
+TEST_F(LayerImplScrollTest, ApplySentScrollsWithAcceptingDelegate) {
+ gfx::Vector2d max_scroll_offset(50, 80);
+ gfx::Vector2d scroll_offset(10, 5);
+ gfx::Vector2d sent_scroll_delta(12, -3);
+ gfx::Vector2dF scroll_delta(20.5f, 8.5f);
+
+ layer()->SetMaxScrollOffset(max_scroll_offset);
+ layer()->SetScrollOffset(scroll_offset);
+ ScrollDelegateAccept delegate;
+ layer()->SetScrollOffsetDelegate(&delegate);
+ layer()->ScrollBy(scroll_delta);
+ layer()->SetSentScrollDelta(sent_scroll_delta);
+
+ EXPECT_VECTOR_EQ(scroll_offset + scroll_delta, layer()->TotalScrollOffset());
+ EXPECT_VECTOR_EQ(scroll_offset, layer()->scroll_offset());
+ EXPECT_VECTOR_EQ(sent_scroll_delta, layer()->sent_scroll_delta());
+
+ layer()->ApplySentScrollDeltas();
+
+ EXPECT_VECTOR_EQ(scroll_offset + scroll_delta, layer()->TotalScrollOffset());
+ EXPECT_VECTOR_EQ(scroll_offset + sent_scroll_delta, layer()->scroll_offset());
+ EXPECT_VECTOR_EQ(gfx::Vector2d(), layer()->sent_scroll_delta());
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/layers/layer_iterator.cc b/chromium/cc/layers/layer_iterator.cc
new file mode 100644
index 00000000000..c33e7536ec0
--- /dev/null
+++ b/chromium/cc/layers/layer_iterator.cc
@@ -0,0 +1,225 @@
+// 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 "cc/layers/layer_iterator.h"
+
+#include <vector>
+
+#include "cc/layers/layer.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/layers/render_surface.h"
+#include "cc/layers/render_surface_impl.h"
+
+namespace cc {
+
+template <typename LayerType,
+ typename LayerList,
+ typename RenderSurfaceType,
+ typename ActionType>
+void LayerIteratorActions::BackToFront::Begin(
+ LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>* it) {
+ it->target_render_surface_layer_index_ = 0;
+ it->current_layer_index_ =
+ LayerIteratorValue::kLayerIndexRepresentingTargetRenderSurface;
+
+ highest_target_render_surface_layer_ = 0;
+}
+
+template <typename LayerType,
+ typename LayerList,
+ typename RenderSurfaceType,
+ typename ActionType>
+void LayerIteratorActions::BackToFront::End(
+ LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>* it) {
+ it->target_render_surface_layer_index_ =
+ LayerIteratorValue::kInvalidTargetRenderSurfaceLayerIndex;
+ it->current_layer_index_ = 0;
+}
+
+template <typename LayerType,
+ typename LayerList,
+ typename RenderSurfaceType,
+ typename ActionType>
+void LayerIteratorActions::BackToFront::Next(
+ LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>* it) {
+ // If the current layer has a RS, move to its layer list. Otherwise,
+ // visit the next layer in the current RS layer list.
+ if (it->current_layer_represents_contributing_render_surface()) {
+ // Save our position in the child_layers list for the RenderSurface,
+ // then jump to the next RenderSurface. Save where we
+ // came from in the next RenderSurface so we can get back to it.
+ it->target_render_surface()->current_layer_index_history_ =
+ it->current_layer_index_;
+ int previous_target_render_surface_layer =
+ it->target_render_surface_layer_index_;
+
+ it->target_render_surface_layer_index_ =
+ ++highest_target_render_surface_layer_;
+ it->current_layer_index_ =
+ LayerIteratorValue::kLayerIndexRepresentingTargetRenderSurface;
+
+ it->target_render_surface()->target_render_surface_layer_index_history_ =
+ previous_target_render_surface_layer;
+ } else {
+ ++it->current_layer_index_;
+
+ int target_render_surface_num_children =
+ it->target_render_surface_children().size();
+ while (it->current_layer_index_ == target_render_surface_num_children) {
+ // Jump back to the previous RenderSurface,
+ // and get back the position where we were in that list,
+ // and move to the next position there.
+ if (!it->target_render_surface_layer_index_) {
+ // End of the list
+ it->target_render_surface_layer_index_ =
+ LayerIteratorValue::kInvalidTargetRenderSurfaceLayerIndex;
+ it->current_layer_index_ = 0;
+ return;
+ }
+ it->target_render_surface_layer_index_ = it->target_render_surface()
+ ->target_render_surface_layer_index_history_;
+ it->current_layer_index_ =
+ it->target_render_surface()->current_layer_index_history_ + 1;
+
+ target_render_surface_num_children =
+ it->target_render_surface_children().size();
+ }
+ }
+}
+
+template <typename LayerType,
+ typename LayerList,
+ typename RenderSurfaceType,
+ typename ActionType>
+void LayerIteratorActions::FrontToBack::Begin(
+ LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>* it) {
+ it->target_render_surface_layer_index_ = 0;
+ it->current_layer_index_ = it->target_render_surface_children().size() - 1;
+ GoToHighestInSubtree(it);
+}
+
+template <typename LayerType,
+ typename LayerList,
+ typename RenderSurfaceType,
+ typename ActionType>
+void LayerIteratorActions::FrontToBack::End(
+ LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>* it) {
+ it->target_render_surface_layer_index_ =
+ LayerIteratorValue::kInvalidTargetRenderSurfaceLayerIndex;
+ it->current_layer_index_ = 0;
+}
+
+template <typename LayerType,
+ typename LayerList,
+ typename RenderSurfaceType,
+ typename ActionType>
+void LayerIteratorActions::FrontToBack::Next(
+ LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>* it) {
+ // Moves to the previous layer in the current RS layer list.
+ // Then we check if the new current layer has its own RS,
+ // in which case there are things in that RS layer list that are higher,
+ // so we find the highest layer in that subtree.
+ // If we move back past the front of the list,
+ // we jump up to the previous RS layer list, picking up again where we
+ // had previously recursed into the current RS layer list.
+
+ if (!it->current_layer_represents_target_render_surface()) {
+ // Subtracting one here will eventually cause the current layer
+ // to become that layer representing the target render surface.
+ --it->current_layer_index_;
+ GoToHighestInSubtree(it);
+ } else {
+ while (it->current_layer_represents_target_render_surface()) {
+ if (!it->target_render_surface_layer_index_) {
+ // End of the list
+ it->target_render_surface_layer_index_ =
+ LayerIteratorValue::kInvalidTargetRenderSurfaceLayerIndex;
+ it->current_layer_index_ = 0;
+ return;
+ }
+ it->target_render_surface_layer_index_ = it->target_render_surface()
+ ->target_render_surface_layer_index_history_;
+ it->current_layer_index_ =
+ it->target_render_surface()->current_layer_index_history_;
+ }
+ }
+}
+
+template <typename LayerType,
+ typename LayerList,
+ typename RenderSurfaceType,
+ typename ActionType>
+void LayerIteratorActions::FrontToBack::GoToHighestInSubtree(
+ LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>* it) {
+ if (it->current_layer_represents_target_render_surface())
+ return;
+ while (it->current_layer_represents_contributing_render_surface()) {
+ // Save where we were in the current target surface, move to the next one,
+ // and save the target surface that we came from there
+ // so we can go back to it.
+ it->target_render_surface()->current_layer_index_history_ =
+ it->current_layer_index_;
+ int previous_target_render_surface_layer =
+ it->target_render_surface_layer_index_;
+
+ for (LayerType* layer = it->current_layer();
+ it->target_render_surface_layer() != layer;
+ ++it->target_render_surface_layer_index_) {
+ }
+ it->current_layer_index_ = it->target_render_surface_children().size() - 1;
+
+ it->target_render_surface()->target_render_surface_layer_index_history_ =
+ previous_target_render_surface_layer;
+ }
+}
+
+// Declare each of the above functions for Layer and LayerImpl classes
+// so that they are linked.
+template CC_EXPORT void LayerIteratorActions::BackToFront::Begin(
+ LayerIterator<Layer, RenderSurfaceLayerList, RenderSurface, BackToFront>*
+ it);
+template CC_EXPORT void LayerIteratorActions::BackToFront::End(
+ LayerIterator<Layer, RenderSurfaceLayerList, RenderSurface, BackToFront>*
+ it);
+template CC_EXPORT void LayerIteratorActions::BackToFront::Next(
+ LayerIterator<Layer, RenderSurfaceLayerList, RenderSurface, BackToFront>*
+ it);
+
+template CC_EXPORT void LayerIteratorActions::BackToFront::Begin(
+ LayerIterator<LayerImpl, LayerImplList, RenderSurfaceImpl, BackToFront>*
+ it);
+template CC_EXPORT void LayerIteratorActions::BackToFront::End(
+ LayerIterator<LayerImpl, LayerImplList, RenderSurfaceImpl, BackToFront>*
+ it);
+template CC_EXPORT void LayerIteratorActions::BackToFront::Next(
+ LayerIterator<LayerImpl, LayerImplList, RenderSurfaceImpl, BackToFront>*
+ it);
+
+template CC_EXPORT void LayerIteratorActions::FrontToBack::Next(
+ LayerIterator<Layer, RenderSurfaceLayerList, RenderSurface, FrontToBack>*
+ it);
+template CC_EXPORT void LayerIteratorActions::FrontToBack::End(
+ LayerIterator<Layer, RenderSurfaceLayerList, RenderSurface, FrontToBack>*
+ it);
+template CC_EXPORT void LayerIteratorActions::FrontToBack::Begin(
+ LayerIterator<Layer, RenderSurfaceLayerList, RenderSurface, FrontToBack>*
+ it);
+template CC_EXPORT void LayerIteratorActions::FrontToBack::GoToHighestInSubtree(
+ LayerIterator<Layer, RenderSurfaceLayerList, RenderSurface, FrontToBack>*
+ it);
+
+template CC_EXPORT void LayerIteratorActions::FrontToBack::Next(
+ LayerIterator<LayerImpl, LayerImplList, RenderSurfaceImpl, FrontToBack>*
+ it);
+template CC_EXPORT void LayerIteratorActions::FrontToBack::End(
+ LayerIterator<LayerImpl, LayerImplList, RenderSurfaceImpl, FrontToBack>*
+ it);
+template CC_EXPORT void LayerIteratorActions::FrontToBack::Begin(
+ LayerIterator<LayerImpl, LayerImplList, RenderSurfaceImpl, FrontToBack>*
+ it);
+template CC_EXPORT void LayerIteratorActions::FrontToBack::GoToHighestInSubtree(
+ LayerIterator<LayerImpl, LayerImplList, RenderSurfaceImpl, FrontToBack>*
+ it);
+
+} // namespace cc
diff --git a/chromium/cc/layers/layer_iterator.h b/chromium/cc/layers/layer_iterator.h
new file mode 100644
index 00000000000..b8a02c34e22
--- /dev/null
+++ b/chromium/cc/layers/layer_iterator.h
@@ -0,0 +1,304 @@
+// 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.
+
+#ifndef CC_LAYERS_LAYER_ITERATOR_H_
+#define CC_LAYERS_LAYER_ITERATOR_H_
+
+#include "base/memory/ref_counted.h"
+#include "cc/base/cc_export.h"
+#include "cc/trees/layer_tree_host_common.h"
+
+namespace cc {
+
+// These classes provide means to iterate over the
+// RenderSurface-Layer tree.
+
+// Example code follows, for a tree of Layer/RenderSurface objects.
+// See below for details.
+//
+// void DoStuffOnLayers(
+// const RenderSurfaceLayerList& render_surface_layer_list) {
+// typedef LayerIterator<Layer,
+// RenderSurfaceLayerList,
+// RenderSurface,
+// LayerIteratorActions::FrontToBack>
+// LayerIteratorType;
+//
+// LayerIteratorType end =
+// LayerIteratorType::End(&render_surface_layer_list);
+// for (LayerIteratorType
+// it = LayerIteratorType::Begin(&render_surface_layer_list);
+// it != end;
+// ++it) {
+// // Only one of these will be true
+// if (it.represents_target_render_surface())
+// foo(*it); // *it is a layer representing a target RenderSurface
+// if (it.represents_contributing_render_surface())
+// bar(*it); // *it is a layer representing a RenderSurface that
+// // contributes to the layer's target RenderSurface
+// if (it.represents_itself())
+// baz(*it); // *it is a layer representing itself,
+// // as it contributes to its own target RenderSurface
+// }
+// }
+
+// A RenderSurface R may be referred to in one of two different contexts.
+// One RenderSurface is "current" at any time, for whatever operation
+// is being performed. This current surface is referred to as a target surface.
+// For example, when R is being painted it would be the target surface.
+// Once R has been painted, its contents may be included into another
+// surface S. While S is considered the target surface when it is being
+// painted, R is called a contributing surface in this context as it
+// contributes to the content of the target surface S.
+//
+// The iterator's current position in the tree always points to some layer.
+// The state of the iterator indicates the role of the layer,
+// and will be one of the following three states.
+// A single layer L will appear in the iteration process in at least one,
+// and possibly all, of these states.
+// 1. Representing the target surface: The iterator in this state,
+// pointing at layer L, indicates that the target RenderSurface
+// is now the surface owned by L. This will occur exactly once for each
+// RenderSurface in the tree.
+// 2. Representing a contributing surface: The iterator in this state,
+// pointing at layer L, refers to the RenderSurface owned
+// by L as a contributing surface, without changing the current
+// target RenderSurface.
+// 3. Representing itself: The iterator in this state, pointing at layer L,
+// refers to the layer itself, as a child of the
+// current target RenderSurface.
+//
+// The BackToFront iterator will return a layer representing the target surface
+// before returning layers representing themselves as children of the current
+// target surface. Whereas the FrontToBack ordering will iterate over children
+// layers of a surface before the layer representing the surface
+// as a target surface.
+//
+// To use the iterators:
+//
+// Create a stepping iterator and end iterator by calling
+// LayerIterator::Begin() and LayerIterator::End() and passing in the
+// list of layers owning target RenderSurfaces. Step through the tree
+// by incrementing the stepping iterator while it is != to
+// the end iterator. At each step the iterator knows what the layer
+// is representing, and you can query the iterator to decide
+// what actions to perform with the layer given what it represents.
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Non-templated constants
+struct LayerIteratorValue {
+ static const int kInvalidTargetRenderSurfaceLayerIndex = -1;
+ // This must be -1 since the iterator action code assumes that this value can
+ // be reached by subtracting one from the position of the first layer in the
+ // current target surface's child layer list, which is 0.
+ static const int kLayerIndexRepresentingTargetRenderSurface = -1;
+};
+
+// The position of a layer iterator that is independent
+// of its many template types.
+template <typename LayerType> struct LayerIteratorPosition {
+ bool represents_target_render_surface;
+ bool represents_contributing_render_surface;
+ bool represents_itself;
+ LayerType* target_render_surface_layer;
+ LayerType* current_layer;
+};
+
+// An iterator class for walking over layers in the
+// RenderSurface-Layer tree.
+template <typename LayerType,
+ typename LayerList,
+ typename RenderSurfaceType,
+ typename IteratorActionType>
+class LayerIterator {
+ typedef LayerIterator<LayerType,
+ LayerList,
+ RenderSurfaceType,
+ IteratorActionType> LayerIteratorType;
+
+ public:
+ LayerIterator() : render_surface_layer_list_(NULL) {}
+
+ static LayerIteratorType Begin(const LayerList* render_surface_layer_list) {
+ return LayerIteratorType(render_surface_layer_list, true);
+ }
+ static LayerIteratorType End(const LayerList* render_surface_layer_list) {
+ return LayerIteratorType(render_surface_layer_list, false);
+ }
+
+ LayerIteratorType& operator++() {
+ actions_.Next(this);
+ return *this;
+ }
+ bool operator==(const LayerIterator& other) const {
+ return target_render_surface_layer_index_ ==
+ other.target_render_surface_layer_index_ &&
+ current_layer_index_ == other.current_layer_index_;
+ }
+ bool operator!=(const LayerIteratorType& other) const {
+ return !(*this == other);
+ }
+
+ LayerType* operator->() const { return current_layer(); }
+ LayerType* operator*() const { return current_layer(); }
+
+ bool represents_target_render_surface() const {
+ return current_layer_represents_target_render_surface();
+ }
+ bool represents_contributing_render_surface() const {
+ return !represents_target_render_surface() &&
+ current_layer_represents_contributing_render_surface();
+ }
+ bool represents_itself() const {
+ return !represents_target_render_surface() &&
+ !represents_contributing_render_surface();
+ }
+
+ LayerType* target_render_surface_layer() const {
+ return render_surface_layer_list_->at(target_render_surface_layer_index_);
+ }
+
+ operator const LayerIteratorPosition<LayerType>() const {
+ LayerIteratorPosition<LayerType> position;
+ position.represents_target_render_surface =
+ represents_target_render_surface();
+ position.represents_contributing_render_surface =
+ represents_contributing_render_surface();
+ position.represents_itself = represents_itself();
+ position.target_render_surface_layer = target_render_surface_layer();
+ position.current_layer = current_layer();
+ return position;
+ }
+
+ private:
+ LayerIterator(const LayerList* render_surface_layer_list, bool start)
+ : render_surface_layer_list_(render_surface_layer_list),
+ target_render_surface_layer_index_(0) {
+ for (size_t i = 0; i < render_surface_layer_list->size(); ++i) {
+ if (!render_surface_layer_list->at(i)->render_surface()) {
+ NOTREACHED();
+ actions_.End(this);
+ return;
+ }
+ }
+
+ if (start && !render_surface_layer_list->empty())
+ actions_.Begin(this);
+ else
+ actions_.End(this);
+ }
+
+ inline LayerType* current_layer() const {
+ return current_layer_represents_target_render_surface()
+ ? target_render_surface_layer()
+ : target_render_surface_children().at(current_layer_index_);
+ }
+
+ inline bool current_layer_represents_contributing_render_surface() const {
+ return LayerTreeHostCommon::RenderSurfaceContributesToTarget<LayerType>(
+ current_layer(), target_render_surface_layer()->id());
+ }
+ inline bool current_layer_represents_target_render_surface() const {
+ return current_layer_index_ ==
+ LayerIteratorValue::kLayerIndexRepresentingTargetRenderSurface;
+ }
+
+ inline RenderSurfaceType* target_render_surface() const {
+ return target_render_surface_layer()->render_surface();
+ }
+ inline const LayerList& target_render_surface_children() const {
+ return target_render_surface()->layer_list();
+ }
+
+ IteratorActionType actions_;
+ const LayerList* render_surface_layer_list_;
+
+ // The iterator's current position.
+
+ // A position in the render_surface_layer_list. This points to a layer which
+ // owns the current target surface. This is a value from 0 to n-1
+ // (n = size of render_surface_layer_list = number of surfaces).
+ // A value outside of this range
+ // (for example, LayerIteratorValue::kInvalidTargetRenderSurfaceLayerIndex)
+ // is used to indicate a position outside the bounds of the tree.
+ int target_render_surface_layer_index_;
+ // A position in the list of layers that are children of the
+ // current target surface. When pointing to one of these layers,
+ // this is a value from 0 to n-1 (n = number of children).
+ // Since the iterator must also stop at the layers representing
+ // the target surface, this is done by setting the current_layerIndex
+ // to a value of LayerIteratorValue::LayerRepresentingTargetRenderSurface.
+ int current_layer_index_;
+
+ friend struct LayerIteratorActions;
+};
+
+// Orderings for iterating over the RenderSurface-Layer tree.
+struct CC_EXPORT LayerIteratorActions {
+ // Walks layers sorted by z-order from back to front.
+ class CC_EXPORT BackToFront {
+ public:
+ template <typename LayerType,
+ typename LayerList,
+ typename RenderSurfaceType,
+ typename ActionType>
+ void Begin(
+ LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>* it);
+
+ template <typename LayerType,
+ typename LayerList,
+ typename RenderSurfaceType,
+ typename ActionType>
+ void End(
+ LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>* it);
+
+ template <typename LayerType,
+ typename LayerList,
+ typename RenderSurfaceType,
+ typename ActionType>
+ void Next(
+ LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>* it);
+
+ private:
+ int highest_target_render_surface_layer_;
+ };
+
+ // Walks layers sorted by z-order from front to back
+ class CC_EXPORT FrontToBack {
+ public:
+ template <typename LayerType,
+ typename LayerList,
+ typename RenderSurfaceType,
+ typename ActionType>
+ void Begin(
+ LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>* it);
+
+ template <typename LayerType,
+ typename LayerList,
+ typename RenderSurfaceType,
+ typename ActionType>
+ void End(
+ LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>* it);
+
+ template <typename LayerType,
+ typename LayerList,
+ typename RenderSurfaceType,
+ typename ActionType>
+ void Next(
+ LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>* it);
+
+ private:
+ template <typename LayerType,
+ typename LayerList,
+ typename RenderSurfaceType,
+ typename ActionType>
+ void GoToHighestInSubtree(
+ LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>* it);
+ };
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_LAYER_ITERATOR_H_
diff --git a/chromium/cc/layers/layer_iterator_unittest.cc b/chromium/cc/layers/layer_iterator_unittest.cc
new file mode 100644
index 00000000000..c2ab71b3f92
--- /dev/null
+++ b/chromium/cc/layers/layer_iterator_unittest.cc
@@ -0,0 +1,271 @@
+// 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 "cc/layers/layer_iterator.h"
+
+#include <vector>
+
+#include "cc/layers/layer.h"
+#include "cc/test/fake_layer_tree_host.h"
+#include "cc/trees/layer_tree_host_common.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/transform.h"
+
+using ::testing::Mock;
+using ::testing::_;
+using ::testing::AtLeast;
+using ::testing::AnyNumber;
+
+namespace cc {
+namespace {
+
+class TestLayer : public Layer {
+ public:
+ static scoped_refptr<TestLayer> Create() {
+ return make_scoped_refptr(new TestLayer());
+ }
+
+ int count_representing_target_surface_;
+ int count_representing_contributing_surface_;
+ int count_representing_itself_;
+
+ virtual bool DrawsContent() const OVERRIDE { return draws_content_; }
+ void set_draws_content(bool draws_content) { draws_content_ = draws_content; }
+
+ private:
+ TestLayer() : Layer(), draws_content_(true) {
+ SetBounds(gfx::Size(100, 100));
+ SetPosition(gfx::Point());
+ SetAnchorPoint(gfx::Point());
+ }
+ virtual ~TestLayer() {}
+
+ bool draws_content_;
+};
+
+#define EXPECT_COUNT(layer, target, contrib, itself) \
+ EXPECT_EQ(target, layer->count_representing_target_surface_); \
+ EXPECT_EQ(contrib, layer->count_representing_contributing_surface_); \
+ EXPECT_EQ(itself, layer->count_representing_itself_);
+
+typedef LayerIterator<Layer,
+ RenderSurfaceLayerList,
+ RenderSurface,
+ LayerIteratorActions::FrontToBack> FrontToBack;
+typedef LayerIterator<Layer,
+ RenderSurfaceLayerList,
+ RenderSurface,
+ LayerIteratorActions::BackToFront> BackToFront;
+
+void ResetCounts(RenderSurfaceLayerList* render_surface_layer_list) {
+ for (unsigned surface_index = 0;
+ surface_index < render_surface_layer_list->size();
+ ++surface_index) {
+ TestLayer* render_surface_layer = static_cast<TestLayer*>(
+ render_surface_layer_list->at(surface_index));
+ RenderSurface* render_surface = render_surface_layer->render_surface();
+
+ render_surface_layer->count_representing_target_surface_ = -1;
+ render_surface_layer->count_representing_contributing_surface_ = -1;
+ render_surface_layer->count_representing_itself_ = -1;
+
+ for (unsigned layer_index = 0;
+ layer_index < render_surface->layer_list().size();
+ ++layer_index) {
+ TestLayer* layer = static_cast<TestLayer*>(
+ render_surface->layer_list().at(layer_index));
+
+ layer->count_representing_target_surface_ = -1;
+ layer->count_representing_contributing_surface_ = -1;
+ layer->count_representing_itself_ = -1;
+ }
+ }
+}
+
+void IterateFrontToBack(
+ RenderSurfaceLayerList* render_surface_layer_list) {
+ ResetCounts(render_surface_layer_list);
+ int count = 0;
+ for (FrontToBack it = FrontToBack::Begin(render_surface_layer_list);
+ it != FrontToBack::End(render_surface_layer_list);
+ ++it, ++count) {
+ TestLayer* layer = static_cast<TestLayer*>(*it);
+ if (it.represents_target_render_surface())
+ layer->count_representing_target_surface_ = count;
+ if (it.represents_contributing_render_surface())
+ layer->count_representing_contributing_surface_ = count;
+ if (it.represents_itself())
+ layer->count_representing_itself_ = count;
+ }
+}
+
+void IterateBackToFront(
+ RenderSurfaceLayerList* render_surface_layer_list) {
+ ResetCounts(render_surface_layer_list);
+ int count = 0;
+ for (BackToFront it = BackToFront::Begin(render_surface_layer_list);
+ it != BackToFront::End(render_surface_layer_list);
+ ++it, ++count) {
+ TestLayer* layer = static_cast<TestLayer*>(*it);
+ if (it.represents_target_render_surface())
+ layer->count_representing_target_surface_ = count;
+ if (it.represents_contributing_render_surface())
+ layer->count_representing_contributing_surface_ = count;
+ if (it.represents_itself())
+ layer->count_representing_itself_ = count;
+ }
+}
+
+TEST(LayerIteratorTest, EmptyTree) {
+ RenderSurfaceLayerList render_surface_layer_list;
+
+ IterateBackToFront(&render_surface_layer_list);
+ IterateFrontToBack(&render_surface_layer_list);
+}
+
+TEST(LayerIteratorTest, SimpleTree) {
+ scoped_refptr<TestLayer> root_layer = TestLayer::Create();
+ scoped_refptr<TestLayer> first = TestLayer::Create();
+ scoped_refptr<TestLayer> second = TestLayer::Create();
+ scoped_refptr<TestLayer> third = TestLayer::Create();
+ scoped_refptr<TestLayer> fourth = TestLayer::Create();
+
+ root_layer->AddChild(first);
+ root_layer->AddChild(second);
+ root_layer->AddChild(third);
+ root_layer->AddChild(fourth);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root_layer);
+
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root_layer.get(), root_layer->bounds(), &render_surface_layer_list);
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ IterateBackToFront(&render_surface_layer_list);
+ EXPECT_COUNT(root_layer, 0, -1, 1);
+ EXPECT_COUNT(first, -1, -1, 2);
+ EXPECT_COUNT(second, -1, -1, 3);
+ EXPECT_COUNT(third, -1, -1, 4);
+ EXPECT_COUNT(fourth, -1, -1, 5);
+
+ IterateFrontToBack(&render_surface_layer_list);
+ EXPECT_COUNT(root_layer, 5, -1, 4);
+ EXPECT_COUNT(first, -1, -1, 3);
+ EXPECT_COUNT(second, -1, -1, 2);
+ EXPECT_COUNT(third, -1, -1, 1);
+ EXPECT_COUNT(fourth, -1, -1, 0);
+}
+
+TEST(LayerIteratorTest, ComplexTree) {
+ scoped_refptr<TestLayer> root_layer = TestLayer::Create();
+ scoped_refptr<TestLayer> root1 = TestLayer::Create();
+ scoped_refptr<TestLayer> root2 = TestLayer::Create();
+ scoped_refptr<TestLayer> root3 = TestLayer::Create();
+ scoped_refptr<TestLayer> root21 = TestLayer::Create();
+ scoped_refptr<TestLayer> root22 = TestLayer::Create();
+ scoped_refptr<TestLayer> root23 = TestLayer::Create();
+ scoped_refptr<TestLayer> root221 = TestLayer::Create();
+ scoped_refptr<TestLayer> root231 = TestLayer::Create();
+
+ root_layer->AddChild(root1);
+ root_layer->AddChild(root2);
+ root_layer->AddChild(root3);
+ root2->AddChild(root21);
+ root2->AddChild(root22);
+ root2->AddChild(root23);
+ root22->AddChild(root221);
+ root23->AddChild(root231);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root_layer);
+
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root_layer.get(), root_layer->bounds(), &render_surface_layer_list);
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ IterateBackToFront(&render_surface_layer_list);
+ EXPECT_COUNT(root_layer, 0, -1, 1);
+ EXPECT_COUNT(root1, -1, -1, 2);
+ EXPECT_COUNT(root2, -1, -1, 3);
+ EXPECT_COUNT(root21, -1, -1, 4);
+ EXPECT_COUNT(root22, -1, -1, 5);
+ EXPECT_COUNT(root221, -1, -1, 6);
+ EXPECT_COUNT(root23, -1, -1, 7);
+ EXPECT_COUNT(root231, -1, -1, 8);
+ EXPECT_COUNT(root3, -1, -1, 9);
+
+ IterateFrontToBack(&render_surface_layer_list);
+ EXPECT_COUNT(root_layer, 9, -1, 8);
+ EXPECT_COUNT(root1, -1, -1, 7);
+ EXPECT_COUNT(root2, -1, -1, 6);
+ EXPECT_COUNT(root21, -1, -1, 5);
+ EXPECT_COUNT(root22, -1, -1, 4);
+ EXPECT_COUNT(root221, -1, -1, 3);
+ EXPECT_COUNT(root23, -1, -1, 2);
+ EXPECT_COUNT(root231, -1, -1, 1);
+ EXPECT_COUNT(root3, -1, -1, 0);
+}
+
+TEST(LayerIteratorTest, ComplexTreeMultiSurface) {
+ scoped_refptr<TestLayer> root_layer = TestLayer::Create();
+ scoped_refptr<TestLayer> root1 = TestLayer::Create();
+ scoped_refptr<TestLayer> root2 = TestLayer::Create();
+ scoped_refptr<TestLayer> root3 = TestLayer::Create();
+ scoped_refptr<TestLayer> root21 = TestLayer::Create();
+ scoped_refptr<TestLayer> root22 = TestLayer::Create();
+ scoped_refptr<TestLayer> root23 = TestLayer::Create();
+ scoped_refptr<TestLayer> root221 = TestLayer::Create();
+ scoped_refptr<TestLayer> root231 = TestLayer::Create();
+
+ root_layer->AddChild(root1);
+ root_layer->AddChild(root2);
+ root_layer->AddChild(root3);
+ root2->set_draws_content(false);
+ root2->SetOpacity(0.5f);
+ root2->SetForceRenderSurface(true); // Force the layer to own a new surface.
+ root2->AddChild(root21);
+ root2->AddChild(root22);
+ root2->AddChild(root23);
+ root22->SetOpacity(0.5f);
+ root22->AddChild(root221);
+ root23->SetOpacity(0.5f);
+ root23->AddChild(root231);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root_layer);
+
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root_layer.get(), root_layer->bounds(), &render_surface_layer_list);
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ IterateBackToFront(&render_surface_layer_list);
+ EXPECT_COUNT(root_layer, 0, -1, 1);
+ EXPECT_COUNT(root1, -1, -1, 2);
+ EXPECT_COUNT(root2, 4, 3, -1);
+ EXPECT_COUNT(root21, -1, -1, 5);
+ EXPECT_COUNT(root22, 7, 6, 8);
+ EXPECT_COUNT(root221, -1, -1, 9);
+ EXPECT_COUNT(root23, 11, 10, 12);
+ EXPECT_COUNT(root231, -1, -1, 13);
+ EXPECT_COUNT(root3, -1, -1, 14);
+
+ IterateFrontToBack(&render_surface_layer_list);
+ EXPECT_COUNT(root_layer, 14, -1, 13);
+ EXPECT_COUNT(root1, -1, -1, 12);
+ EXPECT_COUNT(root2, 10, 11, -1);
+ EXPECT_COUNT(root21, -1, -1, 9);
+ EXPECT_COUNT(root22, 7, 8, 6);
+ EXPECT_COUNT(root221, -1, -1, 5);
+ EXPECT_COUNT(root23, 3, 4, 2);
+ EXPECT_COUNT(root231, -1, -1, 1);
+ EXPECT_COUNT(root3, -1, -1, 0);
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/layers/layer_lists.cc b/chromium/cc/layers/layer_lists.cc
new file mode 100644
index 00000000000..89cd4985701
--- /dev/null
+++ b/chromium/cc/layers/layer_lists.cc
@@ -0,0 +1,60 @@
+// Copyright 2013 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 "cc/layers/layer_lists.h"
+
+#include "cc/layers/layer.h"
+
+namespace cc {
+
+RenderSurfaceLayerList::RenderSurfaceLayerList() {}
+
+RenderSurfaceLayerList::~RenderSurfaceLayerList() {
+ for (size_t i = 0; i < size(); ++i)
+ at(size() - 1 - i)->ClearRenderSurface();
+}
+
+Layer* RenderSurfaceLayerList::at(size_t i) const {
+ return list_.at(i);
+}
+
+void RenderSurfaceLayerList::pop_back() {
+ list_.pop_back();
+}
+
+void RenderSurfaceLayerList::push_back(const scoped_refptr<Layer>& layer) {
+ list_.push_back(layer);
+}
+
+Layer* RenderSurfaceLayerList::back() {
+ return list_.back();
+}
+
+size_t RenderSurfaceLayerList::size() const {
+ return list_.size();
+}
+
+LayerList::iterator RenderSurfaceLayerList::begin() {
+ return list_.begin();
+}
+
+LayerList::iterator RenderSurfaceLayerList::end() {
+ return list_.end();
+}
+
+LayerList::const_iterator RenderSurfaceLayerList::begin() const {
+ return list_.begin();
+}
+
+LayerList::const_iterator RenderSurfaceLayerList::end() const {
+ return list_.end();
+}
+
+void RenderSurfaceLayerList::clear() {
+ for (size_t i = 0; i < list_.size(); ++i)
+ DCHECK(!list_[i]->render_surface());
+ list_.clear();
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/layer_lists.h b/chromium/cc/layers/layer_lists.h
new file mode 100644
index 00000000000..24a9c3c14d6
--- /dev/null
+++ b/chromium/cc/layers/layer_lists.h
@@ -0,0 +1,48 @@
+// Copyright 2013 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.
+
+#ifndef CC_LAYERS_LAYER_LISTS_H_
+#define CC_LAYERS_LAYER_LISTS_H_
+
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "cc/base/cc_export.h"
+#include "cc/base/scoped_ptr_vector.h"
+
+namespace cc {
+class Layer;
+class LayerImpl;
+
+typedef std::vector<scoped_refptr<Layer> > LayerList;
+
+typedef ScopedPtrVector<LayerImpl> OwnedLayerImplList;
+typedef std::vector<LayerImpl*> LayerImplList;
+
+class CC_EXPORT RenderSurfaceLayerList {
+ public:
+ RenderSurfaceLayerList();
+ ~RenderSurfaceLayerList();
+
+ Layer* at(size_t i) const;
+ void pop_back();
+ void push_back(const scoped_refptr<Layer>& layer);
+ Layer* back();
+ size_t size() const;
+ bool empty() const { return size() == 0u; }
+ LayerList::iterator begin();
+ LayerList::iterator end();
+ LayerList::const_iterator begin() const;
+ LayerList::const_iterator end() const;
+ void clear();
+
+ private:
+ LayerList list_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderSurfaceLayerList);
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_LAYER_LISTS_H_
diff --git a/chromium/cc/layers/layer_position_constraint.cc b/chromium/cc/layers/layer_position_constraint.cc
new file mode 100644
index 00000000000..183b6b5b53e
--- /dev/null
+++ b/chromium/cc/layers/layer_position_constraint.cc
@@ -0,0 +1,29 @@
+// Copyright 2013 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 "cc/layers/layer_position_constraint.h"
+
+namespace cc {
+
+LayerPositionConstraint::LayerPositionConstraint()
+ : is_fixed_position_(false),
+ is_fixed_to_right_edge_(false),
+ is_fixed_to_bottom_edge_(false) {
+}
+
+bool LayerPositionConstraint::operator==(
+ const LayerPositionConstraint& other) const {
+ if (!is_fixed_position_ && !other.is_fixed_position_)
+ return true;
+ return is_fixed_position_ == other.is_fixed_position_ &&
+ is_fixed_to_right_edge_ == other.is_fixed_to_right_edge_ &&
+ is_fixed_to_bottom_edge_ == other.is_fixed_to_bottom_edge_;
+}
+
+bool LayerPositionConstraint::operator!=(
+ const LayerPositionConstraint& other) const {
+ return !(*this == other);
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/layer_position_constraint.h b/chromium/cc/layers/layer_position_constraint.h
new file mode 100644
index 00000000000..9a575137736
--- /dev/null
+++ b/chromium/cc/layers/layer_position_constraint.h
@@ -0,0 +1,38 @@
+// Copyright 2013 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.
+
+#ifndef CC_LAYERS_LAYER_POSITION_CONSTRAINT_H_
+#define CC_LAYERS_LAYER_POSITION_CONSTRAINT_H_
+
+#include "cc/base/cc_export.h"
+
+namespace cc {
+
+class CC_EXPORT LayerPositionConstraint {
+ public:
+ LayerPositionConstraint();
+
+ void set_is_fixed_position(bool fixed) { is_fixed_position_ = fixed; }
+ bool is_fixed_position() const { return is_fixed_position_; }
+ void set_is_fixed_to_right_edge(bool fixed) {
+ is_fixed_to_right_edge_ = fixed;
+ }
+ bool is_fixed_to_right_edge() const { return is_fixed_to_right_edge_; }
+ void set_is_fixed_to_bottom_edge(bool fixed) {
+ is_fixed_to_bottom_edge_ = fixed;
+ }
+ bool is_fixed_to_bottom_edge() const { return is_fixed_to_bottom_edge_; }
+
+ bool operator==(const LayerPositionConstraint&) const;
+ bool operator!=(const LayerPositionConstraint&) const;
+
+ private:
+ bool is_fixed_position_ : 1;
+ bool is_fixed_to_right_edge_ : 1;
+ bool is_fixed_to_bottom_edge_ : 1;
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_LAYER_POSITION_CONSTRAINT_H_
diff --git a/chromium/cc/layers/layer_position_constraint_unittest.cc b/chromium/cc/layers/layer_position_constraint_unittest.cc
new file mode 100644
index 00000000000..6384ba45327
--- /dev/null
+++ b/chromium/cc/layers/layer_position_constraint_unittest.cc
@@ -0,0 +1,1098 @@
+// Copyright 2013 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 "cc/layers/layer_position_constraint.h"
+
+#include <vector>
+
+#include "cc/layers/layer_impl.h"
+#include "cc/test/fake_impl_proxy.h"
+#include "cc/test/fake_layer_tree_host_impl.h"
+#include "cc/test/geometry_test_utils.h"
+#include "cc/trees/layer_tree_host_common.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+void SetLayerPropertiesForTesting(LayerImpl* layer,
+ const gfx::Transform& transform,
+ const gfx::Transform& sublayer_transform,
+ gfx::PointF anchor,
+ gfx::PointF position,
+ gfx::Size bounds,
+ bool preserves3d) {
+ layer->SetTransform(transform);
+ layer->SetSublayerTransform(sublayer_transform);
+ layer->SetAnchorPoint(anchor);
+ layer->SetPosition(position);
+ layer->SetBounds(bounds);
+ layer->SetPreserves3d(preserves3d);
+ layer->SetContentBounds(bounds);
+}
+
+void ExecuteCalculateDrawProperties(LayerImpl* root_layer,
+ float device_scale_factor,
+ float page_scale_factor,
+ LayerImpl* page_scale_application_layer,
+ bool can_use_lcd_text) {
+ gfx::Transform identity_matrix;
+ std::vector<LayerImpl*> dummy_render_surface_layer_list;
+ gfx::Size device_viewport_size =
+ gfx::Size(root_layer->bounds().width() * device_scale_factor,
+ root_layer->bounds().height() * device_scale_factor);
+
+ // We are probably not testing what is intended if the root_layer bounds are
+ // empty.
+ DCHECK(!root_layer->bounds().IsEmpty());
+ LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
+ root_layer, device_viewport_size, &dummy_render_surface_layer_list);
+ inputs.device_scale_factor = device_scale_factor;
+ inputs.page_scale_factor = page_scale_factor;
+ inputs.page_scale_application_layer = page_scale_application_layer;
+ inputs.can_use_lcd_text = can_use_lcd_text;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+}
+
+void ExecuteCalculateDrawProperties(LayerImpl* root_layer) {
+ LayerImpl* page_scale_application_layer = NULL;
+ ExecuteCalculateDrawProperties(
+ root_layer, 1.f, 1.f, page_scale_application_layer, false);
+}
+
+class LayerPositionConstraintTest : public testing::Test {
+ public:
+ LayerPositionConstraintTest()
+ : host_impl_(&proxy_) {
+ root_ = CreateTreeForTest();
+ fixed_to_top_left_.set_is_fixed_position(true);
+ fixed_to_bottom_right_.set_is_fixed_position(true);
+ fixed_to_bottom_right_.set_is_fixed_to_right_edge(true);
+ fixed_to_bottom_right_.set_is_fixed_to_bottom_edge(true);
+ }
+
+ scoped_ptr<LayerImpl> CreateTreeForTest() {
+ scoped_ptr<LayerImpl> root =
+ LayerImpl::Create(host_impl_.active_tree(), 1);
+ scoped_ptr<LayerImpl> child =
+ LayerImpl::Create(host_impl_.active_tree(), 2);
+ scoped_ptr<LayerImpl> grand_child =
+ LayerImpl::Create(host_impl_.active_tree(), 3);
+ scoped_ptr<LayerImpl> great_grand_child =
+ LayerImpl::Create(host_impl_.active_tree(), 4);
+
+ gfx::Transform IdentityMatrix;
+ gfx::PointF anchor;
+ gfx::PointF position;
+ gfx::Size bounds(100, 100);
+ SetLayerPropertiesForTesting(root.get(),
+ IdentityMatrix,
+ IdentityMatrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ SetLayerPropertiesForTesting(child.get(),
+ IdentityMatrix,
+ IdentityMatrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ SetLayerPropertiesForTesting(grand_child.get(),
+ IdentityMatrix,
+ IdentityMatrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ SetLayerPropertiesForTesting(great_grand_child.get(),
+ IdentityMatrix,
+ IdentityMatrix,
+ anchor,
+ position,
+ bounds,
+ false);
+
+ root->SetMaxScrollOffset(gfx::Vector2d(100, 100));
+ root->SetScrollable(true);
+ child->SetMaxScrollOffset(gfx::Vector2d(100, 100));
+ child->SetScrollable(true);
+ grand_child->SetMaxScrollOffset(gfx::Vector2d(100, 100));
+ grand_child->SetScrollable(true);
+
+ grand_child->AddChild(great_grand_child.Pass());
+ child->AddChild(grand_child.Pass());
+ root->AddChild(child.Pass());
+
+ return root.Pass();
+ }
+
+ protected:
+ FakeImplProxy proxy_;
+ FakeLayerTreeHostImpl host_impl_;
+ scoped_ptr<LayerImpl> root_;
+
+ LayerPositionConstraint fixed_to_top_left_;
+ LayerPositionConstraint fixed_to_bottom_right_;
+};
+
+TEST_F(LayerPositionConstraintTest,
+ ScrollCompensationForFixedPositionLayerWithDirectContainer) {
+ // This test checks for correct scroll compensation when the fixed-position
+ // container is the direct parent of the fixed-position layer.
+ LayerImpl* child = root_->children()[0];
+ LayerImpl* grand_child = child->children()[0];
+
+ child->SetIsContainerForFixedPositionLayers(true);
+ grand_child->SetPositionConstraint(fixed_to_top_left_);
+
+ // Case 1: scroll delta of 0, 0
+ child->SetScrollDelta(gfx::Vector2d(0, 0));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ gfx::Transform expected_child_transform;
+ gfx::Transform expected_grand_child_transform = expected_child_transform;
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+
+ // Case 2: scroll delta of 10, 10
+ child->SetScrollDelta(gfx::Vector2d(10, 10));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // Here the child is affected by scroll delta, but the fixed position
+ // grand_child should not be affected.
+ expected_child_transform.MakeIdentity();
+ expected_child_transform.Translate(-10.0, -10.0);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+
+ // Case 3: fixed-container size delta of 20, 20
+ child->SetFixedContainerSizeDelta(gfx::Vector2d(20, 20));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // Top-left fixed-position layer should not be affected by container size.
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+
+ // Case 4: Bottom-right fixed-position layer.
+ grand_child->SetPositionConstraint(fixed_to_bottom_right_);
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // Bottom-right fixed-position layer moves as container resizes.
+ expected_grand_child_transform.MakeIdentity();
+ // Apply size delta from the child(container) layer.
+ expected_grand_child_transform.Translate(20.0, 20.0);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+}
+
+TEST_F(LayerPositionConstraintTest,
+ ScrollCompensationForFixedPositionLayerWithTransformedDirectContainer) {
+ // This test checks for correct scroll compensation when the fixed-position
+ // container is the direct parent of the fixed-position layer, but that
+ // container is transformed. In this case, the fixed position element
+ // inherits the container's transform, but the scroll delta that has to be
+ // undone should not be affected by that transform.
+ //
+ // Transforms are in general non-commutative; using something like a
+ // non-uniform scale helps to verify that translations and non-uniform scales
+ // are applied in the correct order.
+ LayerImpl* child = root_->children()[0];
+ LayerImpl* grand_child = child->children()[0];
+
+ // This scale will cause child and grand_child to be effectively 200 x 800
+ // with respect to the render target.
+ gfx::Transform non_uniform_scale;
+ non_uniform_scale.Scale(2.0, 8.0);
+ child->SetTransform(non_uniform_scale);
+
+ child->SetIsContainerForFixedPositionLayers(true);
+ grand_child->SetPositionConstraint(fixed_to_top_left_);
+
+ // Case 1: scroll delta of 0, 0
+ child->SetScrollDelta(gfx::Vector2d(0, 0));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ gfx::Transform expected_child_transform;
+ expected_child_transform.PreconcatTransform(non_uniform_scale);
+
+ gfx::Transform expected_grand_child_transform = expected_child_transform;
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+
+ // Case 2: scroll delta of 10, 20
+ child->SetScrollDelta(gfx::Vector2d(10, 20));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // The child should be affected by scroll delta, but the fixed position
+ // grand_child should not be affected.
+ expected_child_transform.MakeIdentity();
+ expected_child_transform.Translate(-10.0, -20.0); // scroll delta
+ expected_child_transform.PreconcatTransform(non_uniform_scale);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+
+ // Case 3: fixed-container size delta of 20, 20
+ child->SetFixedContainerSizeDelta(gfx::Vector2d(20, 20));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // Top-left fixed-position layer should not be affected by container size.
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+
+ // Case 4: Bottom-right fixed-position layer.
+ grand_child->SetPositionConstraint(fixed_to_bottom_right_);
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // Bottom-right fixed-position layer moves as container resizes.
+ expected_grand_child_transform.MakeIdentity();
+ // Apply child layer transform.
+ expected_grand_child_transform.PreconcatTransform(non_uniform_scale);
+ // Apply size delta from the child(container) layer.
+ expected_grand_child_transform.Translate(20.0, 20.0);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+}
+
+TEST_F(LayerPositionConstraintTest,
+ ScrollCompensationForFixedPositionLayerWithDistantContainer) {
+ // This test checks for correct scroll compensation when the fixed-position
+ // container is NOT the direct parent of the fixed-position layer.
+ LayerImpl* child = root_->children()[0];
+ LayerImpl* grand_child = child->children()[0];
+ LayerImpl* great_grand_child = grand_child->children()[0];
+
+ child->SetIsContainerForFixedPositionLayers(true);
+ grand_child->SetPosition(gfx::PointF(8.f, 6.f));
+ great_grand_child->SetPositionConstraint(fixed_to_top_left_);
+
+ // Case 1: scroll delta of 0, 0
+ child->SetScrollDelta(gfx::Vector2d(0, 0));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ gfx::Transform expected_child_transform;
+ gfx::Transform expected_grand_child_transform;
+ expected_grand_child_transform.Translate(8.0, 6.0);
+
+ gfx::Transform expected_great_grand_child_transform =
+ expected_grand_child_transform;
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_great_grand_child_transform,
+ great_grand_child->draw_transform());
+
+ // Case 2: scroll delta of 10, 10
+ child->SetScrollDelta(gfx::Vector2d(10, 10));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // Here the child and grand_child are affected by scroll delta, but the fixed
+ // position great_grand_child should not be affected.
+ expected_child_transform.MakeIdentity();
+ expected_child_transform.Translate(-10.0, -10.0);
+ expected_grand_child_transform.MakeIdentity();
+ expected_grand_child_transform.Translate(-2.0, -4.0);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_great_grand_child_transform,
+ great_grand_child->draw_transform());
+
+ // Case 3: fixed-container size delta of 20, 20
+ child->SetFixedContainerSizeDelta(gfx::Vector2d(20, 20));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // Top-left fixed-position layer should not be affected by container size.
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_great_grand_child_transform,
+ great_grand_child->draw_transform());
+
+ // Case 4: Bottom-right fixed-position layer.
+ great_grand_child->SetPositionConstraint(fixed_to_bottom_right_);
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // Bottom-right fixed-position layer moves as container resizes.
+ expected_great_grand_child_transform.MakeIdentity();
+ // Apply size delta from the child(container) layer.
+ expected_great_grand_child_transform.Translate(20.0, 20.0);
+ // Apply layer position from the grand child layer.
+ expected_great_grand_child_transform.Translate(8.0, 6.0);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_great_grand_child_transform,
+ great_grand_child->draw_transform());
+}
+
+TEST_F(LayerPositionConstraintTest,
+ ScrollCompensationForFixedPositionLayerWithDistantContainerAndTransforms) {
+ // This test checks for correct scroll compensation when the fixed-position
+ // container is NOT the direct parent of the fixed-position layer, and the
+ // hierarchy has various transforms that have to be processed in the correct
+ // order.
+ LayerImpl* child = root_->children()[0];
+ LayerImpl* grand_child = child->children()[0];
+ LayerImpl* great_grand_child = grand_child->children()[0];
+
+ gfx::Transform rotation_about_z;
+ rotation_about_z.RotateAboutZAxis(90.0);
+
+ child->SetIsContainerForFixedPositionLayers(true);
+ child->SetTransform(rotation_about_z);
+ grand_child->SetPosition(gfx::PointF(8.f, 6.f));
+ grand_child->SetTransform(rotation_about_z);
+ // great_grand_child is positioned upside-down with respect to the render
+ // target.
+ great_grand_child->SetPositionConstraint(fixed_to_top_left_);
+
+ // Case 1: scroll delta of 0, 0
+ child->SetScrollDelta(gfx::Vector2d(0, 0));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ gfx::Transform expected_child_transform;
+ expected_child_transform.PreconcatTransform(rotation_about_z);
+
+ gfx::Transform expected_grand_child_transform;
+ expected_grand_child_transform.PreconcatTransform(
+ rotation_about_z); // child's local transform is inherited
+ // translation because of position occurs before layer's local transform.
+ expected_grand_child_transform.Translate(8.0, 6.0);
+ expected_grand_child_transform.PreconcatTransform(
+ rotation_about_z); // grand_child's local transform
+
+ gfx::Transform expected_great_grand_child_transform =
+ expected_grand_child_transform;
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_great_grand_child_transform,
+ great_grand_child->draw_transform());
+
+ // Case 2: scroll delta of 10, 20
+ child->SetScrollDelta(gfx::Vector2d(10, 20));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // Here the child and grand_child are affected by scroll delta, but the fixed
+ // position great_grand_child should not be affected.
+ expected_child_transform.MakeIdentity();
+ expected_child_transform.Translate(-10.0, -20.0); // scroll delta
+ expected_child_transform.PreconcatTransform(rotation_about_z);
+
+ expected_grand_child_transform.MakeIdentity();
+ expected_grand_child_transform.Translate(
+ -10.0, -20.0); // child's scroll delta is inherited
+ expected_grand_child_transform.PreconcatTransform(
+ rotation_about_z); // child's local transform is inherited
+ // translation because of position occurs before layer's local transform.
+ expected_grand_child_transform.Translate(8.0, 6.0);
+ expected_grand_child_transform.PreconcatTransform(
+ rotation_about_z); // grand_child's local transform
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_great_grand_child_transform,
+ great_grand_child->draw_transform());
+
+ // Case 3: fixed-container size delta of 20, 20
+ child->SetFixedContainerSizeDelta(gfx::Vector2d(20, 20));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // Top-left fixed-position layer should not be affected by container size.
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_great_grand_child_transform,
+ great_grand_child->draw_transform());
+
+ // Case 4: Bottom-right fixed-position layer.
+ great_grand_child->SetPositionConstraint(fixed_to_bottom_right_);
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // Bottom-right fixed-position layer moves as container resizes.
+ expected_great_grand_child_transform.MakeIdentity();
+ // Apply child layer transform.
+ expected_great_grand_child_transform.PreconcatTransform(rotation_about_z);
+ // Apply size delta from the child(container) layer.
+ expected_great_grand_child_transform.Translate(20.0, 20.0);
+ // Apply layer position from the grand child layer.
+ expected_great_grand_child_transform.Translate(8.0, 6.0);
+ // Apply grand child layer transform.
+ expected_great_grand_child_transform.PreconcatTransform(rotation_about_z);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_great_grand_child_transform,
+ great_grand_child->draw_transform());
+}
+
+TEST_F(LayerPositionConstraintTest,
+ ScrollCompensationForFixedPositionLayerWithMultipleScrollDeltas) {
+ // This test checks for correct scroll compensation when the fixed-position
+ // container has multiple ancestors that have nonzero scroll delta before
+ // reaching the space where the layer is fixed. In this test, each scroll
+ // delta occurs in a different space because of each layer's local transform.
+ // This test checks for correct scroll compensation when the fixed-position
+ // container is NOT the direct parent of the fixed-position layer, and the
+ // hierarchy has various transforms that have to be processed in the correct
+ // order.
+ LayerImpl* child = root_->children()[0];
+ LayerImpl* grand_child = child->children()[0];
+ LayerImpl* great_grand_child = grand_child->children()[0];
+
+ gfx::Transform rotation_about_z;
+ rotation_about_z.RotateAboutZAxis(90.0);
+
+ child->SetIsContainerForFixedPositionLayers(true);
+ child->SetTransform(rotation_about_z);
+ grand_child->SetPosition(gfx::PointF(8.f, 6.f));
+ grand_child->SetTransform(rotation_about_z);
+ // great_grand_child is positioned upside-down with respect to the render
+ // target.
+ great_grand_child->SetPositionConstraint(fixed_to_top_left_);
+
+ // Case 1: scroll delta of 0, 0
+ child->SetScrollDelta(gfx::Vector2d(0, 0));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ gfx::Transform expected_child_transform;
+ expected_child_transform.PreconcatTransform(rotation_about_z);
+
+ gfx::Transform expected_grand_child_transform;
+ expected_grand_child_transform.PreconcatTransform(
+ rotation_about_z); // child's local transform is inherited
+ // translation because of position occurs before layer's local transform.
+ expected_grand_child_transform.Translate(8.0, 6.0);
+ expected_grand_child_transform.PreconcatTransform(
+ rotation_about_z); // grand_child's local transform
+
+ gfx::Transform expected_great_grand_child_transform =
+ expected_grand_child_transform;
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_great_grand_child_transform,
+ great_grand_child->draw_transform());
+
+ // Case 2: scroll delta of 10, 20
+ child->SetScrollDelta(gfx::Vector2d(10, 0));
+ grand_child->SetScrollDelta(gfx::Vector2d(5, 0));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // Here the child and grand_child are affected by scroll delta, but the fixed
+ // position great_grand_child should not be affected.
+ expected_child_transform.MakeIdentity();
+ expected_child_transform.Translate(-10.0, 0.0); // scroll delta
+ expected_child_transform.PreconcatTransform(rotation_about_z);
+
+ expected_grand_child_transform.MakeIdentity();
+ expected_grand_child_transform.Translate(
+ -10.0, 0.0); // child's scroll delta is inherited
+ expected_grand_child_transform.PreconcatTransform(
+ rotation_about_z); // child's local transform is inherited
+ expected_grand_child_transform.Translate(-5.0,
+ 0.0); // grand_child's scroll delta
+ // translation because of position occurs before layer's local transform.
+ expected_grand_child_transform.Translate(8.0, 6.0);
+ expected_grand_child_transform.PreconcatTransform(
+ rotation_about_z); // grand_child's local transform
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_great_grand_child_transform,
+ great_grand_child->draw_transform());
+
+ // Case 3: fixed-container size delta of 20, 20
+ child->SetFixedContainerSizeDelta(gfx::Vector2d(20, 20));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // Top-left fixed-position layer should not be affected by container size.
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_great_grand_child_transform,
+ great_grand_child->draw_transform());
+
+ // Case 4: Bottom-right fixed-position layer.
+ great_grand_child->SetPositionConstraint(fixed_to_bottom_right_);
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // Bottom-right fixed-position layer moves as container resizes.
+ expected_great_grand_child_transform.MakeIdentity();
+ // Apply child layer transform.
+ expected_great_grand_child_transform.PreconcatTransform(rotation_about_z);
+ // Apply size delta from the child(container) layer.
+ expected_great_grand_child_transform.Translate(20.0, 20.0);
+ // Apply layer position from the grand child layer.
+ expected_great_grand_child_transform.Translate(8.0, 6.0);
+ // Apply grand child layer transform.
+ expected_great_grand_child_transform.PreconcatTransform(rotation_about_z);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_great_grand_child_transform,
+ great_grand_child->draw_transform());
+}
+
+TEST_F(LayerPositionConstraintTest,
+ ScrollCompensationForFixedPositionWithIntermediateSurfaceAndTransforms) {
+ // This test checks for correct scroll compensation when the fixed-position
+ // container contributes to a different render surface than the fixed-position
+ // layer. In this case, the surface draw transforms also have to be accounted
+ // for when checking the scroll delta.
+ LayerImpl* child = root_->children()[0];
+ LayerImpl* grand_child = child->children()[0];
+ LayerImpl* great_grand_child = grand_child->children()[0];
+
+ child->SetIsContainerForFixedPositionLayers(true);
+ grand_child->SetPosition(gfx::PointF(8.f, 6.f));
+ grand_child->SetForceRenderSurface(true);
+ great_grand_child->SetPositionConstraint(fixed_to_top_left_);
+ great_grand_child->SetDrawsContent(true);
+
+ gfx::Transform rotation_about_z;
+ rotation_about_z.RotateAboutZAxis(90.0);
+ grand_child->SetTransform(rotation_about_z);
+
+ // Case 1: scroll delta of 0, 0
+ child->SetScrollDelta(gfx::Vector2d(0, 0));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ gfx::Transform expected_child_transform;
+ gfx::Transform expected_surface_draw_transform;
+ expected_surface_draw_transform.Translate(8.0, 6.0);
+ expected_surface_draw_transform.PreconcatTransform(rotation_about_z);
+ gfx::Transform expected_grand_child_transform;
+ gfx::Transform expected_great_grand_child_transform;
+ ASSERT_TRUE(grand_child->render_surface());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected_surface_draw_transform,
+ grand_child->render_surface()->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_great_grand_child_transform,
+ great_grand_child->draw_transform());
+
+ // Case 2: scroll delta of 10, 30
+ child->SetScrollDelta(gfx::Vector2d(10, 30));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // Here the grand_child remains unchanged, because it scrolls along with the
+ // render surface, and the translation is actually in the render surface. But,
+ // the fixed position great_grand_child is more awkward: its actually being
+ // drawn with respect to the render surface, but it needs to remain fixed with
+ // resepct to a container beyond that surface. So, the net result is that,
+ // unlike previous tests where the fixed position layer's transform remains
+ // unchanged, here the fixed position layer's transform explicitly contains
+ // the translation that cancels out the scroll.
+ expected_child_transform.MakeIdentity();
+ expected_child_transform.Translate(-10.0, -30.0); // scroll delta
+
+ expected_surface_draw_transform.MakeIdentity();
+ expected_surface_draw_transform.Translate(-10.0, -30.0); // scroll delta
+ expected_surface_draw_transform.Translate(8.0, 6.0);
+ expected_surface_draw_transform.PreconcatTransform(rotation_about_z);
+
+ // The rotation and its inverse are needed to place the scroll delta
+ // compensation in the correct space. This test will fail if the
+ // rotation/inverse are backwards, too, so it requires perfect order of
+ // operations.
+ expected_great_grand_child_transform.MakeIdentity();
+ expected_great_grand_child_transform.PreconcatTransform(
+ Inverse(rotation_about_z));
+ // explicit canceling out the scroll delta that gets embedded in the fixed
+ // position layer's surface.
+ expected_great_grand_child_transform.Translate(10.0, 30.0);
+ expected_great_grand_child_transform.PreconcatTransform(rotation_about_z);
+
+ ASSERT_TRUE(grand_child->render_surface());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected_surface_draw_transform,
+ grand_child->render_surface()->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_great_grand_child_transform,
+ great_grand_child->draw_transform());
+
+ // Case 3: fixed-container size delta of 20, 20
+ child->SetFixedContainerSizeDelta(gfx::Vector2d(20, 20));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // Top-left fixed-position layer should not be affected by container size.
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_great_grand_child_transform,
+ great_grand_child->draw_transform());
+
+ // Case 4: Bottom-right fixed-position layer.
+ great_grand_child->SetPositionConstraint(fixed_to_bottom_right_);
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // Bottom-right fixed-position layer moves as container resizes.
+ expected_great_grand_child_transform.MakeIdentity();
+ // The rotation and its inverse are needed to place the scroll delta
+ // compensation in the correct space. This test will fail if the
+ // rotation/inverse are backwards, too, so it requires perfect order of
+ // operations.
+ expected_great_grand_child_transform.PreconcatTransform(
+ Inverse(rotation_about_z));
+ // explicit canceling out the scroll delta that gets embedded in the fixed
+ // position layer's surface.
+ expected_great_grand_child_transform.Translate(10.0, 30.0);
+ // Also apply size delta in the child(container) layer space.
+ expected_great_grand_child_transform.Translate(20.0, 20.0);
+ expected_great_grand_child_transform.PreconcatTransform(rotation_about_z);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_great_grand_child_transform,
+ great_grand_child->draw_transform());
+}
+
+TEST_F(LayerPositionConstraintTest,
+ ScrollCompensationForFixedPositionLayerWithMultipleIntermediateSurfaces) {
+ // This test checks for correct scroll compensation when the fixed-position
+ // container contributes to a different render surface than the fixed-position
+ // layer, with additional render surfaces in-between. This checks that the
+ // conversion to ancestor surfaces is accumulated properly in the final matrix
+ // transform.
+ LayerImpl* child = root_->children()[0];
+ LayerImpl* grand_child = child->children()[0];
+ LayerImpl* great_grand_child = grand_child->children()[0];
+
+ // Add one more layer to the test tree for this scenario.
+ {
+ gfx::Transform identity;
+ scoped_ptr<LayerImpl> fixed_position_child =
+ LayerImpl::Create(host_impl_.active_tree(), 5);
+ SetLayerPropertiesForTesting(fixed_position_child.get(),
+ identity,
+ identity,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ great_grand_child->AddChild(fixed_position_child.Pass());
+ }
+ LayerImpl* fixed_position_child = great_grand_child->children()[0];
+
+ // Actually set up the scenario here.
+ child->SetIsContainerForFixedPositionLayers(true);
+ grand_child->SetPosition(gfx::PointF(8.f, 6.f));
+ grand_child->SetForceRenderSurface(true);
+ great_grand_child->SetPosition(gfx::PointF(40.f, 60.f));
+ great_grand_child->SetForceRenderSurface(true);
+ fixed_position_child->SetPositionConstraint(fixed_to_top_left_);
+ fixed_position_child->SetDrawsContent(true);
+
+ // The additional rotations, which are non-commutative with translations, help
+ // to verify that we have correct order-of-operations in the final scroll
+ // compensation. Note that rotating about the center of the layer ensures we
+ // do not accidentally clip away layers that we want to test.
+ gfx::Transform rotation_about_z;
+ rotation_about_z.Translate(50.0, 50.0);
+ rotation_about_z.RotateAboutZAxis(90.0);
+ rotation_about_z.Translate(-50.0, -50.0);
+ grand_child->SetTransform(rotation_about_z);
+ great_grand_child->SetTransform(rotation_about_z);
+
+ // Case 1: scroll delta of 0, 0
+ child->SetScrollDelta(gfx::Vector2d(0, 0));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ gfx::Transform expected_child_transform;
+
+ gfx::Transform expected_grand_child_surface_draw_transform;
+ expected_grand_child_surface_draw_transform.Translate(8.0, 6.0);
+ expected_grand_child_surface_draw_transform.PreconcatTransform(
+ rotation_about_z);
+
+ gfx::Transform expected_grand_child_transform;
+
+ gfx::Transform expected_great_grand_child_surface_draw_transform;
+ expected_great_grand_child_surface_draw_transform.Translate(40.0, 60.0);
+ expected_great_grand_child_surface_draw_transform.PreconcatTransform(
+ rotation_about_z);
+
+ gfx::Transform expected_great_grand_child_transform;
+
+ gfx::Transform expected_fixed_position_child_transform;
+
+ ASSERT_TRUE(grand_child->render_surface());
+ ASSERT_TRUE(great_grand_child->render_surface());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected_grand_child_surface_draw_transform,
+ grand_child->render_surface()->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected_great_grand_child_surface_draw_transform,
+ great_grand_child->render_surface()->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_great_grand_child_transform,
+ great_grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_fixed_position_child_transform,
+ fixed_position_child->draw_transform());
+
+ // Case 2: scroll delta of 10, 30
+ child->SetScrollDelta(gfx::Vector2d(10, 30));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ expected_child_transform.MakeIdentity();
+ expected_child_transform.Translate(-10.0, -30.0); // scroll delta
+
+ expected_grand_child_surface_draw_transform.MakeIdentity();
+ expected_grand_child_surface_draw_transform.Translate(-10.0,
+ -30.0); // scroll delta
+ expected_grand_child_surface_draw_transform.Translate(8.0, 6.0);
+ expected_grand_child_surface_draw_transform.PreconcatTransform(
+ rotation_about_z);
+
+ // grand_child, great_grand_child, and great_grand_child's surface are not
+ // expected to change, since they are all not fixed, and they are all drawn
+ // with respect to grand_child's surface that already has the scroll delta
+ // accounted for.
+
+ // But the great-great grandchild, "fixed_position_child", should have a
+ // transform that explicitly cancels out the scroll delta. The expected
+ // transform is: compound_draw_transform.Inverse() * translate(positive scroll
+ // delta) * compound_origin_transform from great_grand_childSurface's origin
+ // to the root surface.
+ gfx::Transform compound_draw_transform;
+ compound_draw_transform.Translate(8.0,
+ 6.0); // origin translation of grand_child
+ compound_draw_transform.PreconcatTransform(
+ rotation_about_z); // rotation of grand_child
+ compound_draw_transform.Translate(
+ 40.0, 60.0); // origin translation of great_grand_child
+ compound_draw_transform.PreconcatTransform(
+ rotation_about_z); // rotation of great_grand_child
+
+ expected_fixed_position_child_transform.MakeIdentity();
+ expected_fixed_position_child_transform.PreconcatTransform(
+ Inverse(compound_draw_transform));
+ // explicit canceling out the scroll delta that gets embedded in the fixed
+ // position layer's surface.
+ expected_fixed_position_child_transform.Translate(10.0, 30.0);
+ expected_fixed_position_child_transform.PreconcatTransform(
+ compound_draw_transform);
+
+ ASSERT_TRUE(grand_child->render_surface());
+ ASSERT_TRUE(great_grand_child->render_surface());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected_grand_child_surface_draw_transform,
+ grand_child->render_surface()->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected_great_grand_child_surface_draw_transform,
+ great_grand_child->render_surface()->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_great_grand_child_transform,
+ great_grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_fixed_position_child_transform,
+ fixed_position_child->draw_transform());
+
+
+ // Case 3: fixed-container size delta of 20, 20
+ child->SetFixedContainerSizeDelta(gfx::Vector2d(20, 20));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // Top-left fixed-position layer should not be affected by container size.
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_great_grand_child_transform,
+ great_grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_fixed_position_child_transform,
+ fixed_position_child->draw_transform());
+
+ // Case 4: Bottom-right fixed-position layer.
+ fixed_position_child->SetPositionConstraint(fixed_to_bottom_right_);
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // Bottom-right fixed-position layer moves as container resizes.
+ expected_fixed_position_child_transform.MakeIdentity();
+ expected_fixed_position_child_transform.PreconcatTransform(
+ Inverse(compound_draw_transform));
+ // explicit canceling out the scroll delta that gets embedded in the fixed
+ // position layer's surface.
+ expected_fixed_position_child_transform.Translate(10.0, 30.0);
+ // Also apply size delta in the child(container) layer space.
+ expected_fixed_position_child_transform.Translate(20.0, 20.0);
+ expected_fixed_position_child_transform.PreconcatTransform(
+ compound_draw_transform);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_great_grand_child_transform,
+ great_grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_fixed_position_child_transform,
+ fixed_position_child->draw_transform());
+}
+
+TEST_F(LayerPositionConstraintTest,
+ ScrollCompensationForFixedPositionLayerWithContainerLayerThatHasSurface) {
+ // This test checks for correct scroll compensation when the fixed-position
+ // container itself has a render surface. In this case, the container layer
+ // should be treated like a layer that contributes to a render target, and
+ // that render target is completely irrelevant; it should not affect the
+ // scroll compensation.
+ LayerImpl* child = root_->children()[0];
+ LayerImpl* grand_child = child->children()[0];
+
+ child->SetIsContainerForFixedPositionLayers(true);
+ child->SetForceRenderSurface(true);
+ grand_child->SetPositionConstraint(fixed_to_top_left_);
+ grand_child->SetDrawsContent(true);
+
+ // Case 1: scroll delta of 0, 0
+ child->SetScrollDelta(gfx::Vector2d(0, 0));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ gfx::Transform expected_surface_draw_transform;
+ expected_surface_draw_transform.Translate(0.0, 0.0);
+ gfx::Transform expected_child_transform;
+ gfx::Transform expected_grand_child_transform;
+ ASSERT_TRUE(child->render_surface());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_surface_draw_transform,
+ child->render_surface()->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+
+ // Case 2: scroll delta of 10, 10
+ child->SetScrollDelta(gfx::Vector2d(10, 10));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // The surface is translated by scroll delta, the child transform doesn't
+ // change because it scrolls along with the surface, but the fixed position
+ // grand_child needs to compensate for the scroll translation.
+ expected_surface_draw_transform.MakeIdentity();
+ expected_surface_draw_transform.Translate(-10.0, -10.0);
+ expected_grand_child_transform.MakeIdentity();
+ expected_grand_child_transform.Translate(10.0, 10.0);
+
+ ASSERT_TRUE(child->render_surface());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_surface_draw_transform,
+ child->render_surface()->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+
+ // Case 3: fixed-container size delta of 20, 20
+ child->SetFixedContainerSizeDelta(gfx::Vector2d(20, 20));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // Top-left fixed-position layer should not be affected by container size.
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+
+ // Case 4: Bottom-right fixed-position layer.
+ grand_child->SetPositionConstraint(fixed_to_bottom_right_);
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // Bottom-right fixed-position layer moves as container resizes.
+ expected_grand_child_transform.MakeIdentity();
+ // The surface is translated by scroll delta, the child transform doesn't
+ // change because it scrolls along with the surface, but the fixed position
+ // grand_child needs to compensate for the scroll translation.
+ expected_grand_child_transform.Translate(10.0, 10.0);
+ // Apply size delta from the child(container) layer.
+ expected_grand_child_transform.Translate(20.0, 20.0);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+}
+
+TEST_F(LayerPositionConstraintTest,
+ ScrollCompensationForFixedPositionLayerThatIsAlsoFixedPositionContainer) {
+ // This test checks the scenario where a fixed-position layer also happens to
+ // be a container itself for a descendant fixed position layer. In particular,
+ // the layer should not accidentally be fixed to itself.
+ LayerImpl* child = root_->children()[0];
+ LayerImpl* grand_child = child->children()[0];
+
+ child->SetIsContainerForFixedPositionLayers(true);
+ grand_child->SetPositionConstraint(fixed_to_top_left_);
+
+ // This should not confuse the grand_child. If correct, the grand_child would
+ // still be considered fixed to its container (i.e. "child").
+ grand_child->SetIsContainerForFixedPositionLayers(true);
+
+ // Case 1: scroll delta of 0, 0
+ child->SetScrollDelta(gfx::Vector2d(0, 0));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ gfx::Transform expected_child_transform;
+ gfx::Transform expected_grand_child_transform;
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+
+ // Case 2: scroll delta of 10, 10
+ child->SetScrollDelta(gfx::Vector2d(10, 10));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // Here the child is affected by scroll delta, but the fixed position
+ // grand_child should not be affected.
+ expected_child_transform.MakeIdentity();
+ expected_child_transform.Translate(-10.0, -10.0);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+
+ // Case 3: fixed-container size delta of 20, 20
+ child->SetFixedContainerSizeDelta(gfx::Vector2d(20, 20));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // Top-left fixed-position layer should not be affected by container size.
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+
+ // Case 4: Bottom-right fixed-position layer.
+ grand_child->SetPositionConstraint(fixed_to_bottom_right_);
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // Bottom-right fixed-position layer moves as container resizes.
+ expected_grand_child_transform.MakeIdentity();
+ // Apply size delta from the child(container) layer.
+ expected_grand_child_transform.Translate(20.0, 20.0);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+}
+
+TEST_F(LayerPositionConstraintTest,
+ ScrollCompensationForFixedPositionLayerThatHasNoContainer) {
+ // This test checks scroll compensation when a fixed-position layer does not
+ // find any ancestor that is a "containerForFixedPositionLayers". In this
+ // situation, the layer should be fixed to the root layer.
+ LayerImpl* child = root_->children()[0];
+ LayerImpl* grand_child = child->children()[0];
+
+ gfx::Transform rotation_by_z;
+ rotation_by_z.RotateAboutZAxis(90.0);
+
+ root_->SetTransform(rotation_by_z);
+ grand_child->SetPositionConstraint(fixed_to_top_left_);
+
+ // Case 1: root scroll delta of 0, 0
+ root_->SetScrollDelta(gfx::Vector2d(0, 0));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ gfx::Transform identity_matrix;
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(rotation_by_z, child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(rotation_by_z,
+ grand_child->draw_transform());
+
+ // Case 2: root scroll delta of 10, 10
+ root_->SetScrollDelta(gfx::Vector2d(10, 20));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ gfx::Transform expected_child_transform;
+ expected_child_transform.Translate(-10.0, -20.0);
+ expected_child_transform.PreconcatTransform(rotation_by_z);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(rotation_by_z,
+ grand_child->draw_transform());
+
+ // Case 3: fixed-container size delta of 20, 20
+ root_->SetFixedContainerSizeDelta(gfx::Vector2d(20, 20));
+ ExecuteCalculateDrawProperties(root_.get());
+
+ // Top-left fixed-position layer should not be affected by container size.
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(rotation_by_z,
+ grand_child->draw_transform());
+
+ // Case 4: Bottom-right fixed-position layer.
+ grand_child->SetPositionConstraint(fixed_to_bottom_right_);
+ ExecuteCalculateDrawProperties(root_.get());
+
+ gfx::Transform expected_grand_child_transform;
+ expected_grand_child_transform.Translate(-20.0, 20.0);
+ expected_grand_child_transform.PreconcatTransform(rotation_by_z);
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_transform,
+ grand_child->draw_transform());
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/layers/layer_unittest.cc b/chromium/cc/layers/layer_unittest.cc
new file mode 100644
index 00000000000..bd1e16fa658
--- /dev/null
+++ b/chromium/cc/layers/layer_unittest.cc
@@ -0,0 +1,1037 @@
+// Copyright 2011 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 "cc/layers/layer.h"
+
+#include "cc/animation/keyframed_animation_curve.h"
+#include "cc/base/math_util.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/resources/layer_painter.h"
+#include "cc/test/animation_test_common.h"
+#include "cc/test/fake_impl_proxy.h"
+#include "cc/test/fake_layer_tree_host_client.h"
+#include "cc/test/fake_layer_tree_host_impl.h"
+#include "cc/test/geometry_test_utils.h"
+#include "cc/test/layer_test_common.h"
+#include "cc/trees/layer_tree_host.h"
+#include "cc/trees/single_thread_proxy.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/transform.h"
+
+using ::testing::AnyNumber;
+using ::testing::AtLeast;
+using ::testing::Mock;
+using ::testing::StrictMock;
+using ::testing::_;
+
+#define EXPECT_SET_NEEDS_FULL_TREE_SYNC(expect, code_to_test) do { \
+ EXPECT_CALL(*layer_tree_host_, SetNeedsFullTreeSync()).Times((expect)); \
+ code_to_test; \
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get()); \
+ } while (false)
+
+
+namespace cc {
+namespace {
+
+class MockLayerTreeHost : public LayerTreeHost {
+ public:
+ explicit MockLayerTreeHost(LayerTreeHostClient* client)
+ : LayerTreeHost(client, LayerTreeSettings()) {
+ Initialize(NULL);
+ }
+
+ MOCK_METHOD0(SetNeedsCommit, void());
+ MOCK_METHOD0(SetNeedsUpdateLayers, void());
+ MOCK_METHOD0(SetNeedsFullTreeSync, void());
+};
+
+class MockLayerPainter : public LayerPainter {
+ public:
+ virtual void Paint(SkCanvas* canvas,
+ gfx::Rect content_rect,
+ gfx::RectF* opaque) OVERRIDE {}
+};
+
+
+class LayerTest : public testing::Test {
+ public:
+ LayerTest()
+ : host_impl_(&proxy_),
+ fake_client_(FakeLayerTreeHostClient::DIRECT_3D) {}
+
+ protected:
+ virtual void SetUp() OVERRIDE {
+ layer_tree_host_.reset(new StrictMock<MockLayerTreeHost>(&fake_client_));
+ }
+
+ virtual void TearDown() OVERRIDE {
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+ EXPECT_CALL(*layer_tree_host_, SetNeedsFullTreeSync()).Times(AnyNumber());
+ parent_ = NULL;
+ child1_ = NULL;
+ child2_ = NULL;
+ child3_ = NULL;
+ grand_child1_ = NULL;
+ grand_child2_ = NULL;
+ grand_child3_ = NULL;
+
+ layer_tree_host_->SetRootLayer(NULL);
+ layer_tree_host_.reset();
+ }
+
+ void VerifyTestTreeInitialState() const {
+ ASSERT_EQ(3U, parent_->children().size());
+ EXPECT_EQ(child1_, parent_->children()[0]);
+ EXPECT_EQ(child2_, parent_->children()[1]);
+ EXPECT_EQ(child3_, parent_->children()[2]);
+ EXPECT_EQ(parent_.get(), child1_->parent());
+ EXPECT_EQ(parent_.get(), child2_->parent());
+ EXPECT_EQ(parent_.get(), child3_->parent());
+
+ ASSERT_EQ(2U, child1_->children().size());
+ EXPECT_EQ(grand_child1_, child1_->children()[0]);
+ EXPECT_EQ(grand_child2_, child1_->children()[1]);
+ EXPECT_EQ(child1_.get(), grand_child1_->parent());
+ EXPECT_EQ(child1_.get(), grand_child2_->parent());
+
+ ASSERT_EQ(1U, child2_->children().size());
+ EXPECT_EQ(grand_child3_, child2_->children()[0]);
+ EXPECT_EQ(child2_.get(), grand_child3_->parent());
+
+ ASSERT_EQ(0U, child3_->children().size());
+ }
+
+ void CreateSimpleTestTree() {
+ parent_ = Layer::Create();
+ child1_ = Layer::Create();
+ child2_ = Layer::Create();
+ child3_ = Layer::Create();
+ grand_child1_ = Layer::Create();
+ grand_child2_ = Layer::Create();
+ grand_child3_ = Layer::Create();
+
+ EXPECT_CALL(*layer_tree_host_, SetNeedsFullTreeSync()).Times(AnyNumber());
+ layer_tree_host_->SetRootLayer(parent_);
+
+ parent_->AddChild(child1_);
+ parent_->AddChild(child2_);
+ parent_->AddChild(child3_);
+ child1_->AddChild(grand_child1_);
+ child1_->AddChild(grand_child2_);
+ child2_->AddChild(grand_child3_);
+
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+
+ VerifyTestTreeInitialState();
+ }
+
+ FakeImplProxy proxy_;
+ FakeLayerTreeHostImpl host_impl_;
+
+ FakeLayerTreeHostClient fake_client_;
+ scoped_ptr<StrictMock<MockLayerTreeHost> > layer_tree_host_;
+ scoped_refptr<Layer> parent_;
+ scoped_refptr<Layer> child1_;
+ scoped_refptr<Layer> child2_;
+ scoped_refptr<Layer> child3_;
+ scoped_refptr<Layer> grand_child1_;
+ scoped_refptr<Layer> grand_child2_;
+ scoped_refptr<Layer> grand_child3_;
+};
+
+TEST_F(LayerTest, BasicCreateAndDestroy) {
+ scoped_refptr<Layer> test_layer = Layer::Create();
+ ASSERT_TRUE(test_layer.get());
+
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(0);
+ test_layer->SetLayerTreeHost(layer_tree_host_.get());
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(0);
+ test_layer->SetLayerTreeHost(NULL);
+}
+
+TEST_F(LayerTest, AddAndRemoveChild) {
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<Layer> child = Layer::Create();
+
+ // Upon creation, layers should not have children or parent.
+ ASSERT_EQ(0U, parent->children().size());
+ EXPECT_FALSE(child->parent());
+
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, layer_tree_host_->SetRootLayer(parent));
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, parent->AddChild(child));
+
+ ASSERT_EQ(1U, parent->children().size());
+ EXPECT_EQ(child.get(), parent->children()[0]);
+ EXPECT_EQ(parent.get(), child->parent());
+ EXPECT_EQ(parent.get(), child->RootLayer());
+
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(AtLeast(1), child->RemoveFromParent());
+}
+
+TEST_F(LayerTest, AddSameChildTwice) {
+ EXPECT_CALL(*layer_tree_host_, SetNeedsFullTreeSync()).Times(AtLeast(1));
+
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<Layer> child = Layer::Create();
+
+ layer_tree_host_->SetRootLayer(parent);
+
+ ASSERT_EQ(0u, parent->children().size());
+
+ parent->AddChild(child);
+ ASSERT_EQ(1u, parent->children().size());
+ EXPECT_EQ(parent.get(), child->parent());
+
+ parent->AddChild(child);
+ ASSERT_EQ(1u, parent->children().size());
+ EXPECT_EQ(parent.get(), child->parent());
+}
+
+TEST_F(LayerTest, InsertChild) {
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<Layer> child1 = Layer::Create();
+ scoped_refptr<Layer> child2 = Layer::Create();
+ scoped_refptr<Layer> child3 = Layer::Create();
+ scoped_refptr<Layer> child4 = Layer::Create();
+
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, layer_tree_host_->SetRootLayer(parent));
+
+ ASSERT_EQ(0U, parent->children().size());
+
+ // Case 1: inserting to empty list.
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, parent->InsertChild(child3, 0));
+ ASSERT_EQ(1U, parent->children().size());
+ EXPECT_EQ(child3, parent->children()[0]);
+ EXPECT_EQ(parent.get(), child3->parent());
+
+ // Case 2: inserting to beginning of list
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, parent->InsertChild(child1, 0));
+ ASSERT_EQ(2U, parent->children().size());
+ EXPECT_EQ(child1, parent->children()[0]);
+ EXPECT_EQ(child3, parent->children()[1]);
+ EXPECT_EQ(parent.get(), child1->parent());
+
+ // Case 3: inserting to middle of list
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, parent->InsertChild(child2, 1));
+ ASSERT_EQ(3U, parent->children().size());
+ EXPECT_EQ(child1, parent->children()[0]);
+ EXPECT_EQ(child2, parent->children()[1]);
+ EXPECT_EQ(child3, parent->children()[2]);
+ EXPECT_EQ(parent.get(), child2->parent());
+
+ // Case 4: inserting to end of list
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, parent->InsertChild(child4, 3));
+
+ ASSERT_EQ(4U, parent->children().size());
+ EXPECT_EQ(child1, parent->children()[0]);
+ EXPECT_EQ(child2, parent->children()[1]);
+ EXPECT_EQ(child3, parent->children()[2]);
+ EXPECT_EQ(child4, parent->children()[3]);
+ EXPECT_EQ(parent.get(), child4->parent());
+
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, layer_tree_host_->SetRootLayer(NULL));
+}
+
+TEST_F(LayerTest, InsertChildPastEndOfList) {
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<Layer> child1 = Layer::Create();
+ scoped_refptr<Layer> child2 = Layer::Create();
+
+ ASSERT_EQ(0U, parent->children().size());
+
+ // insert to an out-of-bounds index
+ parent->InsertChild(child1, 53);
+
+ ASSERT_EQ(1U, parent->children().size());
+ EXPECT_EQ(child1, parent->children()[0]);
+
+ // insert another child to out-of-bounds, when list is not already empty.
+ parent->InsertChild(child2, 2459);
+
+ ASSERT_EQ(2U, parent->children().size());
+ EXPECT_EQ(child1, parent->children()[0]);
+ EXPECT_EQ(child2, parent->children()[1]);
+}
+
+TEST_F(LayerTest, InsertSameChildTwice) {
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<Layer> child1 = Layer::Create();
+ scoped_refptr<Layer> child2 = Layer::Create();
+
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, layer_tree_host_->SetRootLayer(parent));
+
+ ASSERT_EQ(0U, parent->children().size());
+
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, parent->InsertChild(child1, 0));
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, parent->InsertChild(child2, 1));
+
+ ASSERT_EQ(2U, parent->children().size());
+ EXPECT_EQ(child1, parent->children()[0]);
+ EXPECT_EQ(child2, parent->children()[1]);
+
+ // Inserting the same child again should cause the child to be removed and
+ // re-inserted at the new location.
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(AtLeast(1), parent->InsertChild(child1, 1));
+
+ // child1 should now be at the end of the list.
+ ASSERT_EQ(2U, parent->children().size());
+ EXPECT_EQ(child2, parent->children()[0]);
+ EXPECT_EQ(child1, parent->children()[1]);
+
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, layer_tree_host_->SetRootLayer(NULL));
+}
+
+TEST_F(LayerTest, ReplaceChildWithNewChild) {
+ CreateSimpleTestTree();
+ scoped_refptr<Layer> child4 = Layer::Create();
+
+ EXPECT_FALSE(child4->parent());
+
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(
+ AtLeast(1), parent_->ReplaceChild(child2_.get(), child4));
+ EXPECT_FALSE(parent_->NeedsDisplayForTesting());
+ EXPECT_FALSE(child1_->NeedsDisplayForTesting());
+ EXPECT_FALSE(child2_->NeedsDisplayForTesting());
+ EXPECT_FALSE(child3_->NeedsDisplayForTesting());
+ EXPECT_FALSE(child4->NeedsDisplayForTesting());
+
+ ASSERT_EQ(static_cast<size_t>(3), parent_->children().size());
+ EXPECT_EQ(child1_, parent_->children()[0]);
+ EXPECT_EQ(child4, parent_->children()[1]);
+ EXPECT_EQ(child3_, parent_->children()[2]);
+ EXPECT_EQ(parent_.get(), child4->parent());
+
+ EXPECT_FALSE(child2_->parent());
+}
+
+TEST_F(LayerTest, ReplaceChildWithNewChildThatHasOtherParent) {
+ CreateSimpleTestTree();
+
+ // create another simple tree with test_layer and child4.
+ scoped_refptr<Layer> test_layer = Layer::Create();
+ scoped_refptr<Layer> child4 = Layer::Create();
+ test_layer->AddChild(child4);
+ ASSERT_EQ(1U, test_layer->children().size());
+ EXPECT_EQ(child4, test_layer->children()[0]);
+ EXPECT_EQ(test_layer.get(), child4->parent());
+
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(
+ AtLeast(1), parent_->ReplaceChild(child2_.get(), child4));
+
+ ASSERT_EQ(3U, parent_->children().size());
+ EXPECT_EQ(child1_, parent_->children()[0]);
+ EXPECT_EQ(child4, parent_->children()[1]);
+ EXPECT_EQ(child3_, parent_->children()[2]);
+ EXPECT_EQ(parent_.get(), child4->parent());
+
+ // test_layer should no longer have child4,
+ // and child2 should no longer have a parent.
+ ASSERT_EQ(0U, test_layer->children().size());
+ EXPECT_FALSE(child2_->parent());
+}
+
+TEST_F(LayerTest, ReplaceChildWithSameChild) {
+ CreateSimpleTestTree();
+
+ // SetNeedsFullTreeSync / SetNeedsCommit should not be called because its the
+ // same child.
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(0);
+ EXPECT_CALL(*layer_tree_host_, SetNeedsFullTreeSync()).Times(0);
+ parent_->ReplaceChild(child2_.get(), child2_);
+
+ VerifyTestTreeInitialState();
+}
+
+TEST_F(LayerTest, RemoveAllChildren) {
+ CreateSimpleTestTree();
+
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(AtLeast(3), parent_->RemoveAllChildren());
+
+ ASSERT_EQ(0U, parent_->children().size());
+ EXPECT_FALSE(child1_->parent());
+ EXPECT_FALSE(child2_->parent());
+ EXPECT_FALSE(child3_->parent());
+}
+
+TEST_F(LayerTest, SetChildren) {
+ scoped_refptr<Layer> old_parent = Layer::Create();
+ scoped_refptr<Layer> new_parent = Layer::Create();
+
+ scoped_refptr<Layer> child1 = Layer::Create();
+ scoped_refptr<Layer> child2 = Layer::Create();
+
+ LayerList new_children;
+ new_children.push_back(child1);
+ new_children.push_back(child2);
+
+ // Set up and verify initial test conditions: child1 has a parent, child2 has
+ // no parent.
+ old_parent->AddChild(child1);
+ ASSERT_EQ(0U, new_parent->children().size());
+ EXPECT_EQ(old_parent.get(), child1->parent());
+ EXPECT_FALSE(child2->parent());
+
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(
+ 1, layer_tree_host_->SetRootLayer(new_parent));
+
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(
+ AtLeast(1), new_parent->SetChildren(new_children));
+
+ ASSERT_EQ(2U, new_parent->children().size());
+ EXPECT_EQ(new_parent.get(), child1->parent());
+ EXPECT_EQ(new_parent.get(), child2->parent());
+
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, layer_tree_host_->SetRootLayer(NULL));
+}
+
+TEST_F(LayerTest, HasAncestor) {
+ scoped_refptr<Layer> parent = Layer::Create();
+ EXPECT_FALSE(parent->HasAncestor(parent));
+
+ scoped_refptr<Layer> child = Layer::Create();
+ parent->AddChild(child);
+
+ EXPECT_FALSE(child->HasAncestor(child));
+ EXPECT_TRUE(child->HasAncestor(parent));
+ EXPECT_FALSE(parent->HasAncestor(child));
+
+ scoped_refptr<Layer> child_child = Layer::Create();
+ child->AddChild(child_child);
+
+ EXPECT_FALSE(child_child->HasAncestor(child_child));
+ EXPECT_TRUE(child_child->HasAncestor(parent));
+ EXPECT_TRUE(child_child->HasAncestor(child));
+ EXPECT_FALSE(parent->HasAncestor(child));
+ EXPECT_FALSE(parent->HasAncestor(child_child));
+}
+
+TEST_F(LayerTest, GetRootLayerAfterTreeManipulations) {
+ CreateSimpleTestTree();
+
+ // For this test we don't care about SetNeedsFullTreeSync calls.
+ EXPECT_CALL(*layer_tree_host_, SetNeedsFullTreeSync()).Times(AnyNumber());
+
+ scoped_refptr<Layer> child4 = Layer::Create();
+
+ EXPECT_EQ(parent_.get(), parent_->RootLayer());
+ EXPECT_EQ(parent_.get(), child1_->RootLayer());
+ EXPECT_EQ(parent_.get(), child2_->RootLayer());
+ EXPECT_EQ(parent_.get(), child3_->RootLayer());
+ EXPECT_EQ(child4.get(), child4->RootLayer());
+ EXPECT_EQ(parent_.get(), grand_child1_->RootLayer());
+ EXPECT_EQ(parent_.get(), grand_child2_->RootLayer());
+ EXPECT_EQ(parent_.get(), grand_child3_->RootLayer());
+
+ child1_->RemoveFromParent();
+
+ // |child1| and its children, grand_child1 and grand_child2 are now on a
+ // separate subtree.
+ EXPECT_EQ(parent_.get(), parent_->RootLayer());
+ EXPECT_EQ(child1_.get(), child1_->RootLayer());
+ EXPECT_EQ(parent_.get(), child2_->RootLayer());
+ EXPECT_EQ(parent_.get(), child3_->RootLayer());
+ EXPECT_EQ(child4.get(), child4->RootLayer());
+ EXPECT_EQ(child1_.get(), grand_child1_->RootLayer());
+ EXPECT_EQ(child1_.get(), grand_child2_->RootLayer());
+ EXPECT_EQ(parent_.get(), grand_child3_->RootLayer());
+
+ grand_child3_->AddChild(child4);
+
+ EXPECT_EQ(parent_.get(), parent_->RootLayer());
+ EXPECT_EQ(child1_.get(), child1_->RootLayer());
+ EXPECT_EQ(parent_.get(), child2_->RootLayer());
+ EXPECT_EQ(parent_.get(), child3_->RootLayer());
+ EXPECT_EQ(parent_.get(), child4->RootLayer());
+ EXPECT_EQ(child1_.get(), grand_child1_->RootLayer());
+ EXPECT_EQ(child1_.get(), grand_child2_->RootLayer());
+ EXPECT_EQ(parent_.get(), grand_child3_->RootLayer());
+
+ child2_->ReplaceChild(grand_child3_.get(), child1_);
+
+ // |grand_child3| gets orphaned and the child1 subtree gets planted back into
+ // the tree under child2.
+ EXPECT_EQ(parent_.get(), parent_->RootLayer());
+ EXPECT_EQ(parent_.get(), child1_->RootLayer());
+ EXPECT_EQ(parent_.get(), child2_->RootLayer());
+ EXPECT_EQ(parent_.get(), child3_->RootLayer());
+ EXPECT_EQ(grand_child3_.get(), child4->RootLayer());
+ EXPECT_EQ(parent_.get(), grand_child1_->RootLayer());
+ EXPECT_EQ(parent_.get(), grand_child2_->RootLayer());
+ EXPECT_EQ(grand_child3_.get(), grand_child3_->RootLayer());
+}
+
+TEST_F(LayerTest, CheckSetNeedsDisplayCausesCorrectBehavior) {
+ // The semantics for SetNeedsDisplay which are tested here:
+ // 1. sets NeedsDisplay flag appropriately.
+ // 2. indirectly calls SetNeedsUpdate, exactly once for each call to
+ // SetNeedsDisplay.
+
+ scoped_refptr<Layer> test_layer = Layer::Create();
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(
+ 1, layer_tree_host_->SetRootLayer(test_layer));
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetIsDrawable(true));
+
+ gfx::Size test_bounds = gfx::Size(501, 508);
+
+ gfx::RectF dirty1 = gfx::RectF(10.f, 15.f, 1.f, 2.f);
+ gfx::RectF dirty2 = gfx::RectF(20.f, 25.f, 3.f, 4.f);
+ gfx::RectF empty_dirty_rect = gfx::RectF(40.f, 45.f, 0.f, 0.f);
+ gfx::RectF out_of_bounds_dirty_rect = gfx::RectF(400.f, 405.f, 500.f, 502.f);
+
+ // Before anything, test_layer should not be dirty.
+ EXPECT_FALSE(test_layer->NeedsDisplayForTesting());
+
+ // This is just initialization, but SetNeedsCommit behavior is verified anyway
+ // to avoid warnings.
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetBounds(test_bounds));
+ EXPECT_FALSE(test_layer->NeedsDisplayForTesting());
+
+ // The real test begins here.
+ test_layer->ResetNeedsDisplayForTesting();
+ EXPECT_FALSE(test_layer->NeedsDisplayForTesting());
+
+ // Case 1: Layer should accept dirty rects that go beyond its bounds.
+ test_layer->ResetNeedsDisplayForTesting();
+ EXPECT_FALSE(test_layer->NeedsDisplayForTesting());
+ EXPECT_SET_NEEDS_UPDATE(
+ 1, test_layer->SetNeedsDisplayRect(out_of_bounds_dirty_rect));
+ EXPECT_TRUE(test_layer->NeedsDisplayForTesting());
+ test_layer->ResetNeedsDisplayForTesting();
+
+ // Case 2: SetNeedsDisplay() without the dirty rect arg.
+ test_layer->ResetNeedsDisplayForTesting();
+ EXPECT_FALSE(test_layer->NeedsDisplayForTesting());
+ EXPECT_SET_NEEDS_UPDATE(1, test_layer->SetNeedsDisplay());
+ EXPECT_TRUE(test_layer->NeedsDisplayForTesting());
+ test_layer->ResetNeedsDisplayForTesting();
+
+ // Case 3: SetNeedsDisplay() with an empty rect.
+ test_layer->ResetNeedsDisplayForTesting();
+ EXPECT_FALSE(test_layer->NeedsDisplayForTesting());
+ EXPECT_SET_NEEDS_COMMIT(0, test_layer->SetNeedsDisplayRect(gfx::Rect()));
+ EXPECT_FALSE(test_layer->NeedsDisplayForTesting());
+
+ // Case 4: SetNeedsDisplay() with a non-drawable layer
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetIsDrawable(false));
+ test_layer->ResetNeedsDisplayForTesting();
+ EXPECT_FALSE(test_layer->NeedsDisplayForTesting());
+ EXPECT_SET_NEEDS_UPDATE(0, test_layer->SetNeedsDisplayRect(dirty1));
+ EXPECT_TRUE(test_layer->NeedsDisplayForTesting());
+}
+
+TEST_F(LayerTest, CheckPropertyChangeCausesCorrectBehavior) {
+ scoped_refptr<Layer> test_layer = Layer::Create();
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(
+ 1, layer_tree_host_->SetRootLayer(test_layer));
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetIsDrawable(true));
+
+ scoped_refptr<Layer> dummy_layer1 = Layer::Create();
+ scoped_refptr<Layer> dummy_layer2 = Layer::Create();
+
+ // sanity check of initial test condition
+ EXPECT_FALSE(test_layer->NeedsDisplayForTesting());
+
+ // Next, test properties that should call SetNeedsCommit (but not
+ // SetNeedsDisplay). All properties need to be set to new values in order for
+ // SetNeedsCommit to be called.
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetAnchorPoint(
+ gfx::PointF(1.23f, 4.56f)));
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetAnchorPointZ(0.7f));
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetBackgroundColor(SK_ColorLTGRAY));
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetMasksToBounds(true));
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetOpacity(0.5f));
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetContentsOpaque(true));
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetPosition(gfx::PointF(4.f, 9.f)));
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetSublayerTransform(
+ gfx::Transform(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)));
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetScrollable(true));
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetScrollOffset(
+ gfx::Vector2d(10, 10)));
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetShouldScrollOnMainThread(true));
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetNonFastScrollableRegion(
+ Region(gfx::Rect(1, 1, 2, 2))));
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetHaveWheelEventHandlers(true));
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetTransform(
+ gfx::Transform(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)));
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetDoubleSided(false));
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetDebugName("Test Layer"));
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetDrawCheckerboardForMissingTiles(
+ !test_layer->DrawCheckerboardForMissingTiles()));
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetForceRenderSurface(true));
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetHideLayerAndSubtree(true));
+
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, test_layer->SetMaskLayer(
+ dummy_layer1.get()));
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, test_layer->SetReplicaLayer(
+ dummy_layer2.get()));
+
+ // The above tests should not have caused a change to the needs_display flag.
+ EXPECT_FALSE(test_layer->NeedsDisplayForTesting());
+
+ // As layers are removed from the tree, they will cause a tree sync.
+ EXPECT_CALL(*layer_tree_host_, SetNeedsFullTreeSync()).Times((AnyNumber()));
+}
+
+TEST_F(LayerTest, PushPropertiesAccumulatesUpdateRect) {
+ scoped_refptr<Layer> test_layer = Layer::Create();
+ scoped_ptr<LayerImpl> impl_layer =
+ LayerImpl::Create(host_impl_.active_tree(), 1);
+
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(1,
+ layer_tree_host_->SetRootLayer(test_layer));
+
+ test_layer->SetNeedsDisplayRect(gfx::RectF(0.f, 0.f, 5.f, 5.f));
+ test_layer->PushPropertiesTo(impl_layer.get());
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(0.f, 0.f, 5.f, 5.f),
+ impl_layer->update_rect());
+
+ // The LayerImpl's update_rect() should be accumulated here, since we did not
+ // do anything to clear it.
+ test_layer->SetNeedsDisplayRect(gfx::RectF(10.f, 10.f, 5.f, 5.f));
+ test_layer->PushPropertiesTo(impl_layer.get());
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(0.f, 0.f, 15.f, 15.f),
+ impl_layer->update_rect());
+
+ // If we do clear the LayerImpl side, then the next update_rect() should be
+ // fresh without accumulation.
+ impl_layer->ResetAllChangeTrackingForSubtree();
+ test_layer->SetNeedsDisplayRect(gfx::RectF(10.f, 10.f, 5.f, 5.f));
+ test_layer->PushPropertiesTo(impl_layer.get());
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(10.f, 10.f, 5.f, 5.f),
+ impl_layer->update_rect());
+}
+
+TEST_F(LayerTest, PushPropertiesCausesSurfacePropertyChangedForTransform) {
+ scoped_refptr<Layer> test_layer = Layer::Create();
+ scoped_ptr<LayerImpl> impl_layer =
+ LayerImpl::Create(host_impl_.active_tree(), 1);
+
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(1,
+ layer_tree_host_->SetRootLayer(test_layer));
+
+ gfx::Transform transform;
+ transform.Rotate(45.0);
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetTransform(transform));
+
+ EXPECT_FALSE(impl_layer->LayerSurfacePropertyChanged());
+
+ test_layer->PushPropertiesTo(impl_layer.get());
+
+ EXPECT_TRUE(impl_layer->LayerSurfacePropertyChanged());
+}
+
+TEST_F(LayerTest, PushPropertiesCausesSurfacePropertyChangedForOpacity) {
+ scoped_refptr<Layer> test_layer = Layer::Create();
+ scoped_ptr<LayerImpl> impl_layer =
+ LayerImpl::Create(host_impl_.active_tree(), 1);
+
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(1,
+ layer_tree_host_->SetRootLayer(test_layer));
+
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetOpacity(0.5f));
+
+ EXPECT_FALSE(impl_layer->LayerSurfacePropertyChanged());
+
+ test_layer->PushPropertiesTo(impl_layer.get());
+
+ EXPECT_TRUE(impl_layer->LayerSurfacePropertyChanged());
+}
+
+TEST_F(LayerTest,
+ PushPropsDoesntCauseSurfacePropertyChangedDuringImplOnlyTransformAnim) {
+ scoped_refptr<Layer> test_layer = Layer::Create();
+ scoped_ptr<LayerImpl> impl_layer =
+ LayerImpl::Create(host_impl_.active_tree(), 1);
+
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(1,
+ layer_tree_host_->SetRootLayer(test_layer));
+
+ scoped_ptr<AnimationRegistrar> registrar = AnimationRegistrar::Create();
+ impl_layer->layer_animation_controller()->SetAnimationRegistrar(
+ registrar.get());
+
+ AddAnimatedTransformToController(impl_layer->layer_animation_controller(),
+ 1.0,
+ 0,
+ 100);
+
+ gfx::Transform transform;
+ transform.Rotate(45.0);
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetTransform(transform));
+
+ EXPECT_FALSE(impl_layer->LayerSurfacePropertyChanged());
+ test_layer->PushPropertiesTo(impl_layer.get());
+ EXPECT_TRUE(impl_layer->LayerSurfacePropertyChanged());
+
+ impl_layer->ResetAllChangeTrackingForSubtree();
+ AddAnimatedTransformToController(impl_layer->layer_animation_controller(),
+ 1.0,
+ 0,
+ 100);
+ impl_layer->layer_animation_controller()->GetAnimation(Animation::Transform)->
+ set_is_impl_only(true);
+ transform.Rotate(45.0);
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetTransform(transform));
+
+ EXPECT_FALSE(impl_layer->LayerSurfacePropertyChanged());
+ test_layer->PushPropertiesTo(impl_layer.get());
+ EXPECT_FALSE(impl_layer->LayerSurfacePropertyChanged());
+}
+
+TEST_F(LayerTest,
+ PushPropsDoesntCauseSurfacePropertyChangedDuringImplOnlyOpacityAnim) {
+ scoped_refptr<Layer> test_layer = Layer::Create();
+ scoped_ptr<LayerImpl> impl_layer =
+ LayerImpl::Create(host_impl_.active_tree(), 1);
+
+ EXPECT_SET_NEEDS_FULL_TREE_SYNC(1,
+ layer_tree_host_->SetRootLayer(test_layer));
+
+ scoped_ptr<AnimationRegistrar> registrar = AnimationRegistrar::Create();
+ impl_layer->layer_animation_controller()->SetAnimationRegistrar(
+ registrar.get());
+
+ AddOpacityTransitionToController(impl_layer->layer_animation_controller(),
+ 1.0,
+ 0.3f,
+ 0.7f,
+ false);
+
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetOpacity(0.5f));
+
+ EXPECT_FALSE(impl_layer->LayerSurfacePropertyChanged());
+ test_layer->PushPropertiesTo(impl_layer.get());
+ EXPECT_TRUE(impl_layer->LayerSurfacePropertyChanged());
+
+ impl_layer->ResetAllChangeTrackingForSubtree();
+ AddOpacityTransitionToController(impl_layer->layer_animation_controller(),
+ 1.0,
+ 0.3f,
+ 0.7f,
+ false);
+ impl_layer->layer_animation_controller()->GetAnimation(Animation::Opacity)->
+ set_is_impl_only(true);
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetOpacity(0.75f));
+
+ EXPECT_FALSE(impl_layer->LayerSurfacePropertyChanged());
+ test_layer->PushPropertiesTo(impl_layer.get());
+ EXPECT_FALSE(impl_layer->LayerSurfacePropertyChanged());
+}
+
+
+TEST_F(LayerTest, MaskAndReplicaHasParent) {
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<Layer> child = Layer::Create();
+ scoped_refptr<Layer> mask = Layer::Create();
+ scoped_refptr<Layer> replica = Layer::Create();
+ scoped_refptr<Layer> replica_mask = Layer::Create();
+ scoped_refptr<Layer> mask_replacement = Layer::Create();
+ scoped_refptr<Layer> replica_replacement = Layer::Create();
+ scoped_refptr<Layer> replica_mask_replacement = Layer::Create();
+
+ parent->AddChild(child);
+ child->SetMaskLayer(mask.get());
+ child->SetReplicaLayer(replica.get());
+ replica->SetMaskLayer(replica_mask.get());
+
+ EXPECT_EQ(parent, child->parent());
+ EXPECT_EQ(child, mask->parent());
+ EXPECT_EQ(child, replica->parent());
+ EXPECT_EQ(replica, replica_mask->parent());
+
+ replica->SetMaskLayer(replica_mask_replacement.get());
+ EXPECT_EQ(NULL, replica_mask->parent());
+ EXPECT_EQ(replica, replica_mask_replacement->parent());
+
+ child->SetMaskLayer(mask_replacement.get());
+ EXPECT_EQ(NULL, mask->parent());
+ EXPECT_EQ(child, mask_replacement->parent());
+
+ child->SetReplicaLayer(replica_replacement.get());
+ EXPECT_EQ(NULL, replica->parent());
+ EXPECT_EQ(child, replica_replacement->parent());
+
+ EXPECT_EQ(replica, replica->mask_layer()->parent());
+}
+
+class LayerTreeHostFactory {
+ public:
+ LayerTreeHostFactory()
+ : client_(FakeLayerTreeHostClient::DIRECT_3D) {}
+
+ scoped_ptr<LayerTreeHost> Create() {
+ return LayerTreeHost::Create(&client_, LayerTreeSettings(), NULL).Pass();
+ }
+
+ scoped_ptr<LayerTreeHost> Create(LayerTreeSettings settings) {
+ return LayerTreeHost::Create(&client_, settings, NULL).Pass();
+ }
+
+ private:
+ FakeLayerTreeHostClient client_;
+};
+
+void AssertLayerTreeHostMatchesForSubtree(Layer* layer, LayerTreeHost* host) {
+ EXPECT_EQ(host, layer->layer_tree_host());
+
+ for (size_t i = 0; i < layer->children().size(); ++i)
+ AssertLayerTreeHostMatchesForSubtree(layer->children()[i].get(), host);
+
+ if (layer->mask_layer())
+ AssertLayerTreeHostMatchesForSubtree(layer->mask_layer(), host);
+
+ if (layer->replica_layer())
+ AssertLayerTreeHostMatchesForSubtree(layer->replica_layer(), host);
+}
+
+TEST(LayerLayerTreeHostTest, EnteringTree) {
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<Layer> child = Layer::Create();
+ scoped_refptr<Layer> mask = Layer::Create();
+ scoped_refptr<Layer> replica = Layer::Create();
+ scoped_refptr<Layer> replica_mask = Layer::Create();
+
+ // Set up a detached tree of layers. The host pointer should be nil for these
+ // layers.
+ parent->AddChild(child);
+ child->SetMaskLayer(mask.get());
+ child->SetReplicaLayer(replica.get());
+ replica->SetMaskLayer(replica_mask.get());
+
+ AssertLayerTreeHostMatchesForSubtree(parent.get(), NULL);
+
+ LayerTreeHostFactory factory;
+ scoped_ptr<LayerTreeHost> layer_tree_host = factory.Create();
+ // Setting the root layer should set the host pointer for all layers in the
+ // tree.
+ layer_tree_host->SetRootLayer(parent.get());
+
+ AssertLayerTreeHostMatchesForSubtree(parent.get(), layer_tree_host.get());
+
+ // Clearing the root layer should also clear out the host pointers for all
+ // layers in the tree.
+ layer_tree_host->SetRootLayer(NULL);
+
+ AssertLayerTreeHostMatchesForSubtree(parent.get(), NULL);
+}
+
+TEST(LayerLayerTreeHostTest, AddingLayerSubtree) {
+ scoped_refptr<Layer> parent = Layer::Create();
+ LayerTreeHostFactory factory;
+ scoped_ptr<LayerTreeHost> layer_tree_host = factory.Create();
+
+ layer_tree_host->SetRootLayer(parent.get());
+
+ EXPECT_EQ(parent->layer_tree_host(), layer_tree_host.get());
+
+ // Adding a subtree to a layer already associated with a host should set the
+ // host pointer on all layers in that subtree.
+ scoped_refptr<Layer> child = Layer::Create();
+ scoped_refptr<Layer> grand_child = Layer::Create();
+ child->AddChild(grand_child);
+
+ // Masks, replicas, and replica masks should pick up the new host too.
+ scoped_refptr<Layer> child_mask = Layer::Create();
+ child->SetMaskLayer(child_mask.get());
+ scoped_refptr<Layer> child_replica = Layer::Create();
+ child->SetReplicaLayer(child_replica.get());
+ scoped_refptr<Layer> child_replica_mask = Layer::Create();
+ child_replica->SetMaskLayer(child_replica_mask.get());
+
+ parent->AddChild(child);
+ AssertLayerTreeHostMatchesForSubtree(parent.get(), layer_tree_host.get());
+
+ layer_tree_host->SetRootLayer(NULL);
+}
+
+TEST(LayerLayerTreeHostTest, ChangeHost) {
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<Layer> child = Layer::Create();
+ scoped_refptr<Layer> mask = Layer::Create();
+ scoped_refptr<Layer> replica = Layer::Create();
+ scoped_refptr<Layer> replica_mask = Layer::Create();
+
+ // Same setup as the previous test.
+ parent->AddChild(child);
+ child->SetMaskLayer(mask.get());
+ child->SetReplicaLayer(replica.get());
+ replica->SetMaskLayer(replica_mask.get());
+
+ LayerTreeHostFactory factory;
+ scoped_ptr<LayerTreeHost> first_layer_tree_host = factory.Create();
+ first_layer_tree_host->SetRootLayer(parent.get());
+
+ AssertLayerTreeHostMatchesForSubtree(parent.get(),
+ first_layer_tree_host.get());
+
+ // Now re-root the tree to a new host (simulating what we do on a context lost
+ // event). This should update the host pointers for all layers in the tree.
+ scoped_ptr<LayerTreeHost> second_layer_tree_host = factory.Create();
+ second_layer_tree_host->SetRootLayer(parent.get());
+
+ AssertLayerTreeHostMatchesForSubtree(parent.get(),
+ second_layer_tree_host.get());
+
+ second_layer_tree_host->SetRootLayer(NULL);
+}
+
+TEST(LayerLayerTreeHostTest, ChangeHostInSubtree) {
+ scoped_refptr<Layer> first_parent = Layer::Create();
+ scoped_refptr<Layer> first_child = Layer::Create();
+ scoped_refptr<Layer> second_parent = Layer::Create();
+ scoped_refptr<Layer> second_child = Layer::Create();
+ scoped_refptr<Layer> second_grand_child = Layer::Create();
+
+ // First put all children under the first parent and set the first host.
+ first_parent->AddChild(first_child);
+ second_child->AddChild(second_grand_child);
+ first_parent->AddChild(second_child);
+
+ LayerTreeHostFactory factory;
+ scoped_ptr<LayerTreeHost> first_layer_tree_host = factory.Create();
+ first_layer_tree_host->SetRootLayer(first_parent.get());
+
+ AssertLayerTreeHostMatchesForSubtree(first_parent.get(),
+ first_layer_tree_host.get());
+
+ // Now reparent the subtree starting at second_child to a layer in a different
+ // tree.
+ scoped_ptr<LayerTreeHost> second_layer_tree_host = factory.Create();
+ second_layer_tree_host->SetRootLayer(second_parent.get());
+
+ second_parent->AddChild(second_child);
+
+ // The moved layer and its children should point to the new host.
+ EXPECT_EQ(second_layer_tree_host.get(), second_child->layer_tree_host());
+ EXPECT_EQ(second_layer_tree_host.get(),
+ second_grand_child->layer_tree_host());
+
+ // Test over, cleanup time.
+ first_layer_tree_host->SetRootLayer(NULL);
+ second_layer_tree_host->SetRootLayer(NULL);
+}
+
+TEST(LayerLayerTreeHostTest, ReplaceMaskAndReplicaLayer) {
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<Layer> mask = Layer::Create();
+ scoped_refptr<Layer> replica = Layer::Create();
+ scoped_refptr<Layer> mask_child = Layer::Create();
+ scoped_refptr<Layer> replica_child = Layer::Create();
+ scoped_refptr<Layer> mask_replacement = Layer::Create();
+ scoped_refptr<Layer> replica_replacement = Layer::Create();
+
+ parent->SetMaskLayer(mask.get());
+ parent->SetReplicaLayer(replica.get());
+ mask->AddChild(mask_child);
+ replica->AddChild(replica_child);
+
+ LayerTreeHostFactory factory;
+ scoped_ptr<LayerTreeHost> layer_tree_host = factory.Create();
+ layer_tree_host->SetRootLayer(parent.get());
+
+ AssertLayerTreeHostMatchesForSubtree(parent.get(), layer_tree_host.get());
+
+ // Replacing the mask should clear out the old mask's subtree's host pointers.
+ parent->SetMaskLayer(mask_replacement.get());
+ EXPECT_EQ(NULL, mask->layer_tree_host());
+ EXPECT_EQ(NULL, mask_child->layer_tree_host());
+
+ // Same for replacing a replica layer.
+ parent->SetReplicaLayer(replica_replacement.get());
+ EXPECT_EQ(NULL, replica->layer_tree_host());
+ EXPECT_EQ(NULL, replica_child->layer_tree_host());
+
+ // Test over, cleanup time.
+ layer_tree_host->SetRootLayer(NULL);
+}
+
+TEST(LayerLayerTreeHostTest, DestroyHostWithNonNullRootLayer) {
+ scoped_refptr<Layer> root = Layer::Create();
+ scoped_refptr<Layer> child = Layer::Create();
+ root->AddChild(child);
+ LayerTreeHostFactory factory;
+ scoped_ptr<LayerTreeHost> layer_tree_host = factory.Create();
+ layer_tree_host->SetRootLayer(root);
+}
+
+static bool AddTestAnimation(Layer* layer) {
+ scoped_ptr<KeyframedFloatAnimationCurve> curve =
+ KeyframedFloatAnimationCurve::Create();
+ curve->AddKeyframe(FloatKeyframe::Create(0.0,
+ 0.3f,
+ scoped_ptr<TimingFunction>()));
+ curve->AddKeyframe(FloatKeyframe::Create(1.0,
+ 0.7f,
+ scoped_ptr<TimingFunction>()));
+ scoped_ptr<Animation> animation =
+ Animation::Create(curve.PassAs<AnimationCurve>(),
+ 0,
+ 0,
+ Animation::Opacity);
+
+ return layer->AddAnimation(animation.Pass());
+}
+
+TEST(LayerLayerTreeHostTest, ShouldNotAddAnimationWithoutAnimationRegistrar) {
+ scoped_refptr<Layer> layer = Layer::Create();
+
+ // Case 1: without a LayerTreeHost and without an AnimationRegistrar, the
+ // animation should not be accepted.
+ EXPECT_FALSE(AddTestAnimation(layer.get()));
+
+ scoped_ptr<AnimationRegistrar> registrar = AnimationRegistrar::Create();
+ layer->layer_animation_controller()->SetAnimationRegistrar(registrar.get());
+
+ // Case 2: with an AnimationRegistrar, the animation should be accepted.
+ EXPECT_TRUE(AddTestAnimation(layer.get()));
+
+ LayerTreeSettings settings;
+ settings.accelerated_animation_enabled = false;
+ LayerTreeHostFactory factory;
+ scoped_ptr<LayerTreeHost> layer_tree_host = factory.Create(settings);
+ layer_tree_host->SetRootLayer(layer);
+ AssertLayerTreeHostMatchesForSubtree(layer.get(), layer_tree_host.get());
+
+ // Case 3: with a LayerTreeHost where accelerated animation is disabled, the
+ // animation should be rejected.
+ EXPECT_FALSE(AddTestAnimation(layer.get()));
+}
+
+TEST_F(LayerTest, SafeOpaqueBackgroundColor) {
+ LayerTreeHostFactory factory;
+ scoped_ptr<LayerTreeHost> layer_tree_host = factory.Create();
+
+ scoped_refptr<Layer> layer = Layer::Create();
+ layer_tree_host->SetRootLayer(layer);
+
+ for (int contents_opaque = 0; contents_opaque < 2; ++contents_opaque) {
+ for (int layer_opaque = 0; layer_opaque < 2; ++layer_opaque) {
+ for (int host_opaque = 0; host_opaque < 2; ++host_opaque) {
+ layer->SetContentsOpaque(!!contents_opaque);
+ layer->SetBackgroundColor(layer_opaque ? SK_ColorRED
+ : SK_ColorTRANSPARENT);
+ layer_tree_host->set_background_color(
+ host_opaque ? SK_ColorRED : SK_ColorTRANSPARENT);
+
+ SkColor safe_color = layer->SafeOpaqueBackgroundColor();
+ if (contents_opaque) {
+ EXPECT_EQ(SkColorGetA(safe_color), 255u)
+ << "Flags: " << contents_opaque << ", " << layer_opaque << ", "
+ << host_opaque << "\n";
+ } else {
+ EXPECT_NE(SkColorGetA(safe_color), 255u)
+ << "Flags: " << contents_opaque << ", " << layer_opaque << ", "
+ << host_opaque << "\n";
+ }
+ }
+ }
+ }
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/layers/nine_patch_layer.cc b/chromium/cc/layers/nine_patch_layer.cc
new file mode 100644
index 00000000000..e32f6e0b125
--- /dev/null
+++ b/chromium/cc/layers/nine_patch_layer.cc
@@ -0,0 +1,116 @@
+// 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 "cc/layers/nine_patch_layer.h"
+
+#include "cc/layers/nine_patch_layer_impl.h"
+#include "cc/resources/prioritized_resource.h"
+#include "cc/resources/resource_update.h"
+#include "cc/resources/resource_update_queue.h"
+#include "cc/trees/layer_tree_host.h"
+
+namespace cc {
+
+scoped_refptr<NinePatchLayer> NinePatchLayer::Create() {
+ return make_scoped_refptr(new NinePatchLayer());
+}
+
+NinePatchLayer::NinePatchLayer()
+ : bitmap_dirty_(false) {}
+
+NinePatchLayer::~NinePatchLayer() {}
+
+scoped_ptr<LayerImpl> NinePatchLayer::CreateLayerImpl(
+ LayerTreeImpl* tree_impl) {
+ return NinePatchLayerImpl::Create(tree_impl, id()).PassAs<LayerImpl>();
+}
+
+void NinePatchLayer::SetTexturePriorities(
+ const PriorityCalculator& priority_calc) {
+ if (resource_ && !resource_->texture()->resource_manager()) {
+ // Release the resource here, as it is no longer tied to a resource manager.
+ resource_.reset();
+ if (!bitmap_.isNull())
+ CreateResource();
+ } else if (bitmap_dirty_ && DrawsContent()) {
+ CreateResource();
+ }
+
+ if (resource_) {
+ resource_->texture()->set_request_priority(
+ PriorityCalculator::UIPriority(true));
+ GLenum texture_format =
+ layer_tree_host()->GetRendererCapabilities().best_texture_format;
+ resource_->texture()->SetDimensions(
+ gfx::Size(bitmap_.width(), bitmap_.height()), texture_format);
+ }
+}
+
+void NinePatchLayer::SetBitmap(const SkBitmap& bitmap, gfx::Rect aperture) {
+ bitmap_ = bitmap;
+ image_aperture_ = aperture;
+ bitmap_dirty_ = true;
+ SetNeedsDisplay();
+}
+
+bool NinePatchLayer::Update(ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion) {
+ bool updated = Layer::Update(queue, occlusion);
+
+ CreateUpdaterIfNeeded();
+
+ if (resource_ &&
+ (bitmap_dirty_ || resource_->texture()->resource_id() == 0)) {
+ gfx::Rect content_rect(0, 0, bitmap_.width(), bitmap_.height());
+ ResourceUpdate upload = ResourceUpdate::Create(resource_->texture(),
+ &bitmap_,
+ content_rect,
+ content_rect,
+ gfx::Vector2d());
+ queue->AppendFullUpload(upload);
+ bitmap_dirty_ = false;
+ updated = true;
+ }
+ return updated;
+}
+
+void NinePatchLayer::CreateUpdaterIfNeeded() {
+ if (updater_.get())
+ return;
+
+ updater_ = ImageLayerUpdater::Create();
+}
+
+void NinePatchLayer::CreateResource() {
+ DCHECK(!bitmap_.isNull());
+ CreateUpdaterIfNeeded();
+ updater_->SetBitmap(bitmap_);
+
+ if (!resource_) {
+ resource_ = updater_->CreateResource(
+ layer_tree_host()->contents_texture_manager());
+ }
+}
+
+bool NinePatchLayer::DrawsContent() const {
+ bool draws = !bitmap_.isNull() &&
+ Layer::DrawsContent() &&
+ bitmap_.width() &&
+ bitmap_.height();
+ return draws;
+}
+
+void NinePatchLayer::PushPropertiesTo(LayerImpl* layer) {
+ Layer::PushPropertiesTo(layer);
+ NinePatchLayerImpl* layer_impl = static_cast<NinePatchLayerImpl*>(layer);
+
+ if (resource_) {
+ DCHECK(!bitmap_.isNull());
+ layer_impl->SetResourceId(resource_->texture()->resource_id());
+ layer_impl->SetLayout(
+ gfx::Size(bitmap_.width(), bitmap_.height()), image_aperture_);
+ }
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/nine_patch_layer.h b/chromium/cc/layers/nine_patch_layer.h
new file mode 100644
index 00000000000..35d08812cff
--- /dev/null
+++ b/chromium/cc/layers/nine_patch_layer.h
@@ -0,0 +1,62 @@
+// 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.
+
+#ifndef CC_LAYERS_NINE_PATCH_LAYER_H_
+#define CC_LAYERS_NINE_PATCH_LAYER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/layers/layer.h"
+#include "cc/resources/image_layer_updater.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/rect.h"
+
+namespace cc {
+
+class ResourceUpdateQueue;
+
+class CC_EXPORT NinePatchLayer : public Layer {
+ public:
+ static scoped_refptr<NinePatchLayer> Create();
+
+ virtual bool DrawsContent() const OVERRIDE;
+ virtual void SetTexturePriorities(const PriorityCalculator& priority_calc)
+ OVERRIDE;
+ virtual bool Update(ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion) OVERRIDE;
+ virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE;
+
+ // aperture is in the pixel space of the bitmap resource and refers to
+ // the center patch of the ninepatch (which is unused in this
+ // implementation). We split off eight rects surrounding it and stick them
+ // on the edges of the layer. The corners are unscaled, the top and bottom
+ // rects are x-stretched to fit, and the left and right rects are
+ // y-stretched to fit.
+ void SetBitmap(const SkBitmap& bitmap, gfx::Rect aperture);
+
+ private:
+ NinePatchLayer();
+ virtual ~NinePatchLayer();
+ virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl)
+ OVERRIDE;
+
+ void CreateUpdaterIfNeeded();
+ void CreateResource();
+
+ scoped_refptr<ImageLayerUpdater> updater_;
+ scoped_ptr<LayerUpdater::Resource> resource_;
+
+ SkBitmap bitmap_;
+ bool bitmap_dirty_;
+
+ // The transparent center region that shows the parent layer's contents in
+ // image space.
+ gfx::Rect image_aperture_;
+
+ DISALLOW_COPY_AND_ASSIGN(NinePatchLayer);
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_NINE_PATCH_LAYER_H_
diff --git a/chromium/cc/layers/nine_patch_layer_impl.cc b/chromium/cc/layers/nine_patch_layer_impl.cc
new file mode 100644
index 00000000000..103f7656eb8
--- /dev/null
+++ b/chromium/cc/layers/nine_patch_layer_impl.cc
@@ -0,0 +1,305 @@
+// 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 "cc/layers/nine_patch_layer_impl.h"
+
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "cc/layers/quad_sink.h"
+#include "cc/quads/texture_draw_quad.h"
+#include "ui/gfx/rect_f.h"
+
+namespace cc {
+
+NinePatchLayerImpl::NinePatchLayerImpl(LayerTreeImpl* tree_impl, int id)
+ : LayerImpl(tree_impl, id),
+ resource_id_(0) {}
+
+NinePatchLayerImpl::~NinePatchLayerImpl() {}
+
+ResourceProvider::ResourceId NinePatchLayerImpl::ContentsResourceId() const {
+ return 0;
+}
+
+scoped_ptr<LayerImpl> NinePatchLayerImpl::CreateLayerImpl(
+ LayerTreeImpl* tree_impl) {
+ return NinePatchLayerImpl::Create(tree_impl, id()).PassAs<LayerImpl>();
+}
+
+void NinePatchLayerImpl::PushPropertiesTo(LayerImpl* layer) {
+ LayerImpl::PushPropertiesTo(layer);
+ NinePatchLayerImpl* layer_impl = static_cast<NinePatchLayerImpl*>(layer);
+
+ if (!resource_id_)
+ return;
+
+ layer_impl->SetResourceId(resource_id_);
+ layer_impl->SetLayout(image_bounds_, image_aperture_);
+}
+
+static gfx::RectF NormalizedRect(float x,
+ float y,
+ float width,
+ float height,
+ float total_width,
+ float total_height) {
+ return gfx::RectF(x / total_width,
+ y / total_height,
+ width / total_width,
+ height / total_height);
+}
+
+void NinePatchLayerImpl::SetLayout(gfx::Size image_bounds, gfx::Rect aperture) {
+ image_bounds_ = image_bounds;
+ image_aperture_ = aperture;
+}
+
+bool NinePatchLayerImpl::WillDraw(DrawMode draw_mode,
+ ResourceProvider* resource_provider) {
+ if (!resource_id_ || draw_mode == DRAW_MODE_RESOURCELESS_SOFTWARE)
+ return false;
+ return LayerImpl::WillDraw(draw_mode, resource_provider);
+}
+
+void NinePatchLayerImpl::AppendQuads(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data) {
+ DCHECK(resource_id_);
+
+ SharedQuadState* shared_quad_state =
+ quad_sink->UseSharedQuadState(CreateSharedQuadState());
+ AppendDebugBorderQuad(quad_sink, shared_quad_state, append_quads_data);
+
+ static const bool flipped = false;
+ static const bool premultiplied_alpha = true;
+
+ DCHECK(!bounds().IsEmpty());
+
+ // NinePatch border widths in bitmap pixel space
+ int left_width = image_aperture_.x();
+ int top_height = image_aperture_.y();
+ int right_width = image_bounds_.width() - image_aperture_.right();
+ int bottom_height = image_bounds_.height() - image_aperture_.bottom();
+
+ // If layer can't fit the corners, clip to show the outer edges of the
+ // image.
+ int corner_total_width = left_width + right_width;
+ int middle_width = bounds().width() - corner_total_width;
+ if (middle_width < 0) {
+ float left_width_proportion =
+ static_cast<float>(left_width) / corner_total_width;
+ int left_width_crop = middle_width * left_width_proportion;
+ left_width += left_width_crop;
+ right_width = bounds().width() - left_width;
+ middle_width = 0;
+ }
+ int corner_total_height = top_height + bottom_height;
+ int middle_height = bounds().height() - corner_total_height;
+ if (middle_height < 0) {
+ float top_height_proportion =
+ static_cast<float>(top_height) / corner_total_height;
+ int top_height_crop = middle_height * top_height_proportion;
+ top_height += top_height_crop;
+ bottom_height = bounds().height() - top_height;
+ middle_height = 0;
+ }
+
+ // Patch positions in layer space
+ gfx::Rect top_left(0, 0, left_width, top_height);
+ gfx::Rect top_right(
+ bounds().width() - right_width, 0, right_width, top_height);
+ gfx::Rect bottom_left(
+ 0, bounds().height() - bottom_height, left_width, bottom_height);
+ gfx::Rect bottom_right(
+ top_right.x(), bottom_left.y(), right_width, bottom_height);
+ gfx::Rect top(top_left.right(), 0, middle_width, top_height);
+ gfx::Rect left(0, top_left.bottom(), left_width, middle_height);
+ gfx::Rect right(top_right.x(),
+ top_right.bottom(),
+ right_width,
+ left.height());
+ gfx::Rect bottom(top.x(), bottom_left.y(), top.width(), bottom_height);
+
+ float img_width = image_bounds_.width();
+ float img_height = image_bounds_.height();
+
+ // Patch positions in bitmap UV space (from zero to one)
+ gfx::RectF uv_top_left = NormalizedRect(0,
+ 0,
+ left_width,
+ top_height,
+ img_width,
+ img_height);
+ gfx::RectF uv_top_right = NormalizedRect(img_width - right_width,
+ 0,
+ right_width,
+ top_height,
+ img_width,
+ img_height);
+ gfx::RectF uv_bottom_left = NormalizedRect(0,
+ img_height - bottom_height,
+ left_width,
+ bottom_height,
+ img_width,
+ img_height);
+ gfx::RectF uv_bottom_right = NormalizedRect(img_width - right_width,
+ img_height - bottom_height,
+ right_width,
+ bottom_height,
+ img_width,
+ img_height);
+ gfx::RectF uv_top(uv_top_left.right(),
+ 0,
+ (img_width - left_width - right_width) / img_width,
+ (top_height) / img_height);
+ gfx::RectF uv_left(0,
+ uv_top_left.bottom(),
+ left_width / img_width,
+ (img_height - top_height - bottom_height) / img_height);
+ gfx::RectF uv_right(uv_top_right.x(),
+ uv_top_right.bottom(),
+ right_width / img_width,
+ uv_left.height());
+ gfx::RectF uv_bottom(uv_top.x(),
+ uv_bottom_left.y(),
+ uv_top.width(),
+ bottom_height / img_height);
+
+ // Nothing is opaque here.
+ // TODO(danakj): Should we look at the SkBitmaps to determine opaqueness?
+ gfx::Rect opaque_rect;
+ const float vertex_opacity[] = {1.0f, 1.0f, 1.0f, 1.0f};
+ scoped_ptr<TextureDrawQuad> quad;
+
+ quad = TextureDrawQuad::Create();
+ quad->SetNew(shared_quad_state,
+ top_left,
+ opaque_rect,
+ resource_id_,
+ premultiplied_alpha,
+ uv_top_left.origin(),
+ uv_top_left.bottom_right(),
+ SK_ColorTRANSPARENT,
+ vertex_opacity,
+ flipped);
+ quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data);
+
+ quad = TextureDrawQuad::Create();
+ quad->SetNew(shared_quad_state,
+ top_right,
+ opaque_rect,
+ resource_id_,
+ premultiplied_alpha,
+ uv_top_right.origin(),
+ uv_top_right.bottom_right(),
+ SK_ColorTRANSPARENT,
+ vertex_opacity,
+ flipped);
+ quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data);
+
+ quad = TextureDrawQuad::Create();
+ quad->SetNew(shared_quad_state,
+ bottom_left,
+ opaque_rect,
+ resource_id_,
+ premultiplied_alpha,
+ uv_bottom_left.origin(),
+ uv_bottom_left.bottom_right(),
+ SK_ColorTRANSPARENT,
+ vertex_opacity,
+ flipped);
+ quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data);
+
+ quad = TextureDrawQuad::Create();
+ quad->SetNew(shared_quad_state,
+ bottom_right,
+ opaque_rect,
+ resource_id_,
+ premultiplied_alpha,
+ uv_bottom_right.origin(),
+ uv_bottom_right.bottom_right(),
+ SK_ColorTRANSPARENT,
+ vertex_opacity,
+ flipped);
+ quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data);
+
+ quad = TextureDrawQuad::Create();
+ quad->SetNew(shared_quad_state,
+ top,
+ opaque_rect,
+ resource_id_,
+ premultiplied_alpha,
+ uv_top.origin(),
+ uv_top.bottom_right(),
+ SK_ColorTRANSPARENT,
+ vertex_opacity,
+ flipped);
+ quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data);
+
+ quad = TextureDrawQuad::Create();
+ quad->SetNew(shared_quad_state,
+ left,
+ opaque_rect,
+ resource_id_,
+ premultiplied_alpha,
+ uv_left.origin(),
+ uv_left.bottom_right(),
+ SK_ColorTRANSPARENT,
+ vertex_opacity,
+ flipped);
+ quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data);
+
+ quad = TextureDrawQuad::Create();
+ quad->SetNew(shared_quad_state,
+ right,
+ opaque_rect,
+ resource_id_,
+ premultiplied_alpha,
+ uv_right.origin(),
+ uv_right.bottom_right(),
+ SK_ColorTRANSPARENT,
+ vertex_opacity,
+ flipped);
+ quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data);
+
+ quad = TextureDrawQuad::Create();
+ quad->SetNew(shared_quad_state,
+ bottom,
+ opaque_rect,
+ resource_id_,
+ premultiplied_alpha,
+ uv_bottom.origin(),
+ uv_bottom.bottom_right(),
+ SK_ColorTRANSPARENT,
+ vertex_opacity,
+ flipped);
+ quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data);
+}
+
+void NinePatchLayerImpl::DidLoseOutputSurface() {
+ resource_id_ = 0;
+}
+
+const char* NinePatchLayerImpl::LayerTypeAsString() const {
+ return "cc::NinePatchLayerImpl";
+}
+
+base::DictionaryValue* NinePatchLayerImpl::LayerTreeAsJson() const {
+ base::DictionaryValue* result = LayerImpl::LayerTreeAsJson();
+
+ base::ListValue* list = new base::ListValue;
+ list->AppendInteger(image_aperture_.origin().x());
+ list->AppendInteger(image_aperture_.origin().y());
+ list->AppendInteger(image_aperture_.size().width());
+ list->AppendInteger(image_aperture_.size().height());
+ result->Set("ImageAperture", list);
+
+ list = new base::ListValue;
+ list->AppendInteger(image_bounds_.width());
+ list->AppendInteger(image_bounds_.height());
+ result->Set("ImageBounds", list);
+
+ return result;
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/nine_patch_layer_impl.h b/chromium/cc/layers/nine_patch_layer_impl.h
new file mode 100644
index 00000000000..bc442d43f33
--- /dev/null
+++ b/chromium/cc/layers/nine_patch_layer_impl.h
@@ -0,0 +1,66 @@
+// 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.
+
+#ifndef CC_LAYERS_NINE_PATCH_LAYER_IMPL_H_
+#define CC_LAYERS_NINE_PATCH_LAYER_IMPL_H_
+
+#include <string>
+
+#include "cc/base/cc_export.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/resources/resource_provider.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace cc {
+
+class CC_EXPORT NinePatchLayerImpl : public LayerImpl {
+ public:
+ static scoped_ptr<NinePatchLayerImpl> Create(LayerTreeImpl* tree_impl,
+ int id) {
+ return make_scoped_ptr(new NinePatchLayerImpl(tree_impl, id));
+ }
+ virtual ~NinePatchLayerImpl();
+
+ void SetResourceId(unsigned id) { resource_id_ = id; }
+ void SetLayout(gfx::Size image_bounds, gfx::Rect aperture);
+
+ virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl)
+ OVERRIDE;
+ virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE;
+
+ virtual bool WillDraw(DrawMode draw_mode,
+ ResourceProvider* resource_provider) OVERRIDE;
+ virtual void AppendQuads(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data) OVERRIDE;
+ virtual ResourceProvider::ResourceId ContentsResourceId() const OVERRIDE;
+ virtual void DidLoseOutputSurface() OVERRIDE;
+
+ virtual base::DictionaryValue* LayerTreeAsJson() const OVERRIDE;
+
+ protected:
+ NinePatchLayerImpl(LayerTreeImpl* tree_impl, int id);
+
+ private:
+ virtual const char* LayerTypeAsString() const OVERRIDE;
+
+ // The size of the NinePatch bitmap in pixels.
+ gfx::Size image_bounds_;
+
+ // The transparent center region that shows the parent layer's contents in
+ // image space.
+ gfx::Rect image_aperture_;
+
+ ResourceProvider::ResourceId resource_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(NinePatchLayerImpl);
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_NINE_PATCH_LAYER_IMPL_H_
diff --git a/chromium/cc/layers/nine_patch_layer_impl_unittest.cc b/chromium/cc/layers/nine_patch_layer_impl_unittest.cc
new file mode 100644
index 00000000000..1985bd4c29c
--- /dev/null
+++ b/chromium/cc/layers/nine_patch_layer_impl_unittest.cc
@@ -0,0 +1,155 @@
+// 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 <stdio.h>
+
+#include "cc/layers/append_quads_data.h"
+#include "cc/layers/nine_patch_layer_impl.h"
+#include "cc/quads/texture_draw_quad.h"
+#include "cc/test/fake_impl_proxy.h"
+#include "cc/test/fake_layer_tree_host_impl.h"
+#include "cc/test/geometry_test_utils.h"
+#include "cc/test/layer_test_common.h"
+#include "cc/test/mock_quad_culler.h"
+#include "cc/trees/single_thread_proxy.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/safe_integer_conversions.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+namespace {
+
+gfx::Rect ToRoundedIntRect(gfx::RectF rect_f) {
+ return gfx::Rect(gfx::ToRoundedInt(rect_f.x()),
+ gfx::ToRoundedInt(rect_f.y()),
+ gfx::ToRoundedInt(rect_f.width()),
+ gfx::ToRoundedInt(rect_f.height()));
+}
+
+TEST(NinePatchLayerImplTest, VerifyDrawQuads) {
+ // Input is a 100x100 bitmap with a 40x50 aperture at x=20, y=30.
+ // The bounds of the layer are set to 400x400, so the draw quads
+ // generated should leave the border width (40) intact.
+ MockQuadCuller quad_culler;
+ gfx::Size bitmap_size(100, 100);
+ gfx::Size layer_size(400, 400);
+ gfx::Rect visible_content_rect(layer_size);
+ gfx::Rect aperture_rect(20, 30, 40, 50);
+ gfx::Rect scaled_aperture_non_uniform(20, 30, 340, 350);
+
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ scoped_ptr<NinePatchLayerImpl> layer =
+ NinePatchLayerImpl::Create(host_impl.active_tree(), 1);
+ layer->draw_properties().visible_content_rect = visible_content_rect;
+ layer->SetBounds(layer_size);
+ layer->SetContentBounds(layer_size);
+ layer->CreateRenderSurface();
+ layer->draw_properties().render_target = layer.get();
+ layer->SetLayout(bitmap_size, aperture_rect);
+ layer->SetResourceId(1);
+
+ // This scale should not affect the generated quad geometry, but only
+ // the shared draw transform.
+ gfx::Transform transform;
+ transform.Scale(10, 10);
+ layer->draw_properties().target_space_transform = transform;
+
+ AppendQuadsData data;
+ layer->AppendQuads(&quad_culler, &data);
+
+ // Verify quad rects
+ const QuadList& quads = quad_culler.quad_list();
+ EXPECT_EQ(8u, quads.size());
+ Region remaining(visible_content_rect);
+ for (size_t i = 0; i < quads.size(); ++i) {
+ DrawQuad* quad = quads[i];
+ gfx::Rect quad_rect = quad->rect;
+
+ EXPECT_TRUE(visible_content_rect.Contains(quad_rect)) << i;
+ EXPECT_TRUE(remaining.Contains(quad_rect)) << i;
+ EXPECT_EQ(transform, quad->quadTransform());
+ remaining.Subtract(Region(quad_rect));
+ }
+ EXPECT_RECT_EQ(scaled_aperture_non_uniform, remaining.bounds());
+ Region scaled_aperture_region(scaled_aperture_non_uniform);
+ EXPECT_EQ(scaled_aperture_region, remaining);
+
+ // Verify UV rects
+ gfx::Rect bitmap_rect(bitmap_size);
+ Region tex_remaining(bitmap_rect);
+ for (size_t i = 0; i < quads.size(); ++i) {
+ DrawQuad* quad = quads[i];
+ const TextureDrawQuad* tex_quad = TextureDrawQuad::MaterialCast(quad);
+ gfx::RectF tex_rect =
+ gfx::BoundingRect(tex_quad->uv_top_left, tex_quad->uv_bottom_right);
+ tex_rect.Scale(bitmap_size.width(), bitmap_size.height());
+ tex_remaining.Subtract(Region(ToRoundedIntRect(tex_rect)));
+ }
+ EXPECT_RECT_EQ(aperture_rect, tex_remaining.bounds());
+ Region aperture_region(aperture_rect);
+ EXPECT_EQ(aperture_region, tex_remaining);
+}
+
+TEST(NinePatchLayerImplTest, VerifyDrawQuadsForSqueezedLayer) {
+ // Test with a layer much smaller than the bitmap.
+ MockQuadCuller quad_culler;
+ gfx::Size bitmap_size(101, 101);
+ gfx::Size layer_size(51, 51);
+ gfx::Rect visible_content_rect(layer_size);
+ gfx::Rect aperture_rect(20, 30, 40, 45); // rightWidth: 40, botHeight: 25
+
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ scoped_ptr<NinePatchLayerImpl> layer =
+ NinePatchLayerImpl::Create(host_impl.active_tree(), 1);
+ layer->draw_properties().visible_content_rect = visible_content_rect;
+ layer->SetBounds(layer_size);
+ layer->SetContentBounds(layer_size);
+ layer->CreateRenderSurface();
+ layer->draw_properties().render_target = layer.get();
+ layer->SetLayout(bitmap_size, aperture_rect);
+ layer->SetResourceId(1);
+
+ AppendQuadsData data;
+ layer->AppendQuads(&quad_culler, &data);
+
+ // Verify corner rects fill the layer and don't overlap
+ const QuadList& quads = quad_culler.quad_list();
+ EXPECT_EQ(4u, quads.size());
+ Region filled;
+ for (size_t i = 0; i < quads.size(); ++i) {
+ DrawQuad* quad = quads[i];
+ gfx::Rect quad_rect = quad->rect;
+
+ EXPECT_FALSE(filled.Intersects(quad_rect));
+ filled.Union(quad_rect);
+ }
+ Region expected_full(visible_content_rect);
+ EXPECT_EQ(expected_full, filled);
+
+ // Verify UV rects cover the corners of the bitmap and the crop is weighted
+ // proportionately to the relative corner sizes (for uneven apertures).
+ gfx::Rect bitmap_rect(bitmap_size);
+ Region tex_remaining(bitmap_rect);
+ for (size_t i = 0; i < quads.size(); ++i) {
+ DrawQuad* quad = quads[i];
+ const TextureDrawQuad* tex_quad = TextureDrawQuad::MaterialCast(quad);
+ gfx::RectF tex_rect =
+ gfx::BoundingRect(tex_quad->uv_top_left, tex_quad->uv_bottom_right);
+ tex_rect.Scale(bitmap_size.width(), bitmap_size.height());
+ tex_remaining.Subtract(Region(ToRoundedIntRect(tex_rect)));
+ }
+ Region expected_remaining_region = Region(gfx::Rect(bitmap_size));
+ expected_remaining_region.Subtract(gfx::Rect(0, 0, 17, 28));
+ expected_remaining_region.Subtract(gfx::Rect(67, 0, 34, 28));
+ expected_remaining_region.Subtract(gfx::Rect(0, 78, 17, 23));
+ expected_remaining_region.Subtract(gfx::Rect(67, 78, 34, 23));
+ EXPECT_EQ(expected_remaining_region, tex_remaining);
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/layers/nine_patch_layer_unittest.cc b/chromium/cc/layers/nine_patch_layer_unittest.cc
new file mode 100644
index 00000000000..5268d02472f
--- /dev/null
+++ b/chromium/cc/layers/nine_patch_layer_unittest.cc
@@ -0,0 +1,146 @@
+// 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 "cc/layers/nine_patch_layer.h"
+
+#include "cc/debug/overdraw_metrics.h"
+#include "cc/resources/prioritized_resource_manager.h"
+#include "cc/resources/resource_provider.h"
+#include "cc/resources/resource_update_queue.h"
+#include "cc/scheduler/texture_uploader.h"
+#include "cc/test/fake_layer_tree_host_client.h"
+#include "cc/test/fake_output_surface.h"
+#include "cc/test/geometry_test_utils.h"
+#include "cc/trees/layer_tree_host.h"
+#include "cc/trees/occlusion_tracker.h"
+#include "cc/trees/single_thread_proxy.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+using ::testing::Mock;
+using ::testing::_;
+using ::testing::AtLeast;
+using ::testing::AnyNumber;
+
+namespace cc {
+namespace {
+
+class MockLayerTreeHost : public LayerTreeHost {
+ public:
+ explicit MockLayerTreeHost(LayerTreeHostClient* client)
+ : LayerTreeHost(client, LayerTreeSettings()) {
+ Initialize(NULL);
+ }
+};
+
+class NinePatchLayerTest : public testing::Test {
+ public:
+ NinePatchLayerTest() : fake_client_(FakeLayerTreeHostClient::DIRECT_3D) {}
+
+ cc::Proxy* Proxy() const { return layer_tree_host_->proxy(); }
+
+ protected:
+ virtual void SetUp() {
+ layer_tree_host_.reset(new MockLayerTreeHost(&fake_client_));
+ }
+
+ virtual void TearDown() {
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+ }
+
+ scoped_ptr<MockLayerTreeHost> layer_tree_host_;
+ FakeLayerTreeHostClient fake_client_;
+};
+
+TEST_F(NinePatchLayerTest, TriggerFullUploadOnceWhenChangingBitmap) {
+ scoped_refptr<NinePatchLayer> test_layer = NinePatchLayer::Create();
+ ASSERT_TRUE(test_layer.get());
+ test_layer->SetIsDrawable(true);
+ test_layer->SetBounds(gfx::Size(100, 100));
+
+ layer_tree_host_->SetRootLayer(test_layer);
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+ EXPECT_EQ(test_layer->layer_tree_host(), layer_tree_host_.get());
+
+ layer_tree_host_->InitializeOutputSurfaceIfNeeded();
+
+ PriorityCalculator calculator;
+ ResourceUpdateQueue queue;
+ OcclusionTracker occlusion_tracker(gfx::Rect(), false);
+
+ // No bitmap set should not trigger any uploads.
+ test_layer->SavePaintProperties();
+ test_layer->SetTexturePriorities(calculator);
+ test_layer->Update(&queue, &occlusion_tracker);
+ EXPECT_EQ(0u, queue.FullUploadSize());
+ EXPECT_EQ(0u, queue.PartialUploadSize());
+
+ // Setting a bitmap set should trigger a single full upload.
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, 10, 10);
+ bitmap.allocPixels();
+ test_layer->SetBitmap(bitmap, gfx::Rect(5, 5, 1, 1));
+ test_layer->SavePaintProperties();
+ test_layer->SetTexturePriorities(calculator);
+ test_layer->Update(&queue, &occlusion_tracker);
+ EXPECT_EQ(1u, queue.FullUploadSize());
+ EXPECT_EQ(0u, queue.PartialUploadSize());
+ ResourceUpdate params = queue.TakeFirstFullUpload();
+ EXPECT_TRUE(params.texture != NULL);
+
+ // Upload the texture.
+ layer_tree_host_->contents_texture_manager()->SetMaxMemoryLimitBytes(
+ 1024 * 1024);
+ layer_tree_host_->contents_texture_manager()->PrioritizeTextures();
+
+ scoped_ptr<OutputSurface> output_surface;
+ scoped_ptr<ResourceProvider> resource_provider;
+ {
+ DebugScopedSetImplThread impl_thread(Proxy());
+ DebugScopedSetMainThreadBlocked main_thread_blocked(Proxy());
+ output_surface = CreateFakeOutputSurface();
+ resource_provider = ResourceProvider::Create(output_surface.get(), 0);
+ params.texture->AcquireBackingTexture(resource_provider.get());
+ ASSERT_TRUE(params.texture->have_backing_texture());
+ }
+
+ // Nothing changed, so no repeated upload.
+ test_layer->SavePaintProperties();
+ test_layer->SetTexturePriorities(calculator);
+ test_layer->Update(&queue, &occlusion_tracker);
+ EXPECT_EQ(0u, queue.FullUploadSize());
+ EXPECT_EQ(0u, queue.PartialUploadSize());
+ {
+ DebugScopedSetImplThread impl_thread(Proxy());
+ DebugScopedSetMainThreadBlocked main_thread_blocked(Proxy());
+ layer_tree_host_->contents_texture_manager()->ClearAllMemory(
+ resource_provider.get());
+ }
+
+ // Reupload after eviction
+ test_layer->SavePaintProperties();
+ test_layer->SetTexturePriorities(calculator);
+ test_layer->Update(&queue, &occlusion_tracker);
+ EXPECT_EQ(1u, queue.FullUploadSize());
+ EXPECT_EQ(0u, queue.PartialUploadSize());
+
+ // PrioritizedResourceManager clearing
+ layer_tree_host_->contents_texture_manager()->UnregisterTexture(
+ params.texture);
+ EXPECT_EQ(NULL, params.texture->resource_manager());
+ test_layer->SavePaintProperties();
+ test_layer->SetTexturePriorities(calculator);
+ ResourceUpdateQueue queue2;
+ test_layer->Update(&queue2, &occlusion_tracker);
+ EXPECT_EQ(1u, queue2.FullUploadSize());
+ EXPECT_EQ(0u, queue2.PartialUploadSize());
+ params = queue2.TakeFirstFullUpload();
+ EXPECT_TRUE(params.texture != NULL);
+ EXPECT_EQ(params.texture->resource_manager(),
+ layer_tree_host_->contents_texture_manager());
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/layers/paint_properties.h b/chromium/cc/layers/paint_properties.h
new file mode 100644
index 00000000000..323e9499edb
--- /dev/null
+++ b/chromium/cc/layers/paint_properties.h
@@ -0,0 +1,23 @@
+// 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.
+
+#ifndef CC_LAYERS_PAINT_PROPERTIES_H_
+#define CC_LAYERS_PAINT_PROPERTIES_H_
+
+#include "ui/gfx/size.h"
+
+namespace cc {
+
+// Container for properties that layers need to save before they can be paint.
+struct CC_EXPORT PaintProperties {
+ PaintProperties() : source_frame_number(-1) {}
+
+ gfx::Size bounds;
+
+ int source_frame_number;
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_PAINT_PROPERTIES_H_
diff --git a/chromium/cc/layers/picture_image_layer.cc b/chromium/cc/layers/picture_image_layer.cc
new file mode 100644
index 00000000000..03e3a9e0157
--- /dev/null
+++ b/chromium/cc/layers/picture_image_layer.cc
@@ -0,0 +1,58 @@
+// Copyright 2010 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 "cc/layers/picture_image_layer.h"
+
+#include "cc/layers/picture_image_layer_impl.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+
+namespace cc {
+
+scoped_refptr<PictureImageLayer> PictureImageLayer::Create() {
+ return make_scoped_refptr(new PictureImageLayer());
+}
+
+PictureImageLayer::PictureImageLayer() : PictureLayer(this) {}
+
+PictureImageLayer::~PictureImageLayer() {
+ ClearClient();
+}
+
+scoped_ptr<LayerImpl> PictureImageLayer::CreateLayerImpl(
+ LayerTreeImpl* tree_impl) {
+ return PictureImageLayerImpl::Create(tree_impl, id()).PassAs<LayerImpl>();
+}
+
+bool PictureImageLayer::DrawsContent() const {
+ return !bitmap_.isNull() && PictureLayer::DrawsContent();
+}
+
+void PictureImageLayer::SetBitmap(const SkBitmap& bitmap) {
+ // SetBitmap() currently gets called whenever there is any
+ // style change that affects the layer even if that change doesn't
+ // affect the actual contents of the image (e.g. a CSS animation).
+ // With this check in place we avoid unecessary texture uploads.
+ if (bitmap.pixelRef() && bitmap.pixelRef() == bitmap_.pixelRef())
+ return;
+
+ bitmap_ = bitmap;
+ SetNeedsDisplay();
+}
+
+void PictureImageLayer::PaintContents(SkCanvas* canvas,
+ gfx::Rect clip,
+ gfx::RectF* opaque) {
+ if (!bitmap_.width() || !bitmap_.height())
+ return;
+
+ SkScalar content_to_layer_scale_x =
+ SkFloatToScalar(static_cast<float>(bounds().width()) / bitmap_.width());
+ SkScalar content_to_layer_scale_y =
+ SkFloatToScalar(static_cast<float>(bounds().height()) / bitmap_.height());
+ canvas->scale(content_to_layer_scale_x, content_to_layer_scale_y);
+
+ canvas->drawBitmap(bitmap_, 0, 0);
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/picture_image_layer.h b/chromium/cc/layers/picture_image_layer.h
new file mode 100644
index 00000000000..09db1df6b3b
--- /dev/null
+++ b/chromium/cc/layers/picture_image_layer.h
@@ -0,0 +1,45 @@
+// Copyright 2010 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.
+
+#ifndef CC_LAYERS_PICTURE_IMAGE_LAYER_H_
+#define CC_LAYERS_PICTURE_IMAGE_LAYER_H_
+
+#include "cc/base/cc_export.h"
+#include "cc/layers/content_layer_client.h"
+#include "cc/layers/picture_layer.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/size.h"
+
+namespace cc {
+
+class CC_EXPORT PictureImageLayer : public PictureLayer, ContentLayerClient {
+ public:
+ static scoped_refptr<PictureImageLayer> Create();
+
+ void SetBitmap(const SkBitmap& image);
+
+ // Layer implementation.
+ virtual scoped_ptr<LayerImpl> CreateLayerImpl(
+ LayerTreeImpl* tree_impl) OVERRIDE;
+ virtual bool DrawsContent() const OVERRIDE;
+
+ // ContentLayerClient implementation.
+ virtual void PaintContents(
+ SkCanvas* canvas,
+ gfx::Rect clip,
+ gfx::RectF* opaque) OVERRIDE;
+ virtual void DidChangeLayerCanUseLCDText() OVERRIDE {}
+
+ private:
+ PictureImageLayer();
+ virtual ~PictureImageLayer();
+
+ SkBitmap bitmap_;
+
+ DISALLOW_COPY_AND_ASSIGN(PictureImageLayer);
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_PICTURE_IMAGE_LAYER_H_
diff --git a/chromium/cc/layers/picture_image_layer_impl.cc b/chromium/cc/layers/picture_image_layer_impl.cc
new file mode 100644
index 00000000000..7ae9de41004
--- /dev/null
+++ b/chromium/cc/layers/picture_image_layer_impl.cc
@@ -0,0 +1,52 @@
+// Copyright 2013 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 "cc/layers/picture_image_layer_impl.h"
+
+#include <algorithm>
+
+#include "cc/debug/debug_colors.h"
+#include "cc/trees/layer_tree_impl.h"
+
+namespace cc {
+
+PictureImageLayerImpl::PictureImageLayerImpl(LayerTreeImpl* tree_impl, int id)
+ : PictureLayerImpl(tree_impl, id) {
+}
+
+PictureImageLayerImpl::~PictureImageLayerImpl() {
+}
+
+const char* PictureImageLayerImpl::LayerTypeAsString() const {
+ return "cc::PictureImageLayerImpl";
+}
+
+scoped_ptr<LayerImpl> PictureImageLayerImpl::CreateLayerImpl(
+ LayerTreeImpl* tree_impl) {
+ return PictureImageLayerImpl::Create(tree_impl, id()).PassAs<LayerImpl>();
+}
+
+void PictureImageLayerImpl::GetDebugBorderProperties(
+ SkColor* color, float* width) const {
+ *color = DebugColors::ImageLayerBorderColor();
+ *width = DebugColors::ImageLayerBorderWidth(layer_tree_impl());
+}
+
+bool PictureImageLayerImpl::ShouldAdjustRasterScale(
+ bool animating_transform_to_screen) const {
+ return false;
+}
+
+void PictureImageLayerImpl::CalculateRasterContentsScale(
+ bool animating_transform_to_screen,
+ float* raster_contents_scale,
+ float* low_res_raster_contents_scale) const {
+ // Don't scale images during rastering to ensure image quality, save memory
+ // and avoid frequent re-rastering on change of scale.
+ *raster_contents_scale = std::max(1.f, MinimumContentsScale());
+ // We don't need low res tiles.
+ *low_res_raster_contents_scale = *raster_contents_scale;
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/picture_image_layer_impl.h b/chromium/cc/layers/picture_image_layer_impl.h
new file mode 100644
index 00000000000..4a2db50f668
--- /dev/null
+++ b/chromium/cc/layers/picture_image_layer_impl.h
@@ -0,0 +1,41 @@
+// Copyright 2013 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.
+
+#ifndef CC_LAYERS_PICTURE_IMAGE_LAYER_IMPL_H_
+#define CC_LAYERS_PICTURE_IMAGE_LAYER_IMPL_H_
+
+#include "cc/layers/picture_layer_impl.h"
+
+namespace cc {
+
+class CC_EXPORT PictureImageLayerImpl : public PictureLayerImpl {
+ public:
+ static scoped_ptr<PictureImageLayerImpl> Create(LayerTreeImpl* tree_impl,
+ int id) {
+ return make_scoped_ptr(new PictureImageLayerImpl(tree_impl, id));
+ }
+ virtual ~PictureImageLayerImpl();
+
+ virtual const char* LayerTypeAsString() const OVERRIDE;
+ virtual scoped_ptr<LayerImpl> CreateLayerImpl(
+ LayerTreeImpl* tree_impl) OVERRIDE;
+
+ protected:
+ PictureImageLayerImpl(LayerTreeImpl* tree_impl, int id);
+
+ virtual bool ShouldAdjustRasterScale(
+ bool animating_transform_to_screen) const OVERRIDE;
+ virtual void CalculateRasterContentsScale(
+ bool animating_transform_to_screen,
+ float* raster_contents_scale,
+ float* low_res_raster_contents_scale) const OVERRIDE;
+ virtual void GetDebugBorderProperties(
+ SkColor* color, float* width) const OVERRIDE;
+
+ DISALLOW_COPY_AND_ASSIGN(PictureImageLayerImpl);
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_PICTURE_IMAGE_LAYER_IMPL_H_
diff --git a/chromium/cc/layers/picture_image_layer_impl_unittest.cc b/chromium/cc/layers/picture_image_layer_impl_unittest.cc
new file mode 100644
index 00000000000..9aae4d6f7d2
--- /dev/null
+++ b/chromium/cc/layers/picture_image_layer_impl_unittest.cc
@@ -0,0 +1,89 @@
+// Copyright 2013 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 "cc/layers/picture_image_layer_impl.h"
+
+#include "cc/test/fake_impl_proxy.h"
+#include "cc/test/fake_layer_tree_host_impl.h"
+#include "cc/test/fake_output_surface.h"
+#include "cc/test/fake_picture_layer_tiling_client.h"
+#include "cc/test/impl_side_painting_settings.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+class TestablePictureImageLayerImpl : public PictureImageLayerImpl {
+ public:
+ TestablePictureImageLayerImpl(LayerTreeImpl* tree_impl, int id)
+ : PictureImageLayerImpl(tree_impl, id) {
+ }
+ friend class PictureImageLayerImplTest;
+};
+
+class PictureImageLayerImplTest : public testing::Test {
+ public:
+ PictureImageLayerImplTest()
+ : host_impl_(ImplSidePaintingSettings(), &proxy_) {
+ tiling_client_.SetTileSize(ImplSidePaintingSettings().default_tile_size);
+ host_impl_.CreatePendingTree();
+ host_impl_.InitializeRenderer(CreateFakeOutputSurface());
+ }
+
+ scoped_ptr<TestablePictureImageLayerImpl> CreateLayer(int id) {
+ TestablePictureImageLayerImpl* layer =
+ new TestablePictureImageLayerImpl(host_impl_.pending_tree(), id);
+ layer->SetBounds(gfx::Size(100, 200));
+ layer->tilings_.reset(new PictureLayerTilingSet(&tiling_client_,
+ layer->bounds()));
+ layer->pile_ = tiling_client_.pile();
+ return make_scoped_ptr(layer);
+ }
+
+ void UpdateDrawProperties() {
+ host_impl_.pending_tree()->UpdateDrawProperties();
+ }
+
+ private:
+ FakeImplProxy proxy_;
+ FakeLayerTreeHostImpl host_impl_;
+ FakePictureLayerTilingClient tiling_client_;
+};
+
+TEST_F(PictureImageLayerImplTest, CalculateContentsScale) {
+ scoped_ptr<TestablePictureImageLayerImpl> layer(CreateLayer(1));
+ layer->SetDrawsContent(true);
+
+ float contents_scale_x;
+ float contents_scale_y;
+ gfx::Size content_bounds;
+ layer->CalculateContentsScale(2.f, 3.f, 4.f, false,
+ &contents_scale_x, &contents_scale_y,
+ &content_bounds);
+ EXPECT_FLOAT_EQ(1.f, contents_scale_x);
+ EXPECT_FLOAT_EQ(1.f, contents_scale_y);
+ EXPECT_EQ(layer->bounds(), content_bounds);
+}
+
+TEST_F(PictureImageLayerImplTest, AreVisibleResourcesReady) {
+ scoped_ptr<TestablePictureImageLayerImpl> layer(CreateLayer(1));
+ layer->SetBounds(gfx::Size(100, 200));
+ layer->SetDrawsContent(true);
+
+ UpdateDrawProperties();
+
+ float contents_scale_x;
+ float contents_scale_y;
+ gfx::Size content_bounds;
+ layer->CalculateContentsScale(2.f, 3.f, 4.f, false,
+ &contents_scale_x, &contents_scale_y,
+ &content_bounds);
+ layer->UpdateTilePriorities();
+
+ EXPECT_TRUE(layer->AreVisibleResourcesReady());
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/layers/picture_layer.cc b/chromium/cc/layers/picture_layer.cc
new file mode 100644
index 00000000000..889e2fbd3f6
--- /dev/null
+++ b/chromium/cc/layers/picture_layer.cc
@@ -0,0 +1,133 @@
+// 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 "cc/layers/picture_layer.h"
+
+#include "cc/debug/benchmark_instrumentation.h"
+#include "cc/debug/devtools_instrumentation.h"
+#include "cc/layers/picture_layer_impl.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "ui/gfx/rect_conversions.h"
+
+namespace cc {
+
+scoped_refptr<PictureLayer> PictureLayer::Create(ContentLayerClient* client) {
+ return make_scoped_refptr(new PictureLayer(client));
+}
+
+PictureLayer::PictureLayer(ContentLayerClient* client)
+ : client_(client),
+ pile_(make_scoped_refptr(new PicturePile())),
+ instrumentation_object_tracker_(id()),
+ is_mask_(false) {
+}
+
+PictureLayer::~PictureLayer() {
+}
+
+bool PictureLayer::DrawsContent() const {
+ return Layer::DrawsContent() && client_;
+}
+
+scoped_ptr<LayerImpl> PictureLayer::CreateLayerImpl(LayerTreeImpl* tree_impl) {
+ return PictureLayerImpl::Create(tree_impl, id()).PassAs<LayerImpl>();
+}
+
+void PictureLayer::PushPropertiesTo(LayerImpl* base_layer) {
+ Layer::PushPropertiesTo(base_layer);
+
+ PictureLayerImpl* layer_impl = static_cast<PictureLayerImpl*>(base_layer);
+ // This should be first so others can use it.
+ layer_impl->UpdateTwinLayer();
+
+ layer_impl->SetIsMask(is_mask_);
+ layer_impl->CreateTilingSetIfNeeded();
+ // Unlike other properties, invalidation must always be set on layer_impl.
+ // See PictureLayerImpl::PushPropertiesTo for more details.
+ layer_impl->invalidation_.Clear();
+ layer_impl->invalidation_.Swap(&pile_invalidation_);
+ layer_impl->pile_ = PicturePileImpl::CreateFromOther(pile_.get());
+ layer_impl->SyncFromActiveLayer();
+
+ // PictureLayer must push properties every frame.
+ // TODO(danakj): If we can avoid requiring to do CreateTilingSetIfNeeded() and
+ // SyncFromActiveLayer() on every commit then this could go away, maybe
+ // conditionally. crbug.com/259402
+ needs_push_properties_ = true;
+}
+
+void PictureLayer::SetLayerTreeHost(LayerTreeHost* host) {
+ Layer::SetLayerTreeHost(host);
+ if (host) {
+ pile_->SetMinContentsScale(host->settings().minimum_contents_scale);
+ pile_->SetTileGridSize(host->settings().default_tile_size);
+ pile_->set_num_raster_threads(host->settings().num_raster_threads);
+ pile_->set_slow_down_raster_scale_factor(
+ host->debug_state().slow_down_raster_scale_factor);
+ pile_->set_show_debug_picture_borders(
+ host->debug_state().show_picture_borders);
+ }
+}
+
+void PictureLayer::SetNeedsDisplayRect(const gfx::RectF& layer_rect) {
+ gfx::Rect rect = gfx::ToEnclosedRect(layer_rect);
+ if (!rect.IsEmpty()) {
+ // Clamp invalidation to the layer bounds.
+ rect.Intersect(gfx::Rect(bounds()));
+ pending_invalidation_.Union(rect);
+ }
+ Layer::SetNeedsDisplayRect(layer_rect);
+}
+
+bool PictureLayer::Update(ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion) {
+ // Do not early-out of this function so that PicturePile::Update has a chance
+ // to record pictures due to changing visibility of this layer.
+
+ TRACE_EVENT1(benchmark_instrumentation::kCategory,
+ benchmark_instrumentation::kPictureLayerUpdate,
+ benchmark_instrumentation::kSourceFrameNumber,
+ layer_tree_host()->source_frame_number());
+
+ bool updated = Layer::Update(queue, occlusion);
+
+ pile_->Resize(paint_properties().bounds);
+
+ // Calling paint in WebKit can sometimes cause invalidations, so save
+ // off the invalidation prior to calling update.
+ pending_invalidation_.Swap(&pile_invalidation_);
+ pending_invalidation_.Clear();
+
+ gfx::Rect visible_layer_rect = gfx::ScaleToEnclosingRect(
+ visible_content_rect(), 1.f / contents_scale_x());
+ if (layer_tree_host()->settings().using_synchronous_renderer_compositor) {
+ // Workaround for http://crbug.com/235910 - to retain backwards compat
+ // the full page content must always be provided in the picture layer.
+ visible_layer_rect = gfx::Rect(bounds());
+ }
+ devtools_instrumentation::ScopedLayerTask paint_layer(
+ devtools_instrumentation::kPaintLayer, id());
+ updated |= pile_->Update(client_,
+ SafeOpaqueBackgroundColor(),
+ contents_opaque(),
+ pile_invalidation_,
+ visible_layer_rect,
+ rendering_stats_instrumentation());
+ if (!updated) {
+ // If this invalidation did not affect the pile, then it can be cleared as
+ // an optimization.
+ pile_invalidation_.Clear();
+ }
+ return updated;
+}
+
+void PictureLayer::SetIsMask(bool is_mask) {
+ is_mask_ = is_mask;
+}
+
+bool PictureLayer::SupportsLCDText() const {
+ return true;
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/picture_layer.h b/chromium/cc/layers/picture_layer.h
new file mode 100644
index 00000000000..7a9a427d427
--- /dev/null
+++ b/chromium/cc/layers/picture_layer.h
@@ -0,0 +1,58 @@
+// 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.
+
+#ifndef CC_LAYERS_PICTURE_LAYER_H_
+#define CC_LAYERS_PICTURE_LAYER_H_
+
+#include "cc/base/invalidation_region.h"
+#include "cc/debug/devtools_instrumentation.h"
+#include "cc/layers/layer.h"
+#include "cc/resources/picture_pile.h"
+#include "cc/trees/occlusion_tracker.h"
+
+namespace cc {
+
+class ContentLayerClient;
+class ResourceUpdateQueue;
+
+class CC_EXPORT PictureLayer : public Layer {
+ public:
+ static scoped_refptr<PictureLayer> Create(ContentLayerClient* client);
+
+ void ClearClient() { client_ = NULL; }
+
+ // Layer interface.
+ virtual bool DrawsContent() const OVERRIDE;
+ virtual scoped_ptr<LayerImpl> CreateLayerImpl(
+ LayerTreeImpl* tree_impl) OVERRIDE;
+ virtual void SetLayerTreeHost(LayerTreeHost* host) OVERRIDE;
+ virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE;
+ virtual void SetNeedsDisplayRect(const gfx::RectF& layer_rect) OVERRIDE;
+ virtual bool Update(
+ ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion) OVERRIDE;
+ virtual void SetIsMask(bool is_mask) OVERRIDE;
+ virtual bool SupportsLCDText() const OVERRIDE;
+
+ protected:
+ explicit PictureLayer(ContentLayerClient* client);
+ virtual ~PictureLayer();
+
+ private:
+ ContentLayerClient* client_;
+ scoped_refptr<PicturePile> pile_;
+ devtools_instrumentation::
+ ScopedLayerObjectTracker instrumentation_object_tracker_;
+ // Invalidation to use the next time update is called.
+ InvalidationRegion pending_invalidation_;
+ // Invalidation from the last time update was called.
+ Region pile_invalidation_;
+ bool is_mask_;
+
+ DISALLOW_COPY_AND_ASSIGN(PictureLayer);
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_PICTURE_LAYER_H_
diff --git a/chromium/cc/layers/picture_layer_impl.cc b/chromium/cc/layers/picture_layer_impl.cc
new file mode 100644
index 00000000000..4f6052f04fa
--- /dev/null
+++ b/chromium/cc/layers/picture_layer_impl.cc
@@ -0,0 +1,1038 @@
+// 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 "cc/layers/picture_layer_impl.h"
+
+#include <algorithm>
+
+#include "base/time/time.h"
+#include "cc/base/math_util.h"
+#include "cc/base/util.h"
+#include "cc/debug/debug_colors.h"
+#include "cc/debug/traced_value.h"
+#include "cc/layers/append_quads_data.h"
+#include "cc/layers/quad_sink.h"
+#include "cc/quads/checkerboard_draw_quad.h"
+#include "cc/quads/debug_border_draw_quad.h"
+#include "cc/quads/picture_draw_quad.h"
+#include "cc/quads/solid_color_draw_quad.h"
+#include "cc/quads/tile_draw_quad.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "ui/gfx/quad_f.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/size_conversions.h"
+
+namespace {
+const float kMaxScaleRatioDuringPinch = 2.0f;
+}
+
+namespace cc {
+
+PictureLayerImpl::PictureLayerImpl(LayerTreeImpl* tree_impl, int id)
+ : LayerImpl(tree_impl, id),
+ twin_layer_(NULL),
+ pile_(PicturePileImpl::Create()),
+ last_content_scale_(0),
+ is_mask_(false),
+ ideal_page_scale_(0.f),
+ ideal_device_scale_(0.f),
+ ideal_source_scale_(0.f),
+ ideal_contents_scale_(0.f),
+ raster_page_scale_(0.f),
+ raster_device_scale_(0.f),
+ raster_source_scale_(0.f),
+ raster_contents_scale_(0.f),
+ low_res_raster_contents_scale_(0.f),
+ raster_source_scale_was_animating_(false),
+ is_using_lcd_text_(tree_impl->settings().can_use_lcd_text) {
+}
+
+PictureLayerImpl::~PictureLayerImpl() {
+}
+
+const char* PictureLayerImpl::LayerTypeAsString() const {
+ return "cc::PictureLayerImpl";
+}
+
+scoped_ptr<LayerImpl> PictureLayerImpl::CreateLayerImpl(
+ LayerTreeImpl* tree_impl) {
+ return PictureLayerImpl::Create(tree_impl, id()).PassAs<LayerImpl>();
+}
+
+void PictureLayerImpl::CreateTilingSetIfNeeded() {
+ DCHECK(layer_tree_impl()->IsPendingTree());
+ if (!tilings_)
+ tilings_.reset(new PictureLayerTilingSet(this, bounds()));
+}
+
+void PictureLayerImpl::PushPropertiesTo(LayerImpl* base_layer) {
+ LayerImpl::PushPropertiesTo(base_layer);
+
+ PictureLayerImpl* layer_impl = static_cast<PictureLayerImpl*>(base_layer);
+
+ // When the pending tree pushes to the active tree, the pending twin
+ // disappears.
+ layer_impl->twin_layer_ = NULL;
+ twin_layer_ = NULL;
+
+ layer_impl->SetIsMask(is_mask_);
+ layer_impl->pile_ = pile_;
+ pile_ = NULL;
+
+ layer_impl->tilings_.swap(tilings_);
+ layer_impl->tilings_->SetClient(layer_impl);
+ if (tilings_)
+ tilings_->SetClient(this);
+
+ layer_impl->raster_page_scale_ = raster_page_scale_;
+ layer_impl->raster_device_scale_ = raster_device_scale_;
+ layer_impl->raster_source_scale_ = raster_source_scale_;
+ layer_impl->raster_contents_scale_ = raster_contents_scale_;
+ layer_impl->low_res_raster_contents_scale_ = low_res_raster_contents_scale_;
+
+ layer_impl->UpdateLCDTextStatus(is_using_lcd_text_);
+
+ // As an optimization, don't make a copy of this potentially complex region,
+ // and swap it directly from the pending to the active layer. In general, any
+ // property pushed to a LayerImpl continues to live on that LayerImpl.
+ // However, invalidation is the difference between two main thread frames, so
+ // it no longer makes sense once the pending tree gets recycled. It will
+ // always get pushed during PictureLayer::PushPropertiesTo.
+ layer_impl->invalidation_.Swap(&invalidation_);
+ invalidation_.Clear();
+}
+
+void PictureLayerImpl::AppendQuads(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data) {
+ gfx::Rect rect(visible_content_rect());
+ gfx::Rect content_rect(content_bounds());
+
+ SharedQuadState* shared_quad_state =
+ quad_sink->UseSharedQuadState(CreateSharedQuadState());
+
+ bool draw_direct_to_backbuffer =
+ draw_properties().can_draw_directly_to_backbuffer &&
+ layer_tree_impl()->settings().force_direct_layer_drawing;
+
+ if (draw_direct_to_backbuffer ||
+ current_draw_mode_ == DRAW_MODE_RESOURCELESS_SOFTWARE) {
+ AppendDebugBorderQuad(
+ quad_sink,
+ shared_quad_state,
+ append_quads_data,
+ DebugColors::DirectPictureBorderColor(),
+ DebugColors::DirectPictureBorderWidth(layer_tree_impl()));
+
+ gfx::Rect geometry_rect = rect;
+ gfx::Rect opaque_rect = contents_opaque() ? geometry_rect : gfx::Rect();
+ gfx::Size texture_size = rect.size();
+ gfx::RectF texture_rect = gfx::RectF(texture_size);
+ gfx::Rect quad_content_rect = rect;
+ float contents_scale = contents_scale_x();
+
+ scoped_ptr<PictureDrawQuad> quad = PictureDrawQuad::Create();
+ quad->SetNew(shared_quad_state,
+ geometry_rect,
+ opaque_rect,
+ texture_rect,
+ texture_size,
+ false,
+ quad_content_rect,
+ contents_scale,
+ draw_direct_to_backbuffer,
+ pile_);
+ if (quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data))
+ append_quads_data->num_missing_tiles++;
+ return;
+ }
+
+ AppendDebugBorderQuad(quad_sink, shared_quad_state, append_quads_data);
+
+ bool clipped = false;
+ gfx::QuadF target_quad = MathUtil::MapQuad(
+ draw_transform(),
+ gfx::QuadF(rect),
+ &clipped);
+ if (ShowDebugBorders()) {
+ for (PictureLayerTilingSet::CoverageIterator iter(
+ tilings_.get(), contents_scale_x(), rect, ideal_contents_scale_);
+ iter;
+ ++iter) {
+ SkColor color;
+ float width;
+ if (*iter && iter->IsReadyToDraw()) {
+ ManagedTileState::TileVersion::Mode mode =
+ iter->GetTileVersionForDrawing().mode();
+ if (mode == ManagedTileState::TileVersion::SOLID_COLOR_MODE) {
+ color = DebugColors::SolidColorTileBorderColor();
+ width = DebugColors::SolidColorTileBorderWidth(layer_tree_impl());
+ } else if (mode == ManagedTileState::TileVersion::PICTURE_PILE_MODE) {
+ color = DebugColors::PictureTileBorderColor();
+ width = DebugColors::PictureTileBorderWidth(layer_tree_impl());
+ } else if (iter->priority(ACTIVE_TREE).resolution == HIGH_RESOLUTION) {
+ color = DebugColors::HighResTileBorderColor();
+ width = DebugColors::HighResTileBorderWidth(layer_tree_impl());
+ } else if (iter->priority(ACTIVE_TREE).resolution == LOW_RESOLUTION) {
+ color = DebugColors::LowResTileBorderColor();
+ width = DebugColors::LowResTileBorderWidth(layer_tree_impl());
+ } else if (iter->contents_scale() > contents_scale_x()) {
+ color = DebugColors::ExtraHighResTileBorderColor();
+ width = DebugColors::ExtraHighResTileBorderWidth(layer_tree_impl());
+ } else {
+ color = DebugColors::ExtraLowResTileBorderColor();
+ width = DebugColors::ExtraLowResTileBorderWidth(layer_tree_impl());
+ }
+ } else {
+ color = DebugColors::MissingTileBorderColor();
+ width = DebugColors::MissingTileBorderWidth(layer_tree_impl());
+ }
+
+ scoped_ptr<DebugBorderDrawQuad> debug_border_quad =
+ DebugBorderDrawQuad::Create();
+ gfx::Rect geometry_rect = iter.geometry_rect();
+ debug_border_quad->SetNew(shared_quad_state, geometry_rect, color, width);
+ quad_sink->Append(debug_border_quad.PassAs<DrawQuad>(),
+ append_quads_data);
+ }
+ }
+
+ // Keep track of the tilings that were used so that tilings that are
+ // unused can be considered for removal.
+ std::vector<PictureLayerTiling*> seen_tilings;
+
+ for (PictureLayerTilingSet::CoverageIterator iter(
+ tilings_.get(), contents_scale_x(), rect, ideal_contents_scale_);
+ iter;
+ ++iter) {
+ gfx::Rect geometry_rect = iter.geometry_rect();
+ if (!*iter || !iter->IsReadyToDraw()) {
+ if (DrawCheckerboardForMissingTiles()) {
+ // TODO(enne): Figure out how to show debug "invalidated checker" color
+ scoped_ptr<CheckerboardDrawQuad> quad = CheckerboardDrawQuad::Create();
+ SkColor color = DebugColors::DefaultCheckerboardColor();
+ quad->SetNew(shared_quad_state, geometry_rect, color);
+ if (quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data))
+ append_quads_data->num_missing_tiles++;
+ } else {
+ SkColor color = SafeOpaqueBackgroundColor();
+ scoped_ptr<SolidColorDrawQuad> quad = SolidColorDrawQuad::Create();
+ quad->SetNew(shared_quad_state, geometry_rect, color, false);
+ if (quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data))
+ append_quads_data->num_missing_tiles++;
+ }
+
+ append_quads_data->had_incomplete_tile = true;
+ continue;
+ }
+
+ const ManagedTileState::TileVersion& tile_version =
+ iter->GetTileVersionForDrawing();
+ switch (tile_version.mode()) {
+ case ManagedTileState::TileVersion::RESOURCE_MODE: {
+ gfx::RectF texture_rect = iter.texture_rect();
+ gfx::Rect opaque_rect = iter->opaque_rect();
+ opaque_rect.Intersect(content_rect);
+
+ if (iter->contents_scale() != ideal_contents_scale_)
+ append_quads_data->had_incomplete_tile = true;
+
+ scoped_ptr<TileDrawQuad> quad = TileDrawQuad::Create();
+ quad->SetNew(shared_quad_state,
+ geometry_rect,
+ opaque_rect,
+ tile_version.get_resource_id(),
+ texture_rect,
+ iter.texture_size(),
+ tile_version.contents_swizzled());
+ quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data);
+ break;
+ }
+ case ManagedTileState::TileVersion::PICTURE_PILE_MODE: {
+ gfx::RectF texture_rect = iter.texture_rect();
+ gfx::Rect opaque_rect = iter->opaque_rect();
+ opaque_rect.Intersect(content_rect);
+
+ scoped_ptr<PictureDrawQuad> quad = PictureDrawQuad::Create();
+ quad->SetNew(shared_quad_state,
+ geometry_rect,
+ opaque_rect,
+ texture_rect,
+ iter.texture_size(),
+ // TODO(reveman): This assumes the renderer will use
+ // GL_RGBA as format of temporary resource. The need
+ // to swizzle should instead be determined by the
+ // renderer.
+ !PlatformColor::SameComponentOrder(GL_RGBA),
+ iter->content_rect(),
+ iter->contents_scale(),
+ draw_direct_to_backbuffer,
+ pile_);
+ quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data);
+ break;
+ }
+ case ManagedTileState::TileVersion::SOLID_COLOR_MODE: {
+ scoped_ptr<SolidColorDrawQuad> quad = SolidColorDrawQuad::Create();
+ quad->SetNew(shared_quad_state,
+ geometry_rect,
+ tile_version.get_solid_color(),
+ false);
+ quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data);
+ break;
+ }
+ default:
+ NOTREACHED();
+ }
+
+ if (!seen_tilings.size() || seen_tilings.back() != iter.CurrentTiling())
+ seen_tilings.push_back(iter.CurrentTiling());
+ }
+
+ // Aggressively remove any tilings that are not seen to save memory. Note
+ // that this is at the expense of doing cause more frequent re-painting. A
+ // better scheme would be to maintain a tighter visible_content_rect for the
+ // finer tilings.
+ CleanUpTilingsOnActiveLayer(seen_tilings);
+}
+
+void PictureLayerImpl::UpdateTilePriorities() {
+ if (!tilings_->num_tilings())
+ return;
+
+ double current_frame_time_in_seconds =
+ (layer_tree_impl()->CurrentFrameTimeTicks() -
+ base::TimeTicks()).InSecondsF();
+
+ bool tiling_needs_update = false;
+ for (size_t i = 0; i < tilings_->num_tilings(); ++i) {
+ if (tilings_->tiling_at(i)->NeedsUpdateForFrameAtTime(
+ current_frame_time_in_seconds)) {
+ tiling_needs_update = true;
+ break;
+ }
+ }
+ if (!tiling_needs_update)
+ return;
+
+ // At this point, tile priorities are going to be modified.
+ layer_tree_impl()->WillModifyTilePriorities();
+
+ UpdateLCDTextStatus(can_use_lcd_text());
+
+ gfx::Transform current_screen_space_transform = screen_space_transform();
+
+ gfx::Rect viewport_in_content_space;
+ gfx::Transform screen_to_layer(gfx::Transform::kSkipInitialization);
+ if (screen_space_transform().GetInverse(&screen_to_layer)) {
+ gfx::Rect device_viewport(layer_tree_impl()->device_viewport_size());
+ viewport_in_content_space = gfx::ToEnclosingRect(
+ MathUtil::ProjectClippedRect(screen_to_layer, device_viewport));
+ }
+
+ WhichTree tree =
+ layer_tree_impl()->IsActiveTree() ? ACTIVE_TREE : PENDING_TREE;
+ size_t max_tiles_for_interest_area =
+ layer_tree_impl()->settings().max_tiles_for_interest_area;
+ tilings_->UpdateTilePriorities(
+ tree,
+ layer_tree_impl()->device_viewport_size(),
+ viewport_in_content_space,
+ visible_content_rect(),
+ last_bounds_,
+ bounds(),
+ last_content_scale_,
+ contents_scale_x(),
+ last_screen_space_transform_,
+ current_screen_space_transform,
+ current_frame_time_in_seconds,
+ max_tiles_for_interest_area);
+
+ if (layer_tree_impl()->IsPendingTree())
+ MarkVisibleResourcesAsRequired();
+
+ last_screen_space_transform_ = current_screen_space_transform;
+ last_bounds_ = bounds();
+ last_content_scale_ = contents_scale_x();
+}
+
+void PictureLayerImpl::DidBecomeActive() {
+ LayerImpl::DidBecomeActive();
+ tilings_->DidBecomeActive();
+ layer_tree_impl()->WillModifyTilePriorities();
+}
+
+void PictureLayerImpl::DidBeginTracing() {
+ pile_->DidBeginTracing();
+}
+
+void PictureLayerImpl::DidLoseOutputSurface() {
+ if (tilings_)
+ tilings_->RemoveAllTilings();
+
+ ResetRasterScale();
+}
+
+void PictureLayerImpl::CalculateContentsScale(
+ float ideal_contents_scale,
+ float device_scale_factor,
+ float page_scale_factor,
+ bool animating_transform_to_screen,
+ float* contents_scale_x,
+ float* contents_scale_y,
+ gfx::Size* content_bounds) {
+ if (!CanHaveTilings()) {
+ ideal_page_scale_ = page_scale_factor;
+ ideal_device_scale_ = device_scale_factor;
+ ideal_contents_scale_ = ideal_contents_scale;
+ ideal_source_scale_ =
+ ideal_contents_scale_ / ideal_page_scale_ / ideal_device_scale_;
+ *contents_scale_x = ideal_contents_scale_;
+ *contents_scale_y = ideal_contents_scale_;
+ *content_bounds = gfx::ToCeiledSize(gfx::ScaleSize(bounds(),
+ ideal_contents_scale_,
+ ideal_contents_scale_));
+ return;
+ }
+
+ float min_contents_scale = MinimumContentsScale();
+ DCHECK_GT(min_contents_scale, 0.f);
+ float min_page_scale = layer_tree_impl()->min_page_scale_factor();
+ DCHECK_GT(min_page_scale, 0.f);
+ float min_device_scale = 1.f;
+ float min_source_scale =
+ min_contents_scale / min_page_scale / min_device_scale;
+
+ float ideal_page_scale = page_scale_factor;
+ float ideal_device_scale = device_scale_factor;
+ float ideal_source_scale =
+ ideal_contents_scale / ideal_page_scale / ideal_device_scale;
+
+ ideal_contents_scale_ = std::max(ideal_contents_scale, min_contents_scale);
+ ideal_page_scale_ = ideal_page_scale;
+ ideal_device_scale_ = ideal_device_scale;
+ ideal_source_scale_ = std::max(ideal_source_scale, min_source_scale);
+
+ ManageTilings(animating_transform_to_screen);
+
+ // The content scale and bounds for a PictureLayerImpl is somewhat fictitious.
+ // There are (usually) several tilings at different scales. However, the
+ // content bounds is the (integer!) space in which quads are generated.
+ // In order to guarantee that we can fill this integer space with any set of
+ // tilings (and then map back to floating point texture coordinates), the
+ // contents scale must be at least as large as the largest of the tilings.
+ float max_contents_scale = min_contents_scale;
+ for (size_t i = 0; i < tilings_->num_tilings(); ++i) {
+ const PictureLayerTiling* tiling = tilings_->tiling_at(i);
+ max_contents_scale = std::max(max_contents_scale, tiling->contents_scale());
+ }
+
+ *contents_scale_x = max_contents_scale;
+ *contents_scale_y = max_contents_scale;
+ *content_bounds = gfx::ToCeiledSize(
+ gfx::ScaleSize(bounds(), max_contents_scale, max_contents_scale));
+}
+
+skia::RefPtr<SkPicture> PictureLayerImpl::GetPicture() {
+ return pile_->GetFlattenedPicture();
+}
+
+scoped_refptr<Tile> PictureLayerImpl::CreateTile(PictureLayerTiling* tiling,
+ gfx::Rect content_rect) {
+ if (!pile_->CanRaster(tiling->contents_scale(), content_rect))
+ return scoped_refptr<Tile>();
+
+ return make_scoped_refptr(new Tile(
+ layer_tree_impl()->tile_manager(),
+ pile_.get(),
+ content_rect.size(),
+ content_rect,
+ contents_opaque() ? content_rect : gfx::Rect(),
+ tiling->contents_scale(),
+ id(),
+ layer_tree_impl()->source_frame_number(),
+ is_using_lcd_text_));
+}
+
+void PictureLayerImpl::UpdatePile(Tile* tile) {
+ tile->set_picture_pile(pile_);
+}
+
+const Region* PictureLayerImpl::GetInvalidation() {
+ return &invalidation_;
+}
+
+const PictureLayerTiling* PictureLayerImpl::GetTwinTiling(
+ const PictureLayerTiling* tiling) {
+
+ if (!twin_layer_)
+ return NULL;
+ for (size_t i = 0; i < twin_layer_->tilings_->num_tilings(); ++i)
+ if (twin_layer_->tilings_->tiling_at(i)->contents_scale() ==
+ tiling->contents_scale())
+ return twin_layer_->tilings_->tiling_at(i);
+ return NULL;
+}
+
+gfx::Size PictureLayerImpl::CalculateTileSize(
+ gfx::Size content_bounds) const {
+ if (is_mask_) {
+ int max_size = layer_tree_impl()->MaxTextureSize();
+ return gfx::Size(
+ std::min(max_size, content_bounds.width()),
+ std::min(max_size, content_bounds.height()));
+ }
+
+ int max_texture_size =
+ layer_tree_impl()->resource_provider()->max_texture_size();
+
+ gfx::Size default_tile_size = layer_tree_impl()->settings().default_tile_size;
+ default_tile_size.SetToMin(gfx::Size(max_texture_size, max_texture_size));
+
+ gfx::Size max_untiled_content_size =
+ layer_tree_impl()->settings().max_untiled_layer_size;
+ max_untiled_content_size.SetToMin(
+ gfx::Size(max_texture_size, max_texture_size));
+
+ bool any_dimension_too_large =
+ content_bounds.width() > max_untiled_content_size.width() ||
+ content_bounds.height() > max_untiled_content_size.height();
+
+ bool any_dimension_one_tile =
+ content_bounds.width() <= default_tile_size.width() ||
+ content_bounds.height() <= default_tile_size.height();
+
+ // If long and skinny, tile at the max untiled content size, and clamp
+ // the smaller dimension to the content size, e.g. 1000x12 layer with
+ // 500x500 max untiled size would get 500x12 tiles. Also do this
+ // if the layer is small.
+ if (any_dimension_one_tile || !any_dimension_too_large) {
+ int width =
+ std::min(max_untiled_content_size.width(), content_bounds.width());
+ int height =
+ std::min(max_untiled_content_size.height(), content_bounds.height());
+ // Round width and height up to the closest multiple of 64, or 56 if
+ // we should avoid power-of-two textures. This helps reduce the number
+ // of different textures sizes to help recycling, and also keeps all
+ // textures multiple-of-eight, which is preferred on some drivers (IMG).
+ bool avoid_pow2 =
+ layer_tree_impl()->GetRendererCapabilities().avoid_pow2_textures;
+ int round_up_to = avoid_pow2 ? 56 : 64;
+ width = RoundUp(width, round_up_to);
+ height = RoundUp(height, round_up_to);
+ return gfx::Size(width, height);
+ }
+
+ return default_tile_size;
+}
+
+void PictureLayerImpl::SyncFromActiveLayer() {
+ DCHECK(layer_tree_impl()->IsPendingTree());
+
+ if (twin_layer_)
+ SyncFromActiveLayer(twin_layer_);
+}
+
+void PictureLayerImpl::SyncFromActiveLayer(const PictureLayerImpl* other) {
+ UpdateLCDTextStatus(other->is_using_lcd_text_);
+
+ if (!DrawsContent()) {
+ ResetRasterScale();
+ return;
+ }
+
+ raster_page_scale_ = other->raster_page_scale_;
+ raster_device_scale_ = other->raster_device_scale_;
+ raster_source_scale_ = other->raster_source_scale_;
+ raster_contents_scale_ = other->raster_contents_scale_;
+ low_res_raster_contents_scale_ = other->low_res_raster_contents_scale_;
+
+ // Add synthetic invalidations for any recordings that were dropped. As
+ // tiles are updated to point to this new pile, this will force the dropping
+ // of tiles that can no longer be rastered. This is not ideal, but is a
+ // trade-off for memory (use the same pile as much as possible, by switching
+ // during DidBecomeActive) and for time (don't bother checking every tile
+ // during activation to see if the new pile can still raster it).
+ for (int x = 0; x < pile_->num_tiles_x(); ++x) {
+ for (int y = 0; y < pile_->num_tiles_y(); ++y) {
+ bool previously_had = other->pile_->HasRecordingAt(x, y);
+ bool now_has = pile_->HasRecordingAt(x, y);
+ if (now_has || !previously_had)
+ continue;
+ gfx::Rect layer_rect = pile_->tile_bounds(x, y);
+ invalidation_.Union(layer_rect);
+ }
+ }
+
+ // Union in the other newly exposed regions as invalid.
+ Region difference_region = Region(gfx::Rect(bounds()));
+ difference_region.Subtract(gfx::Rect(other->bounds()));
+ invalidation_.Union(difference_region);
+
+ if (CanHaveTilings()) {
+ // The recycle tree's tiling set is two frames out of date, so it needs to
+ // have both this frame's invalidation and the previous frame's invalidation
+ // (stored on the active layer).
+ Region tiling_invalidation = other->invalidation_;
+ tiling_invalidation.Union(invalidation_);
+ tilings_->SyncTilings(*other->tilings_,
+ bounds(),
+ tiling_invalidation,
+ MinimumContentsScale());
+ } else {
+ tilings_->RemoveAllTilings();
+ }
+}
+
+void PictureLayerImpl::SyncTiling(
+ const PictureLayerTiling* tiling) {
+ if (!CanHaveTilingWithScale(tiling->contents_scale()))
+ return;
+ tilings_->AddTiling(tiling->contents_scale());
+
+ // If this tree needs update draw properties, then the tiling will
+ // get updated prior to drawing or activation. If this tree does not
+ // need update draw properties, then its transforms are up to date and
+ // we can create tiles for this tiling immediately.
+ if (!layer_tree_impl()->needs_update_draw_properties())
+ UpdateTilePriorities();
+}
+
+void PictureLayerImpl::UpdateTwinLayer() {
+ DCHECK(layer_tree_impl()->IsPendingTree());
+
+ twin_layer_ = static_cast<PictureLayerImpl*>(
+ layer_tree_impl()->FindActiveTreeLayerById(id()));
+ if (twin_layer_)
+ twin_layer_->twin_layer_ = this;
+}
+
+void PictureLayerImpl::SetIsMask(bool is_mask) {
+ if (is_mask_ == is_mask)
+ return;
+ is_mask_ = is_mask;
+ if (tilings_)
+ tilings_->RemoveAllTiles();
+}
+
+ResourceProvider::ResourceId PictureLayerImpl::ContentsResourceId() const {
+ gfx::Rect content_rect(content_bounds());
+ float scale = contents_scale_x();
+ PictureLayerTilingSet::CoverageIterator iter(
+ tilings_.get(), scale, content_rect, ideal_contents_scale_);
+
+ // Mask resource not ready yet.
+ if (!iter || !*iter)
+ return 0;
+
+ // Masks only supported if they fit on exactly one tile.
+ if (iter.geometry_rect() != content_rect)
+ return 0;
+
+ const ManagedTileState::TileVersion& tile_version =
+ iter->GetTileVersionForDrawing();
+ if (!tile_version.IsReadyToDraw() ||
+ tile_version.mode() != ManagedTileState::TileVersion::RESOURCE_MODE)
+ return 0;
+
+ return tile_version.get_resource_id();
+}
+
+void PictureLayerImpl::MarkVisibleResourcesAsRequired() const {
+ DCHECK(layer_tree_impl()->IsPendingTree());
+ DCHECK(!layer_tree_impl()->needs_update_draw_properties());
+ DCHECK(ideal_contents_scale_);
+ DCHECK_GT(tilings_->num_tilings(), 0u);
+
+ gfx::Rect rect(visible_content_rect());
+
+ float min_acceptable_scale =
+ std::min(raster_contents_scale_, ideal_contents_scale_);
+
+ if (PictureLayerImpl* twin = twin_layer_) {
+ float twin_min_acceptable_scale =
+ std::min(twin->ideal_contents_scale_, twin->raster_contents_scale_);
+ // Ignore 0 scale in case CalculateContentsScale() has never been
+ // called for active twin.
+ if (twin_min_acceptable_scale != 0.0f) {
+ min_acceptable_scale =
+ std::min(min_acceptable_scale, twin_min_acceptable_scale);
+ }
+ }
+
+ // Mark tiles for activation in two passes. Ready to draw tiles in acceptable
+ // but non-ideal tilings are marked as required for activation, but any
+ // non-ready tiles are not marked as required. From there, any missing holes
+ // will need to be filled in from the high res tiling.
+
+ PictureLayerTiling* high_res = NULL;
+ Region missing_region = rect;
+ for (size_t i = 0; i < tilings_->num_tilings(); ++i) {
+ PictureLayerTiling* tiling = tilings_->tiling_at(i);
+ DCHECK(tiling->has_ever_been_updated());
+
+ if (tiling->contents_scale() < min_acceptable_scale)
+ continue;
+ if (tiling->resolution() == HIGH_RESOLUTION) {
+ DCHECK(!high_res) << "There can only be one high res tiling";
+ high_res = tiling;
+ continue;
+ }
+ for (PictureLayerTiling::CoverageIterator iter(tiling,
+ contents_scale_x(),
+ rect);
+ iter;
+ ++iter) {
+ if (!*iter || !iter->IsReadyToDraw())
+ continue;
+
+ // This iteration is over the visible content rect which is potentially
+ // less conservative than projecting the viewport into the layer.
+ // Ignore tiles that are know to be outside the viewport.
+ if (iter->priority(PENDING_TREE).distance_to_visible_in_pixels != 0)
+ continue;
+
+ missing_region.Subtract(iter.geometry_rect());
+ iter->mark_required_for_activation();
+ }
+ }
+
+ DCHECK(high_res) << "There must be one high res tiling";
+ for (PictureLayerTiling::CoverageIterator iter(high_res,
+ contents_scale_x(),
+ rect);
+ iter;
+ ++iter) {
+ // A null tile (i.e. missing recording) can just be skipped.
+ if (!*iter)
+ continue;
+
+ // This iteration is over the visible content rect which is potentially
+ // less conservative than projecting the viewport into the layer.
+ // Ignore tiles that are know to be outside the viewport.
+ if (iter->priority(PENDING_TREE).distance_to_visible_in_pixels != 0)
+ continue;
+
+ // If the missing region doesn't cover it, this tile is fully
+ // covered by acceptable tiles at other scales.
+ if (!missing_region.Intersects(iter.geometry_rect()))
+ continue;
+
+ iter->mark_required_for_activation();
+ }
+}
+
+PictureLayerTiling* PictureLayerImpl::AddTiling(float contents_scale) {
+ DCHECK(CanHaveTilingWithScale(contents_scale)) <<
+ "contents_scale: " << contents_scale;
+
+ PictureLayerTiling* tiling = tilings_->AddTiling(contents_scale);
+
+ const Region& recorded = pile_->recorded_region();
+ DCHECK(!recorded.IsEmpty());
+
+ if (twin_layer_)
+ twin_layer_->SyncTiling(tiling);
+
+ return tiling;
+}
+
+void PictureLayerImpl::RemoveTiling(float contents_scale) {
+ for (size_t i = 0; i < tilings_->num_tilings(); ++i) {
+ PictureLayerTiling* tiling = tilings_->tiling_at(i);
+ if (tiling->contents_scale() == contents_scale) {
+ tilings_->Remove(tiling);
+ break;
+ }
+ }
+}
+
+namespace {
+
+inline float PositiveRatio(float float1, float float2) {
+ DCHECK_GT(float1, 0);
+ DCHECK_GT(float2, 0);
+ return float1 > float2 ? float1 / float2 : float2 / float1;
+}
+
+inline bool IsCloserToThan(
+ PictureLayerTiling* layer1,
+ PictureLayerTiling* layer2,
+ float contents_scale) {
+ // Absolute value for ratios.
+ float ratio1 = PositiveRatio(layer1->contents_scale(), contents_scale);
+ float ratio2 = PositiveRatio(layer2->contents_scale(), contents_scale);
+ return ratio1 < ratio2;
+}
+
+} // namespace
+
+void PictureLayerImpl::ManageTilings(bool animating_transform_to_screen) {
+ DCHECK(ideal_contents_scale_);
+ DCHECK(ideal_page_scale_);
+ DCHECK(ideal_device_scale_);
+ DCHECK(ideal_source_scale_);
+ DCHECK(CanHaveTilings());
+
+ bool change_target_tiling =
+ raster_page_scale_ == 0.f ||
+ raster_device_scale_ == 0.f ||
+ raster_source_scale_ == 0.f ||
+ raster_contents_scale_ == 0.f ||
+ low_res_raster_contents_scale_ == 0.f ||
+ ShouldAdjustRasterScale(animating_transform_to_screen);
+
+ // Store the value for the next time ShouldAdjustRasterScale is called.
+ raster_source_scale_was_animating_ = animating_transform_to_screen;
+
+ if (!change_target_tiling)
+ return;
+
+ raster_page_scale_ = ideal_page_scale_;
+ raster_device_scale_ = ideal_device_scale_;
+ raster_source_scale_ = ideal_source_scale_;
+
+ CalculateRasterContentsScale(animating_transform_to_screen,
+ &raster_contents_scale_,
+ &low_res_raster_contents_scale_);
+
+ PictureLayerTiling* high_res = NULL;
+ PictureLayerTiling* low_res = NULL;
+
+ PictureLayerTiling* previous_low_res = NULL;
+ for (size_t i = 0; i < tilings_->num_tilings(); ++i) {
+ PictureLayerTiling* tiling = tilings_->tiling_at(i);
+ if (tiling->contents_scale() == raster_contents_scale_)
+ high_res = tiling;
+ if (tiling->contents_scale() == low_res_raster_contents_scale_)
+ low_res = tiling;
+ if (tiling->resolution() == LOW_RESOLUTION)
+ previous_low_res = tiling;
+
+ // Reset all tilings to non-ideal until the end of this function.
+ tiling->set_resolution(NON_IDEAL_RESOLUTION);
+ }
+
+ if (!high_res) {
+ high_res = AddTiling(raster_contents_scale_);
+ if (raster_contents_scale_ == low_res_raster_contents_scale_)
+ low_res = high_res;
+ }
+
+ // Only create new low res tilings when the transform is static. This
+ // prevents wastefully creating a paired low res tiling for every new high res
+ // tiling during a pinch or a CSS animation.
+ bool is_pinching = layer_tree_impl()->PinchGestureActive();
+ if (!is_pinching && !animating_transform_to_screen && !low_res &&
+ low_res != high_res)
+ low_res = AddTiling(low_res_raster_contents_scale_);
+
+ if (high_res)
+ high_res->set_resolution(HIGH_RESOLUTION);
+ if (low_res && low_res != high_res)
+ low_res->set_resolution(LOW_RESOLUTION);
+ else if (!low_res && previous_low_res)
+ previous_low_res->set_resolution(LOW_RESOLUTION);
+}
+
+bool PictureLayerImpl::ShouldAdjustRasterScale(
+ bool animating_transform_to_screen) const {
+ // TODO(danakj): Adjust raster source scale closer to ideal source scale at
+ // a throttled rate. Possibly make use of invalidation_.IsEmpty() on pending
+ // tree. This will allow CSS scale changes to get re-rastered at an
+ // appropriate rate.
+
+ if (raster_source_scale_was_animating_ && !animating_transform_to_screen)
+ return true;
+
+ bool is_pinching = layer_tree_impl()->PinchGestureActive();
+ if (is_pinching && raster_page_scale_) {
+ // If the page scale diverges too far during pinch, change raster target to
+ // the current page scale.
+ float ratio = PositiveRatio(ideal_page_scale_, raster_page_scale_);
+ if (ratio >= kMaxScaleRatioDuringPinch)
+ return true;
+ }
+
+ if (!is_pinching) {
+ // When not pinching, match the ideal page scale factor.
+ if (raster_page_scale_ != ideal_page_scale_)
+ return true;
+ }
+
+ // Always match the ideal device scale factor.
+ if (raster_device_scale_ != ideal_device_scale_)
+ return true;
+
+ return false;
+}
+
+void PictureLayerImpl::CalculateRasterContentsScale(
+ bool animating_transform_to_screen,
+ float* raster_contents_scale,
+ float* low_res_raster_contents_scale) const {
+ *raster_contents_scale = ideal_contents_scale_;
+
+ // Don't allow animating CSS scales to drop below 1. This is needed because
+ // changes in raster source scale aren't handled. See the comment in
+ // ShouldAdjustRasterScale.
+ if (animating_transform_to_screen) {
+ *raster_contents_scale = std::max(
+ *raster_contents_scale, 1.f * ideal_page_scale_ * ideal_device_scale_);
+ }
+
+ // If this layer would only create one tile at this content scale,
+ // don't create a low res tiling.
+ gfx::Size content_bounds =
+ gfx::ToCeiledSize(gfx::ScaleSize(bounds(), *raster_contents_scale));
+ gfx::Size tile_size = CalculateTileSize(content_bounds);
+ if (tile_size.width() >= content_bounds.width() &&
+ tile_size.height() >= content_bounds.height()) {
+ *low_res_raster_contents_scale = *raster_contents_scale;
+ return;
+ }
+
+ float low_res_factor =
+ layer_tree_impl()->settings().low_res_contents_scale_factor;
+ *low_res_raster_contents_scale = std::max(
+ *raster_contents_scale * low_res_factor,
+ MinimumContentsScale());
+}
+
+void PictureLayerImpl::CleanUpTilingsOnActiveLayer(
+ std::vector<PictureLayerTiling*> used_tilings) {
+ DCHECK(layer_tree_impl()->IsActiveTree());
+
+ float min_acceptable_high_res_scale = std::min(
+ raster_contents_scale_, ideal_contents_scale_);
+ float max_acceptable_high_res_scale = std::max(
+ raster_contents_scale_, ideal_contents_scale_);
+
+ PictureLayerImpl* twin = twin_layer_;
+ if (twin) {
+ min_acceptable_high_res_scale = std::min(
+ min_acceptable_high_res_scale,
+ std::min(twin->raster_contents_scale_, twin->ideal_contents_scale_));
+ max_acceptable_high_res_scale = std::max(
+ max_acceptable_high_res_scale,
+ std::max(twin->raster_contents_scale_, twin->ideal_contents_scale_));
+ }
+
+ std::vector<PictureLayerTiling*> to_remove;
+ for (size_t i = 0; i < tilings_->num_tilings(); ++i) {
+ PictureLayerTiling* tiling = tilings_->tiling_at(i);
+
+ // Keep multiple high resolution tilings even if not used to help
+ // activate earlier at non-ideal resolutions.
+ if (tiling->contents_scale() >= min_acceptable_high_res_scale &&
+ tiling->contents_scale() <= max_acceptable_high_res_scale)
+ continue;
+
+ // Low resolution can't activate, so only keep one around.
+ if (tiling->resolution() == LOW_RESOLUTION)
+ continue;
+
+ // Don't remove tilings that are being used (and thus would cause a flash.)
+ if (std::find(used_tilings.begin(), used_tilings.end(), tiling) !=
+ used_tilings.end())
+ continue;
+
+ to_remove.push_back(tiling);
+ }
+
+ for (size_t i = 0; i < to_remove.size(); ++i) {
+ if (twin)
+ twin->RemoveTiling(to_remove[i]->contents_scale());
+ tilings_->Remove(to_remove[i]);
+ }
+}
+
+float PictureLayerImpl::MinimumContentsScale() const {
+ float setting_min = layer_tree_impl()->settings().minimum_contents_scale;
+
+ // If the contents scale is less than 1 / width (also for height),
+ // then it will end up having less than one pixel of content in that
+ // dimension. Bump the minimum contents scale up in this case to prevent
+ // this from happening.
+ int min_dimension = std::min(bounds().width(), bounds().height());
+ if (!min_dimension)
+ return setting_min;
+
+ return std::max(1.f / min_dimension, setting_min);
+}
+
+void PictureLayerImpl::UpdateLCDTextStatus(bool new_status) {
+ // Once this layer is not using lcd text, don't switch back.
+ if (!is_using_lcd_text_)
+ return;
+
+ if (is_using_lcd_text_ == new_status)
+ return;
+
+ is_using_lcd_text_ = new_status;
+ tilings_->SetCanUseLCDText(is_using_lcd_text_);
+}
+
+void PictureLayerImpl::ResetRasterScale() {
+ raster_page_scale_ = 0.f;
+ raster_device_scale_ = 0.f;
+ raster_source_scale_ = 0.f;
+ raster_contents_scale_ = 0.f;
+ low_res_raster_contents_scale_ = 0.f;
+}
+
+bool PictureLayerImpl::CanHaveTilings() const {
+ if (!DrawsContent())
+ return false;
+ if (pile_->recorded_region().IsEmpty())
+ return false;
+ if (draw_properties().can_draw_directly_to_backbuffer &&
+ layer_tree_impl()->settings().force_direct_layer_drawing)
+ return false;
+ return true;
+}
+
+bool PictureLayerImpl::CanHaveTilingWithScale(float contents_scale) const {
+ if (!CanHaveTilings())
+ return false;
+ if (contents_scale < MinimumContentsScale())
+ return false;
+ return true;
+}
+
+void PictureLayerImpl::GetDebugBorderProperties(
+ SkColor* color,
+ float* width) const {
+ *color = DebugColors::TiledContentLayerBorderColor();
+ *width = DebugColors::TiledContentLayerBorderWidth(layer_tree_impl());
+}
+
+void PictureLayerImpl::AsValueInto(base::DictionaryValue* state) const {
+ LayerImpl::AsValueInto(state);
+ state->SetDouble("ideal_contents_scale", ideal_contents_scale_);
+ state->Set("tilings", tilings_->AsValue().release());
+ state->Set("pictures", pile_->AsValue().release());
+ state->Set("invalidation", invalidation_.AsValue().release());
+
+ scoped_ptr<base::ListValue> coverage_tiles(new base::ListValue);
+ for (PictureLayerTilingSet::CoverageIterator iter(tilings_.get(),
+ contents_scale_x(),
+ gfx::Rect(bounds()),
+ ideal_contents_scale_);
+ iter;
+ ++iter) {
+ scoped_ptr<base::DictionaryValue> tile_data(new base::DictionaryValue);
+ tile_data->Set("geometry_rect",
+ MathUtil::AsValue(iter.geometry_rect()).release());
+ if (*iter)
+ tile_data->Set("tile", TracedValue::CreateIDRef(*iter).release());
+
+ coverage_tiles->Append(tile_data.release());
+ }
+ state->Set("coverage_tiles", coverage_tiles.release());
+}
+
+size_t PictureLayerImpl::GPUMemoryUsageInBytes() const {
+ return tilings_->GPUMemoryUsageInBytes();
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/picture_layer_impl.h b/chromium/cc/layers/picture_layer_impl.h
new file mode 100644
index 00000000000..25cc5d0b7e7
--- /dev/null
+++ b/chromium/cc/layers/picture_layer_impl.h
@@ -0,0 +1,134 @@
+// 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.
+
+#ifndef CC_LAYERS_PICTURE_LAYER_IMPL_H_
+#define CC_LAYERS_PICTURE_LAYER_IMPL_H_
+
+#include <string>
+#include <vector>
+
+#include "cc/base/cc_export.h"
+#include "cc/base/scoped_ptr_vector.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/resources/picture_layer_tiling.h"
+#include "cc/resources/picture_layer_tiling_set.h"
+#include "cc/resources/picture_pile_impl.h"
+#include "skia/ext/refptr.h"
+#include "third_party/skia/include/core/SkPicture.h"
+
+namespace cc {
+
+struct AppendQuadsData;
+class QuadSink;
+
+class CC_EXPORT PictureLayerImpl
+ : public LayerImpl,
+ NON_EXPORTED_BASE(public PictureLayerTilingClient) {
+ public:
+ static scoped_ptr<PictureLayerImpl> Create(LayerTreeImpl* tree_impl, int id) {
+ return make_scoped_ptr(new PictureLayerImpl(tree_impl, id));
+ }
+ virtual ~PictureLayerImpl();
+
+ // LayerImpl overrides.
+ virtual const char* LayerTypeAsString() const OVERRIDE;
+ virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl)
+ OVERRIDE;
+ virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE;
+ virtual void AppendQuads(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data) OVERRIDE;
+ virtual void UpdateTilePriorities() OVERRIDE;
+ virtual void DidBecomeActive() OVERRIDE;
+ virtual void DidBeginTracing() OVERRIDE;
+ virtual void DidLoseOutputSurface() OVERRIDE;
+ virtual void CalculateContentsScale(float ideal_contents_scale,
+ float device_scale_factor,
+ float page_scale_factor,
+ bool animating_transform_to_screen,
+ float* contents_scale_x,
+ float* contents_scale_y,
+ gfx::Size* content_bounds) OVERRIDE;
+ virtual skia::RefPtr<SkPicture> GetPicture() OVERRIDE;
+
+ // PictureLayerTilingClient overrides.
+ virtual scoped_refptr<Tile> CreateTile(PictureLayerTiling* tiling,
+ gfx::Rect content_rect) OVERRIDE;
+ virtual void UpdatePile(Tile* tile) OVERRIDE;
+ virtual gfx::Size CalculateTileSize(
+ gfx::Size content_bounds) const OVERRIDE;
+ virtual const Region* GetInvalidation() OVERRIDE;
+ virtual const PictureLayerTiling* GetTwinTiling(
+ const PictureLayerTiling* tiling) OVERRIDE;
+
+ // PushPropertiesTo active tree => pending tree.
+ void SyncFromActiveLayer();
+ void SyncTiling(const PictureLayerTiling* tiling);
+ void UpdateTwinLayer();
+
+ void CreateTilingSetIfNeeded();
+
+ // Mask-related functions
+ void SetIsMask(bool is_mask);
+ virtual ResourceProvider::ResourceId ContentsResourceId() const OVERRIDE;
+
+ virtual size_t GPUMemoryUsageInBytes() const OVERRIDE;
+
+ protected:
+ PictureLayerImpl(LayerTreeImpl* tree_impl, int id);
+ PictureLayerTiling* AddTiling(float contents_scale);
+ void RemoveTiling(float contents_scale);
+ void SyncFromActiveLayer(const PictureLayerImpl* other);
+ void ManageTilings(bool animating_transform_to_screen);
+ virtual bool ShouldAdjustRasterScale(
+ bool animating_transform_to_screen) const;
+ virtual void CalculateRasterContentsScale(
+ bool animating_transform_to_screen,
+ float* raster_contents_scale,
+ float* low_res_raster_contents_scale) const;
+ void CleanUpTilingsOnActiveLayer(
+ std::vector<PictureLayerTiling*> used_tilings);
+ float MinimumContentsScale() const;
+ void UpdateLCDTextStatus(bool new_status);
+ void ResetRasterScale();
+ void MarkVisibleResourcesAsRequired() const;
+
+ bool CanHaveTilings() const;
+ bool CanHaveTilingWithScale(float contents_scale) const;
+
+ virtual void GetDebugBorderProperties(
+ SkColor* color, float* width) const OVERRIDE;
+ virtual void AsValueInto(base::DictionaryValue* dict) const OVERRIDE;
+
+ PictureLayerImpl* twin_layer_;
+
+ scoped_ptr<PictureLayerTilingSet> tilings_;
+ scoped_refptr<PicturePileImpl> pile_;
+ Region invalidation_;
+
+ gfx::Transform last_screen_space_transform_;
+ gfx::Size last_bounds_;
+ float last_content_scale_;
+ bool is_mask_;
+
+ float ideal_page_scale_;
+ float ideal_device_scale_;
+ float ideal_source_scale_;
+ float ideal_contents_scale_;
+
+ float raster_page_scale_;
+ float raster_device_scale_;
+ float raster_source_scale_;
+ float raster_contents_scale_;
+ float low_res_raster_contents_scale_;
+
+ bool raster_source_scale_was_animating_;
+ bool is_using_lcd_text_;
+
+ friend class PictureLayer;
+ DISALLOW_COPY_AND_ASSIGN(PictureLayerImpl);
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_PICTURE_LAYER_IMPL_H_
diff --git a/chromium/cc/layers/picture_layer_impl_unittest.cc b/chromium/cc/layers/picture_layer_impl_unittest.cc
new file mode 100644
index 00000000000..047a529924a
--- /dev/null
+++ b/chromium/cc/layers/picture_layer_impl_unittest.cc
@@ -0,0 +1,1011 @@
+// Copyright 2013 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 "cc/layers/picture_layer_impl.h"
+
+#include <utility>
+
+#include "cc/layers/append_quads_data.h"
+#include "cc/layers/picture_layer.h"
+#include "cc/test/fake_content_layer_client.h"
+#include "cc/test/fake_impl_proxy.h"
+#include "cc/test/fake_layer_tree_host_impl.h"
+#include "cc/test/fake_output_surface.h"
+#include "cc/test/fake_picture_layer_impl.h"
+#include "cc/test/fake_picture_pile_impl.h"
+#include "cc/test/geometry_test_utils.h"
+#include "cc/test/impl_side_painting_settings.h"
+#include "cc/test/mock_quad_culler.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkDevice.h"
+#include "ui/gfx/rect_conversions.h"
+
+namespace cc {
+namespace {
+
+class MockCanvas : public SkCanvas {
+ public:
+ explicit MockCanvas(SkDevice* device) : SkCanvas(device) {}
+
+ virtual void drawRect(const SkRect& rect, const SkPaint& paint) OVERRIDE {
+ // Capture calls before SkCanvas quickReject() kicks in.
+ rects_.push_back(rect);
+ }
+
+ std::vector<SkRect> rects_;
+};
+
+class PictureLayerImplTest : public testing::Test {
+ public:
+ PictureLayerImplTest()
+ : host_impl_(ImplSidePaintingSettings(), &proxy_),
+ id_(7) {
+ host_impl_.InitializeRenderer(CreateFakeOutputSurface());
+ }
+
+ virtual ~PictureLayerImplTest() {
+ }
+
+ void SetupDefaultTrees(gfx::Size layer_bounds) {
+ gfx::Size tile_size(100, 100);
+
+ scoped_refptr<FakePicturePileImpl> pending_pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+ scoped_refptr<FakePicturePileImpl> active_pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+
+ SetupTrees(pending_pile, active_pile);
+ }
+
+ void SetupTrees(
+ scoped_refptr<PicturePileImpl> pending_pile,
+ scoped_refptr<PicturePileImpl> active_pile) {
+ SetupPendingTree(active_pile);
+ host_impl_.ActivatePendingTree();
+
+ active_layer_ = static_cast<FakePictureLayerImpl*>(
+ host_impl_.active_tree()->LayerById(id_));
+
+ SetupPendingTree(pending_pile);
+ pending_layer_->UpdateTwinLayer();
+ }
+
+ void AddDefaultTilingsWithInvalidation(const Region& invalidation) {
+ active_layer_->AddTiling(2.3f);
+ active_layer_->AddTiling(1.0f);
+ active_layer_->AddTiling(0.5f);
+ for (size_t i = 0; i < active_layer_->tilings()->num_tilings(); ++i)
+ active_layer_->tilings()->tiling_at(i)->CreateAllTilesForTesting();
+ pending_layer_->set_invalidation(invalidation);
+ pending_layer_->SyncFromActiveLayer();
+ for (size_t i = 0; i < pending_layer_->tilings()->num_tilings(); ++i)
+ pending_layer_->tilings()->tiling_at(i)->CreateAllTilesForTesting();
+ }
+
+ void SetupPendingTree(
+ scoped_refptr<PicturePileImpl> pile) {
+ host_impl_.CreatePendingTree();
+ LayerTreeImpl* pending_tree = host_impl_.pending_tree();
+ // Clear recycled tree.
+ pending_tree->DetachLayerTree();
+
+ scoped_ptr<FakePictureLayerImpl> pending_layer =
+ FakePictureLayerImpl::CreateWithPile(pending_tree, id_, pile);
+ pending_layer->SetDrawsContent(true);
+ pending_tree->SetRootLayer(pending_layer.PassAs<LayerImpl>());
+
+ pending_layer_ = static_cast<FakePictureLayerImpl*>(
+ host_impl_.pending_tree()->LayerById(id_));
+ }
+
+ static void VerifyAllTilesExistAndHavePile(
+ const PictureLayerTiling* tiling,
+ PicturePileImpl* pile) {
+ for (PictureLayerTiling::CoverageIterator
+ iter(tiling, tiling->contents_scale(), tiling->ContentRect());
+ iter;
+ ++iter) {
+ EXPECT_TRUE(*iter);
+ EXPECT_EQ(pile, iter->picture_pile());
+ }
+ }
+
+ void SetContentsScaleOnBothLayers(float contents_scale,
+ float device_scale_factor,
+ float page_scale_factor,
+ bool animating_transform) {
+ float result_scale_x, result_scale_y;
+ gfx::Size result_bounds;
+ pending_layer_->CalculateContentsScale(
+ contents_scale,
+ device_scale_factor,
+ page_scale_factor,
+ animating_transform,
+ &result_scale_x,
+ &result_scale_y,
+ &result_bounds);
+ active_layer_->CalculateContentsScale(
+ contents_scale,
+ device_scale_factor,
+ page_scale_factor,
+ animating_transform,
+ &result_scale_x,
+ &result_scale_y,
+ &result_bounds);
+ }
+
+ void ResetTilingsAndRasterScales() {
+ pending_layer_->DidLoseOutputSurface();
+ active_layer_->DidLoseOutputSurface();
+ }
+
+ protected:
+ void TestTileGridAlignmentCommon() {
+ // Layer to span 4 raster tiles in x and in y
+ ImplSidePaintingSettings settings;
+ gfx::Size layer_size(
+ settings.default_tile_size.width() * 7 / 2,
+ settings.default_tile_size.height() * 7 / 2);
+
+ scoped_refptr<FakePicturePileImpl> pending_pile =
+ FakePicturePileImpl::CreateFilledPile(layer_size, layer_size);
+ scoped_refptr<FakePicturePileImpl> active_pile =
+ FakePicturePileImpl::CreateFilledPile(layer_size, layer_size);
+
+ SetupTrees(pending_pile, active_pile);
+
+ float result_scale_x, result_scale_y;
+ gfx::Size result_bounds;
+ active_layer_->CalculateContentsScale(
+ 1.f, 1.f, 1.f, false, &result_scale_x, &result_scale_y, &result_bounds);
+
+ // Add 1x1 rects at the centers of each tile, then re-record pile contents
+ active_layer_->tilings()->tiling_at(0)->CreateAllTilesForTesting();
+ std::vector<Tile*> tiles =
+ active_layer_->tilings()->tiling_at(0)->AllTilesForTesting();
+ EXPECT_EQ(16u, tiles.size());
+ std::vector<SkRect> rects;
+ std::vector<Tile*>::const_iterator tile_iter;
+ for (tile_iter = tiles.begin(); tile_iter < tiles.end(); tile_iter++) {
+ gfx::Point tile_center = (*tile_iter)->content_rect().CenterPoint();
+ gfx::Rect rect(tile_center.x(), tile_center.y(), 1, 1);
+ active_pile->add_draw_rect(rect);
+ rects.push_back(SkRect::MakeXYWH(rect.x(), rect.y(), 1, 1));
+ }
+ // Force re-record with newly injected content
+ active_pile->RemoveRecordingAt(0, 0);
+ active_pile->AddRecordingAt(0, 0);
+
+ SkBitmap store;
+ store.setConfig(SkBitmap::kNo_Config, 1000, 1000);
+ SkDevice device(store);
+
+ std::vector<SkRect>::const_iterator rect_iter = rects.begin();
+ for (tile_iter = tiles.begin(); tile_iter < tiles.end(); tile_iter++) {
+ MockCanvas mock_canvas(&device);
+ active_pile->RasterDirect(
+ &mock_canvas, (*tile_iter)->content_rect(), 1.0f, NULL);
+
+ // This test verifies that when drawing the contents of a specific tile
+ // at content scale 1.0, the playback canvas never receives content from
+ // neighboring tiles which indicates that the tile grid embedded in
+ // SkPicture is perfectly aligned with the compositor's tiles.
+ EXPECT_EQ(1u, mock_canvas.rects_.size());
+ EXPECT_RECT_EQ(*rect_iter, mock_canvas.rects_[0]);
+ rect_iter++;
+ }
+ }
+
+ FakeImplProxy proxy_;
+ FakeLayerTreeHostImpl host_impl_;
+ int id_;
+ FakePictureLayerImpl* pending_layer_;
+ FakePictureLayerImpl* active_layer_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PictureLayerImplTest);
+};
+
+TEST_F(PictureLayerImplTest, TileGridAlignment) {
+ host_impl_.SetDeviceScaleFactor(1.f);
+ TestTileGridAlignmentCommon();
+}
+
+TEST_F(PictureLayerImplTest, TileGridAlignmentHiDPI) {
+ host_impl_.SetDeviceScaleFactor(2.f);
+ TestTileGridAlignmentCommon();
+}
+
+TEST_F(PictureLayerImplTest, CloneNoInvalidation) {
+ gfx::Size tile_size(100, 100);
+ gfx::Size layer_bounds(400, 400);
+
+ scoped_refptr<FakePicturePileImpl> pending_pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+ scoped_refptr<FakePicturePileImpl> active_pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+
+ SetupTrees(pending_pile, active_pile);
+
+ Region invalidation;
+ AddDefaultTilingsWithInvalidation(invalidation);
+
+ EXPECT_EQ(pending_layer_->tilings()->num_tilings(),
+ active_layer_->tilings()->num_tilings());
+
+ const PictureLayerTilingSet* tilings = pending_layer_->tilings();
+ EXPECT_GT(tilings->num_tilings(), 0u);
+ for (size_t i = 0; i < tilings->num_tilings(); ++i)
+ VerifyAllTilesExistAndHavePile(tilings->tiling_at(i), active_pile.get());
+}
+
+TEST_F(PictureLayerImplTest, ClonePartialInvalidation) {
+ gfx::Size tile_size(100, 100);
+ gfx::Size layer_bounds(400, 400);
+ gfx::Rect layer_invalidation(150, 200, 30, 180);
+
+ scoped_refptr<FakePicturePileImpl> pending_pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+ scoped_refptr<FakePicturePileImpl> active_pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+
+ SetupTrees(pending_pile, active_pile);
+
+ Region invalidation(layer_invalidation);
+ AddDefaultTilingsWithInvalidation(invalidation);
+
+ const PictureLayerTilingSet* tilings = pending_layer_->tilings();
+ EXPECT_GT(tilings->num_tilings(), 0u);
+ for (size_t i = 0; i < tilings->num_tilings(); ++i) {
+ const PictureLayerTiling* tiling = tilings->tiling_at(i);
+ gfx::Rect content_invalidation = gfx::ScaleToEnclosingRect(
+ layer_invalidation,
+ tiling->contents_scale());
+ for (PictureLayerTiling::CoverageIterator
+ iter(tiling,
+ tiling->contents_scale(),
+ tiling->ContentRect());
+ iter;
+ ++iter) {
+ EXPECT_TRUE(*iter);
+ EXPECT_FALSE(iter.geometry_rect().IsEmpty());
+ if (iter.geometry_rect().Intersects(content_invalidation))
+ EXPECT_EQ(pending_pile, iter->picture_pile());
+ else
+ EXPECT_EQ(active_pile, iter->picture_pile());
+ }
+ }
+}
+
+TEST_F(PictureLayerImplTest, CloneFullInvalidation) {
+ gfx::Size tile_size(90, 80);
+ gfx::Size layer_bounds(300, 500);
+
+ scoped_refptr<FakePicturePileImpl> pending_pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+ scoped_refptr<FakePicturePileImpl> active_pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+
+ SetupTrees(pending_pile, active_pile);
+
+ Region invalidation((gfx::Rect(layer_bounds)));
+ AddDefaultTilingsWithInvalidation(invalidation);
+
+ EXPECT_EQ(pending_layer_->tilings()->num_tilings(),
+ active_layer_->tilings()->num_tilings());
+
+ const PictureLayerTilingSet* tilings = pending_layer_->tilings();
+ EXPECT_GT(tilings->num_tilings(), 0u);
+ for (size_t i = 0; i < tilings->num_tilings(); ++i)
+ VerifyAllTilesExistAndHavePile(tilings->tiling_at(i), pending_pile.get());
+}
+
+TEST_F(PictureLayerImplTest, NoInvalidationBoundsChange) {
+ gfx::Size tile_size(90, 80);
+ gfx::Size active_layer_bounds(300, 500);
+ gfx::Size pending_layer_bounds(400, 800);
+
+ scoped_refptr<FakePicturePileImpl> pending_pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size,
+ pending_layer_bounds);
+ scoped_refptr<FakePicturePileImpl> active_pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, active_layer_bounds);
+
+ SetupTrees(pending_pile, active_pile);
+ pending_layer_->set_fixed_tile_size(gfx::Size(100, 100));
+
+ Region invalidation;
+ AddDefaultTilingsWithInvalidation(invalidation);
+
+ const PictureLayerTilingSet* tilings = pending_layer_->tilings();
+ EXPECT_GT(tilings->num_tilings(), 0u);
+ for (size_t i = 0; i < tilings->num_tilings(); ++i) {
+ const PictureLayerTiling* tiling = tilings->tiling_at(i);
+ gfx::Rect active_content_bounds = gfx::ScaleToEnclosingRect(
+ gfx::Rect(active_layer_bounds),
+ tiling->contents_scale());
+ for (PictureLayerTiling::CoverageIterator
+ iter(tiling,
+ tiling->contents_scale(),
+ tiling->ContentRect());
+ iter;
+ ++iter) {
+ EXPECT_TRUE(*iter);
+ EXPECT_FALSE(iter.geometry_rect().IsEmpty());
+ std::vector<Tile*> active_tiles =
+ active_layer_->tilings()->tiling_at(i)->AllTilesForTesting();
+ std::vector<Tile*> pending_tiles = tiling->AllTilesForTesting();
+ if (iter.geometry_rect().right() >= active_content_bounds.width() ||
+ iter.geometry_rect().bottom() >= active_content_bounds.height() ||
+ active_tiles[0]->content_rect().size() !=
+ pending_tiles[0]->content_rect().size()) {
+ EXPECT_EQ(pending_pile, iter->picture_pile());
+ } else {
+ EXPECT_EQ(active_pile, iter->picture_pile());
+ }
+ }
+ }
+}
+
+TEST_F(PictureLayerImplTest, AddTilesFromNewRecording) {
+ gfx::Size tile_size(400, 400);
+ gfx::Size layer_bounds(1300, 1900);
+
+ scoped_refptr<FakePicturePileImpl> pending_pile =
+ FakePicturePileImpl::CreateEmptyPile(tile_size, layer_bounds);
+ scoped_refptr<FakePicturePileImpl> active_pile =
+ FakePicturePileImpl::CreateEmptyPile(tile_size, layer_bounds);
+
+ // Fill in some of active pile, but more of pending pile.
+ int hole_count = 0;
+ for (int x = 0; x < active_pile->tiling().num_tiles_x(); ++x) {
+ for (int y = 0; y < active_pile->tiling().num_tiles_y(); ++y) {
+ if ((x + y) % 2) {
+ pending_pile->AddRecordingAt(x, y);
+ active_pile->AddRecordingAt(x, y);
+ } else {
+ hole_count++;
+ if (hole_count % 2)
+ pending_pile->AddRecordingAt(x, y);
+ }
+ }
+ }
+
+ SetupTrees(pending_pile, active_pile);
+ Region invalidation;
+ AddDefaultTilingsWithInvalidation(invalidation);
+
+ const PictureLayerTilingSet* tilings = pending_layer_->tilings();
+ EXPECT_GT(tilings->num_tilings(), 0u);
+ for (size_t i = 0; i < tilings->num_tilings(); ++i) {
+ const PictureLayerTiling* tiling = tilings->tiling_at(i);
+
+ for (PictureLayerTiling::CoverageIterator
+ iter(tiling,
+ tiling->contents_scale(),
+ tiling->ContentRect());
+ iter;
+ ++iter) {
+ EXPECT_FALSE(iter.full_tile_geometry_rect().IsEmpty());
+ // Ensure there is a recording for this tile.
+ gfx::Rect layer_rect = gfx::ScaleToEnclosingRect(
+ iter.full_tile_geometry_rect(), 1.f / tiling->contents_scale());
+ layer_rect.Intersect(gfx::Rect(layer_bounds));
+
+ bool in_pending = pending_pile->recorded_region().Contains(layer_rect);
+ bool in_active = active_pile->recorded_region().Contains(layer_rect);
+
+ if (in_pending && !in_active)
+ EXPECT_EQ(pending_pile, iter->picture_pile());
+ else if (in_active)
+ EXPECT_EQ(active_pile, iter->picture_pile());
+ else
+ EXPECT_FALSE(*iter);
+ }
+ }
+}
+
+TEST_F(PictureLayerImplTest, ManageTilingsWithNoRecording) {
+ gfx::Size tile_size(400, 400);
+ gfx::Size layer_bounds(1300, 1900);
+
+ scoped_refptr<FakePicturePileImpl> pending_pile =
+ FakePicturePileImpl::CreateEmptyPile(tile_size, layer_bounds);
+ scoped_refptr<FakePicturePileImpl> active_pile =
+ FakePicturePileImpl::CreateEmptyPile(tile_size, layer_bounds);
+
+ float result_scale_x, result_scale_y;
+ gfx::Size result_bounds;
+
+ SetupTrees(pending_pile, active_pile);
+
+ pending_layer_->CalculateContentsScale(
+ 1.f, 1.f, 1.f, false, &result_scale_x, &result_scale_y, &result_bounds);
+
+ EXPECT_EQ(0u, pending_layer_->tilings()->num_tilings());
+}
+
+TEST_F(PictureLayerImplTest, ManageTilingsCreatesTilings) {
+ gfx::Size tile_size(400, 400);
+ gfx::Size layer_bounds(1300, 1900);
+
+ scoped_refptr<FakePicturePileImpl> pending_pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+ scoped_refptr<FakePicturePileImpl> active_pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+
+ float result_scale_x, result_scale_y;
+ gfx::Size result_bounds;
+
+ SetupTrees(pending_pile, active_pile);
+ EXPECT_EQ(0u, pending_layer_->tilings()->num_tilings());
+
+ float low_res_factor = host_impl_.settings().low_res_contents_scale_factor;
+ EXPECT_LT(low_res_factor, 1.f);
+
+ pending_layer_->CalculateContentsScale(1.3f, // ideal contents scale
+ 1.7f, // device scale
+ 3.2f, // page cale
+ false,
+ &result_scale_x,
+ &result_scale_y,
+ &result_bounds);
+ ASSERT_EQ(2u, pending_layer_->tilings()->num_tilings());
+ EXPECT_FLOAT_EQ(
+ 1.3f,
+ pending_layer_->tilings()->tiling_at(0)->contents_scale());
+ EXPECT_FLOAT_EQ(
+ 1.3f * low_res_factor,
+ pending_layer_->tilings()->tiling_at(1)->contents_scale());
+
+ // If we change the layer's CSS scale factor, then we should not get new
+ // tilings.
+ pending_layer_->CalculateContentsScale(1.8f, // ideal contents scale
+ 1.7f, // device scale
+ 3.2f, // page cale
+ false,
+ &result_scale_x,
+ &result_scale_y,
+ &result_bounds);
+ ASSERT_EQ(2u, pending_layer_->tilings()->num_tilings());
+ EXPECT_FLOAT_EQ(
+ 1.3f,
+ pending_layer_->tilings()->tiling_at(0)->contents_scale());
+ EXPECT_FLOAT_EQ(
+ 1.3f * low_res_factor,
+ pending_layer_->tilings()->tiling_at(1)->contents_scale());
+
+ // If we change the page scale factor, then we should get new tilings.
+ pending_layer_->CalculateContentsScale(1.8f, // ideal contents scale
+ 1.7f, // device scale
+ 2.2f, // page cale
+ false,
+ &result_scale_x,
+ &result_scale_y,
+ &result_bounds);
+ ASSERT_EQ(4u, pending_layer_->tilings()->num_tilings());
+ EXPECT_FLOAT_EQ(
+ 1.8f,
+ pending_layer_->tilings()->tiling_at(0)->contents_scale());
+ EXPECT_FLOAT_EQ(
+ 1.8f * low_res_factor,
+ pending_layer_->tilings()->tiling_at(2)->contents_scale());
+
+ // If we change the device scale factor, then we should get new tilings.
+ pending_layer_->CalculateContentsScale(1.9f, // ideal contents scale
+ 1.4f, // device scale
+ 2.2f, // page cale
+ false,
+ &result_scale_x,
+ &result_scale_y,
+ &result_bounds);
+ ASSERT_EQ(6u, pending_layer_->tilings()->num_tilings());
+ EXPECT_FLOAT_EQ(
+ 1.9f,
+ pending_layer_->tilings()->tiling_at(0)->contents_scale());
+ EXPECT_FLOAT_EQ(
+ 1.9f * low_res_factor,
+ pending_layer_->tilings()->tiling_at(3)->contents_scale());
+
+ // If we change the device scale factor, but end up at the same total scale
+ // factor somehow, then we don't get new tilings.
+ pending_layer_->CalculateContentsScale(1.9f, // ideal contents scale
+ 2.2f, // device scale
+ 1.4f, // page cale
+ false,
+ &result_scale_x,
+ &result_scale_y,
+ &result_bounds);
+ ASSERT_EQ(6u, pending_layer_->tilings()->num_tilings());
+ EXPECT_FLOAT_EQ(
+ 1.9f,
+ pending_layer_->tilings()->tiling_at(0)->contents_scale());
+ EXPECT_FLOAT_EQ(
+ 1.9f * low_res_factor,
+ pending_layer_->tilings()->tiling_at(3)->contents_scale());
+}
+
+TEST_F(PictureLayerImplTest, CleanUpTilings) {
+ gfx::Size tile_size(400, 400);
+ gfx::Size layer_bounds(1300, 1900);
+
+ scoped_refptr<FakePicturePileImpl> pending_pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+ scoped_refptr<FakePicturePileImpl> active_pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+
+ float result_scale_x, result_scale_y;
+ gfx::Size result_bounds;
+ std::vector<PictureLayerTiling*> used_tilings;
+
+ SetupTrees(pending_pile, active_pile);
+ EXPECT_EQ(0u, pending_layer_->tilings()->num_tilings());
+
+ float low_res_factor = host_impl_.settings().low_res_contents_scale_factor;
+ EXPECT_LT(low_res_factor, 1.f);
+
+ float device_scale = 1.7f;
+ float page_scale = 3.2f;
+
+ SetContentsScaleOnBothLayers(1.f, device_scale, page_scale, false);
+ ASSERT_EQ(2u, active_layer_->tilings()->num_tilings());
+
+ // We only have ideal tilings, so they aren't removed.
+ used_tilings.clear();
+ active_layer_->CleanUpTilingsOnActiveLayer(used_tilings);
+ ASSERT_EQ(2u, active_layer_->tilings()->num_tilings());
+
+ // Changing the ideal but not creating new tilings.
+ SetContentsScaleOnBothLayers(1.5f, device_scale, page_scale, false);
+ ASSERT_EQ(2u, active_layer_->tilings()->num_tilings());
+
+ // The tilings are still our target scale, so they aren't removed.
+ used_tilings.clear();
+ active_layer_->CleanUpTilingsOnActiveLayer(used_tilings);
+ ASSERT_EQ(2u, active_layer_->tilings()->num_tilings());
+
+ // Create a 1.2 scale tiling. Now we have 1.0 and 1.2 tilings. Ideal = 1.2.
+ page_scale = 1.2f;
+ SetContentsScaleOnBothLayers(1.2f, device_scale, page_scale, false);
+ ASSERT_EQ(4u, active_layer_->tilings()->num_tilings());
+ EXPECT_FLOAT_EQ(
+ 1.f,
+ active_layer_->tilings()->tiling_at(1)->contents_scale());
+ EXPECT_FLOAT_EQ(
+ 1.f * low_res_factor,
+ active_layer_->tilings()->tiling_at(3)->contents_scale());
+
+ // Mark the non-ideal tilings as used. They won't be removed.
+ used_tilings.clear();
+ used_tilings.push_back(active_layer_->tilings()->tiling_at(1));
+ used_tilings.push_back(active_layer_->tilings()->tiling_at(3));
+ active_layer_->CleanUpTilingsOnActiveLayer(used_tilings);
+ ASSERT_EQ(4u, active_layer_->tilings()->num_tilings());
+
+ // Now move the ideal scale to 0.5. Our target stays 1.2.
+ SetContentsScaleOnBothLayers(0.5f, device_scale, page_scale, false);
+
+ // The high resolution tiling is between target and ideal, so is not
+ // removed. The low res tiling for the old ideal=1.0 scale is removed.
+ used_tilings.clear();
+ active_layer_->CleanUpTilingsOnActiveLayer(used_tilings);
+ ASSERT_EQ(3u, active_layer_->tilings()->num_tilings());
+
+ // Now move the ideal scale to 1.0. Our target stays 1.2.
+ SetContentsScaleOnBothLayers(1.f, device_scale, page_scale, false);
+
+ // All the tilings are between are target and the ideal, so they are not
+ // removed.
+ used_tilings.clear();
+ active_layer_->CleanUpTilingsOnActiveLayer(used_tilings);
+ ASSERT_EQ(3u, active_layer_->tilings()->num_tilings());
+
+ // Now move the ideal scale to 1.1 on the active layer. Our target stays 1.2.
+ active_layer_->CalculateContentsScale(1.1f,
+ device_scale,
+ page_scale,
+ false,
+ &result_scale_x,
+ &result_scale_y,
+ &result_bounds);
+
+ // Because the pending layer's ideal scale is still 1.0, our tilings fall
+ // in the range [1.0,1.2] and are kept.
+ used_tilings.clear();
+ active_layer_->CleanUpTilingsOnActiveLayer(used_tilings);
+ ASSERT_EQ(3u, active_layer_->tilings()->num_tilings());
+
+ // Move the ideal scale on the pending layer to 1.1 as well. Our target stays
+ // 1.2 still.
+ pending_layer_->CalculateContentsScale(1.1f,
+ device_scale,
+ page_scale,
+ false,
+ &result_scale_x,
+ &result_scale_y,
+ &result_bounds);
+
+ // Our 1.0 tiling now falls outside the range between our ideal scale and our
+ // target raster scale. But it is in our used tilings set, so nothing is
+ // deleted.
+ used_tilings.clear();
+ used_tilings.push_back(active_layer_->tilings()->tiling_at(1));
+ active_layer_->CleanUpTilingsOnActiveLayer(used_tilings);
+ ASSERT_EQ(3u, active_layer_->tilings()->num_tilings());
+
+ // If we remove it from our used tilings set, it is outside the range to keep
+ // so it is deleted.
+ used_tilings.clear();
+ active_layer_->CleanUpTilingsOnActiveLayer(used_tilings);
+ ASSERT_EQ(2u, active_layer_->tilings()->num_tilings());
+}
+
+#define EXPECT_BOTH_EQ(expression, x) \
+ do { \
+ EXPECT_EQ(pending_layer_->expression, x); \
+ EXPECT_EQ(active_layer_->expression, x); \
+ } while (false)
+
+TEST_F(PictureLayerImplTest, DontAddLowResDuringAnimation) {
+ // Make sure this layer covers multiple tiles, since otherwise low
+ // res won't get created because it is too small.
+ gfx::Size tile_size(host_impl_.settings().default_tile_size);
+ SetupDefaultTrees(gfx::Size(tile_size.width() + 1, tile_size.height() + 1));
+ // Avoid max untiled layer size heuristics via fixed tile size.
+ pending_layer_->set_fixed_tile_size(tile_size);
+ active_layer_->set_fixed_tile_size(tile_size);
+
+ float low_res_factor = host_impl_.settings().low_res_contents_scale_factor;
+ float contents_scale = 1.f;
+ float device_scale = 1.f;
+ float page_scale = 1.f;
+ bool animating_transform = true;
+
+ // Animating, so don't create low res even if there isn't one already.
+ SetContentsScaleOnBothLayers(
+ contents_scale, device_scale, page_scale, animating_transform);
+ EXPECT_BOTH_EQ(HighResTiling()->contents_scale(), 1.f);
+ EXPECT_BOTH_EQ(num_tilings(), 1u);
+
+ // Stop animating, low res gets created.
+ animating_transform = false;
+ SetContentsScaleOnBothLayers(
+ contents_scale, device_scale, page_scale, animating_transform);
+ EXPECT_BOTH_EQ(HighResTiling()->contents_scale(), 1.f);
+ EXPECT_BOTH_EQ(LowResTiling()->contents_scale(), low_res_factor);
+ EXPECT_BOTH_EQ(num_tilings(), 2u);
+
+ // Page scale animation, new high res, but not new low res because animating.
+ contents_scale = 4.f;
+ page_scale = 4.f;
+ animating_transform = true;
+ SetContentsScaleOnBothLayers(
+ contents_scale, device_scale, page_scale, animating_transform);
+ EXPECT_BOTH_EQ(HighResTiling()->contents_scale(), 4.f);
+ EXPECT_BOTH_EQ(LowResTiling()->contents_scale(), low_res_factor);
+ EXPECT_BOTH_EQ(num_tilings(), 3u);
+
+ // Stop animating, new low res gets created for final page scale.
+ animating_transform = false;
+ SetContentsScaleOnBothLayers(
+ contents_scale, device_scale, page_scale, animating_transform);
+ EXPECT_BOTH_EQ(HighResTiling()->contents_scale(), 4.f);
+ EXPECT_BOTH_EQ(LowResTiling()->contents_scale(), 4.f * low_res_factor);
+ EXPECT_BOTH_EQ(num_tilings(), 4u);
+}
+
+TEST_F(PictureLayerImplTest, DontAddLowResForSmallLayers) {
+ gfx::Size tile_size(host_impl_.settings().default_tile_size);
+ SetupDefaultTrees(tile_size);
+
+ float low_res_factor = host_impl_.settings().low_res_contents_scale_factor;
+ float device_scale = 1.f;
+ float page_scale = 1.f;
+ bool animating_transform = false;
+
+ // Contents exactly fit on one tile at scale 1, no low res.
+ float contents_scale = 1.f;
+ SetContentsScaleOnBothLayers(
+ contents_scale, device_scale, page_scale, animating_transform);
+ EXPECT_BOTH_EQ(HighResTiling()->contents_scale(), contents_scale);
+ EXPECT_BOTH_EQ(num_tilings(), 1u);
+
+ ResetTilingsAndRasterScales();
+
+ // Contents that are smaller than one tile, no low res.
+ contents_scale = 0.123f;
+ SetContentsScaleOnBothLayers(
+ contents_scale, device_scale, page_scale, animating_transform);
+ EXPECT_BOTH_EQ(HighResTiling()->contents_scale(), contents_scale);
+ EXPECT_BOTH_EQ(num_tilings(), 1u);
+
+ ResetTilingsAndRasterScales();
+
+ // Any content bounds that would create more than one tile will
+ // generate a low res tiling.
+ contents_scale = 2.5f;
+ SetContentsScaleOnBothLayers(
+ contents_scale, device_scale, page_scale, animating_transform);
+ EXPECT_BOTH_EQ(HighResTiling()->contents_scale(), contents_scale);
+ EXPECT_BOTH_EQ(LowResTiling()->contents_scale(),
+ contents_scale * low_res_factor);
+ EXPECT_BOTH_EQ(num_tilings(), 2u);
+
+ ResetTilingsAndRasterScales();
+
+ // Mask layers dont create low res since they always fit on one tile.
+ pending_layer_->SetIsMask(true);
+ active_layer_->SetIsMask(true);
+ SetContentsScaleOnBothLayers(
+ contents_scale, device_scale, page_scale, animating_transform);
+ EXPECT_BOTH_EQ(HighResTiling()->contents_scale(), contents_scale);
+ EXPECT_BOTH_EQ(num_tilings(), 1u);
+}
+
+TEST_F(PictureLayerImplTest, DidLoseOutputSurface) {
+ gfx::Size tile_size(400, 400);
+ gfx::Size layer_bounds(1300, 1900);
+
+ scoped_refptr<FakePicturePileImpl> pending_pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+ scoped_refptr<FakePicturePileImpl> active_pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+
+ float result_scale_x, result_scale_y;
+ gfx::Size result_bounds;
+
+ SetupTrees(pending_pile, active_pile);
+ EXPECT_EQ(0u, pending_layer_->tilings()->num_tilings());
+
+ pending_layer_->CalculateContentsScale(1.3f, // ideal contents scale
+ 2.7f, // device scale
+ 3.2f, // page cale
+ false,
+ &result_scale_x,
+ &result_scale_y,
+ &result_bounds);
+ EXPECT_EQ(2u, pending_layer_->tilings()->num_tilings());
+
+ // All tilings should be removed when losing output surface.
+ active_layer_->DidLoseOutputSurface();
+ EXPECT_EQ(0u, active_layer_->tilings()->num_tilings());
+ pending_layer_->DidLoseOutputSurface();
+ EXPECT_EQ(0u, pending_layer_->tilings()->num_tilings());
+
+ // This should create new tilings.
+ pending_layer_->CalculateContentsScale(1.3f, // ideal contents scale
+ 2.7f, // device scale
+ 3.2f, // page cale
+ false,
+ &result_scale_x,
+ &result_scale_y,
+ &result_bounds);
+ EXPECT_EQ(2u, pending_layer_->tilings()->num_tilings());
+}
+
+TEST_F(PictureLayerImplTest, ClampTilesToToMaxTileSize) {
+ // The default max tile size is larger than 400x400.
+ gfx::Size tile_size(400, 400);
+ gfx::Size layer_bounds(5000, 5000);
+
+ scoped_refptr<FakePicturePileImpl> pending_pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+ scoped_refptr<FakePicturePileImpl> active_pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+
+ float result_scale_x, result_scale_y;
+ gfx::Size result_bounds;
+
+ SetupTrees(pending_pile, active_pile);
+ EXPECT_EQ(0u, pending_layer_->tilings()->num_tilings());
+
+ pending_layer_->CalculateContentsScale(
+ 1.f, 1.f, 1.f, false, &result_scale_x, &result_scale_y, &result_bounds);
+ ASSERT_EQ(2u, pending_layer_->tilings()->num_tilings());
+
+ pending_layer_->tilings()->tiling_at(0)->CreateAllTilesForTesting();
+
+ // The default value.
+ EXPECT_EQ(gfx::Size(256, 256).ToString(),
+ host_impl_.settings().default_tile_size.ToString());
+
+ Tile* tile = pending_layer_->tilings()->tiling_at(0)->AllTilesForTesting()[0];
+ EXPECT_EQ(gfx::Size(256, 256).ToString(),
+ tile->content_rect().size().ToString());
+
+ pending_layer_->DidLoseOutputSurface();
+
+ // Change the max texture size on the output surface context.
+ scoped_ptr<TestWebGraphicsContext3D> context =
+ TestWebGraphicsContext3D::Create();
+ context->set_max_texture_size(140);
+ host_impl_.InitializeRenderer(FakeOutputSurface::Create3d(
+ context.PassAs<WebKit::WebGraphicsContext3D>()).PassAs<OutputSurface>());
+
+ pending_layer_->CalculateContentsScale(
+ 1.f, 1.f, 1.f, false, &result_scale_x, &result_scale_y, &result_bounds);
+ ASSERT_EQ(2u, pending_layer_->tilings()->num_tilings());
+
+ pending_layer_->tilings()->tiling_at(0)->CreateAllTilesForTesting();
+
+ // Verify the tiles are not larger than the context's max texture size.
+ tile = pending_layer_->tilings()->tiling_at(0)->AllTilesForTesting()[0];
+ EXPECT_GE(140, tile->content_rect().width());
+ EXPECT_GE(140, tile->content_rect().height());
+}
+
+TEST_F(PictureLayerImplTest, ClampSingleTileToToMaxTileSize) {
+ // The default max tile size is larger than 400x400.
+ gfx::Size tile_size(400, 400);
+ gfx::Size layer_bounds(500, 500);
+
+ scoped_refptr<FakePicturePileImpl> pending_pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+ scoped_refptr<FakePicturePileImpl> active_pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+
+ float result_scale_x, result_scale_y;
+ gfx::Size result_bounds;
+
+ SetupTrees(pending_pile, active_pile);
+ EXPECT_EQ(0u, pending_layer_->tilings()->num_tilings());
+
+ pending_layer_->CalculateContentsScale(
+ 1.f, 1.f, 1.f, false, &result_scale_x, &result_scale_y, &result_bounds);
+ ASSERT_LE(1u, pending_layer_->tilings()->num_tilings());
+
+ pending_layer_->tilings()->tiling_at(0)->CreateAllTilesForTesting();
+
+ // The default value. The layer is smaller than this.
+ EXPECT_EQ(gfx::Size(512, 512).ToString(),
+ host_impl_.settings().max_untiled_layer_size.ToString());
+
+ // There should be a single tile since the layer is small.
+ PictureLayerTiling* high_res_tiling = pending_layer_->tilings()->tiling_at(0);
+ EXPECT_EQ(1u, high_res_tiling->AllTilesForTesting().size());
+
+ pending_layer_->DidLoseOutputSurface();
+
+ // Change the max texture size on the output surface context.
+ scoped_ptr<TestWebGraphicsContext3D> context =
+ TestWebGraphicsContext3D::Create();
+ context->set_max_texture_size(140);
+ host_impl_.InitializeRenderer(FakeOutputSurface::Create3d(
+ context.PassAs<WebKit::WebGraphicsContext3D>()).PassAs<OutputSurface>());
+
+ pending_layer_->CalculateContentsScale(
+ 1.f, 1.f, 1.f, false, &result_scale_x, &result_scale_y, &result_bounds);
+ ASSERT_LE(1u, pending_layer_->tilings()->num_tilings());
+
+ pending_layer_->tilings()->tiling_at(0)->CreateAllTilesForTesting();
+
+ // There should be more than one tile since the max texture size won't cover
+ // the layer.
+ high_res_tiling = pending_layer_->tilings()->tiling_at(0);
+ EXPECT_LT(1u, high_res_tiling->AllTilesForTesting().size());
+
+ // Verify the tiles are not larger than the context's max texture size.
+ Tile* tile = pending_layer_->tilings()->tiling_at(0)->AllTilesForTesting()[0];
+ EXPECT_GE(140, tile->content_rect().width());
+ EXPECT_GE(140, tile->content_rect().height());
+}
+
+TEST_F(PictureLayerImplTest, DisallowTileDrawQuads) {
+ MockQuadCuller quad_culler;
+
+ gfx::Size tile_size(400, 400);
+ gfx::Size layer_bounds(1300, 1900);
+
+ scoped_refptr<FakePicturePileImpl> pending_pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+ scoped_refptr<FakePicturePileImpl> active_pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+
+ SetupTrees(pending_pile, active_pile);
+
+ active_layer_->SetContentBounds(layer_bounds);
+ active_layer_->draw_properties().visible_content_rect =
+ gfx::Rect(layer_bounds);
+
+ gfx::Rect layer_invalidation(150, 200, 30, 180);
+ Region invalidation(layer_invalidation);
+ AddDefaultTilingsWithInvalidation(invalidation);
+
+ AppendQuadsData data;
+ active_layer_->WillDraw(DRAW_MODE_RESOURCELESS_SOFTWARE, NULL);
+ active_layer_->AppendQuads(&quad_culler, &data);
+ active_layer_->DidDraw(NULL);
+
+ ASSERT_EQ(1U, quad_culler.quad_list().size());
+ EXPECT_EQ(DrawQuad::PICTURE_CONTENT, quad_culler.quad_list()[0]->material);
+}
+
+TEST_F(PictureLayerImplTest, MarkRequiredNullTiles) {
+ gfx::Size tile_size(100, 100);
+ gfx::Size layer_bounds(1000, 1000);
+
+ scoped_refptr<FakePicturePileImpl> pending_pile =
+ FakePicturePileImpl::CreateEmptyPile(tile_size, layer_bounds);
+ // Layers with entirely empty piles can't get tilings.
+ pending_pile->AddRecordingAt(0, 0);
+
+ SetupPendingTree(pending_pile);
+
+ ASSERT_TRUE(pending_layer_->CanHaveTilings());
+ pending_layer_->AddTiling(1.0f);
+ pending_layer_->AddTiling(2.0f);
+
+ // It should be safe to call this (and MarkVisibleResourcesAsRequired)
+ // on a layer with no recordings.
+ host_impl_.pending_tree()->UpdateDrawProperties();
+ pending_layer_->MarkVisibleResourcesAsRequired();
+}
+
+TEST_F(PictureLayerImplTest, MarkRequiredOffscreenTiles) {
+ gfx::Size tile_size(100, 100);
+ gfx::Size layer_bounds(200, 100);
+
+ scoped_refptr<FakePicturePileImpl> pending_pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+ SetupPendingTree(pending_pile);
+
+ pending_layer_->set_fixed_tile_size(tile_size);
+ ASSERT_TRUE(pending_layer_->CanHaveTilings());
+ PictureLayerTiling* tiling = pending_layer_->AddTiling(1.f);
+ host_impl_.pending_tree()->UpdateDrawProperties();
+ EXPECT_EQ(tiling->resolution(), HIGH_RESOLUTION);
+
+ // Fake set priorities.
+ int tile_count = 0;
+ for (PictureLayerTiling::CoverageIterator iter(
+ tiling,
+ pending_layer_->contents_scale_x(),
+ gfx::Rect(pending_layer_->visible_content_rect()));
+ iter;
+ ++iter) {
+ if (!*iter)
+ continue;
+ Tile* tile = *iter;
+ TilePriority priority;
+ priority.resolution = HIGH_RESOLUTION;
+ if (++tile_count % 2) {
+ priority.time_to_visible_in_seconds = 0.f;
+ priority.distance_to_visible_in_pixels = 0.f;
+ } else {
+ priority.time_to_visible_in_seconds = 1.f;
+ priority.distance_to_visible_in_pixels = 1.f;
+ }
+ tile->SetPriority(PENDING_TREE, priority);
+ }
+
+ pending_layer_->MarkVisibleResourcesAsRequired();
+
+ int num_visible = 0;
+ int num_offscreen = 0;
+
+ for (PictureLayerTiling::CoverageIterator iter(
+ tiling,
+ pending_layer_->contents_scale_x(),
+ gfx::Rect(pending_layer_->visible_content_rect()));
+ iter;
+ ++iter) {
+ if (!*iter)
+ continue;
+ const Tile* tile = *iter;
+ if (tile->priority(PENDING_TREE).distance_to_visible_in_pixels == 0.f) {
+ EXPECT_TRUE(tile->required_for_activation());
+ num_visible++;
+ } else {
+ EXPECT_FALSE(tile->required_for_activation());
+ num_offscreen++;
+ }
+ }
+
+ EXPECT_GT(num_visible, 0);
+ EXPECT_GT(num_offscreen, 0);
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/layers/quad_sink.h b/chromium/cc/layers/quad_sink.h
new file mode 100644
index 00000000000..041b655b861
--- /dev/null
+++ b/chromium/cc/layers/quad_sink.h
@@ -0,0 +1,36 @@
+// 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.
+
+#ifndef CC_LAYERS_QUAD_SINK_H_
+#define CC_LAYERS_QUAD_SINK_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+
+namespace cc {
+
+class DrawQuad;
+class SharedQuadState;
+
+struct AppendQuadsData;
+
+class CC_EXPORT QuadSink {
+ public:
+ virtual ~QuadSink() {}
+
+ // Call this to add a SharedQuadState before appending quads that refer to it.
+ // Returns a pointer to the given SharedQuadState for convenience, that can be
+ // set on the quads to append.
+ virtual SharedQuadState* UseSharedQuadState(
+ scoped_ptr<SharedQuadState> shared_quad_state) = 0;
+
+ // Returns true if the quad is added to the list, and false if the quad is
+ // entirely culled.
+ virtual bool Append(scoped_ptr<DrawQuad> draw_quad,
+ AppendQuadsData* append_quads_data) = 0;
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_QUAD_SINK_H_
diff --git a/chromium/cc/layers/render_pass_sink.h b/chromium/cc/layers/render_pass_sink.h
new file mode 100644
index 00000000000..725f1dd0902
--- /dev/null
+++ b/chromium/cc/layers/render_pass_sink.h
@@ -0,0 +1,24 @@
+// 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.
+
+#ifndef CC_LAYERS_RENDER_PASS_SINK_H_
+#define CC_LAYERS_RENDER_PASS_SINK_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+
+namespace cc {
+class RenderPass;
+
+class CC_EXPORT RenderPassSink {
+ public:
+ virtual void AppendRenderPass(scoped_ptr<RenderPass> render_pass) = 0;
+
+ protected:
+ virtual ~RenderPassSink() {}
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_RENDER_PASS_SINK_H_
diff --git a/chromium/cc/layers/render_surface.cc b/chromium/cc/layers/render_surface.cc
new file mode 100644
index 00000000000..a17beab9ba7
--- /dev/null
+++ b/chromium/cc/layers/render_surface.cc
@@ -0,0 +1,43 @@
+// Copyright 2010 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 "cc/layers/render_surface.h"
+
+#include "cc/base/math_util.h"
+#include "cc/layers/layer.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+
+RenderSurface::RenderSurface(Layer* owning_layer)
+ : owning_layer_(owning_layer),
+ draw_opacity_(1),
+ draw_opacity_is_animating_(false),
+ target_surface_transforms_are_animating_(false),
+ screen_space_transforms_are_animating_(false),
+ is_clipped_(false),
+ contributes_to_drawn_surface_(false),
+ nearest_ancestor_that_moves_pixels_(NULL) {}
+
+RenderSurface::~RenderSurface() {
+ for (size_t i = 0; i < layer_list_.size(); ++i) {
+ DCHECK(!layer_list_.at(i)->render_surface()) <<
+ "RenderSurfaces should be cleared from the contributing layers " <<
+ "before destroying this surface to avoid leaking a circular " <<
+ "reference on the contributing layer. Probably the " <<
+ "RenderSurfaceLayerList should just be destroyed before destroying " <<
+ "any RenderSurfaces on layers.";
+ }
+}
+
+gfx::RectF RenderSurface::DrawableContentRect() const {
+ gfx::RectF drawable_content_rect =
+ MathUtil::MapClippedRect(draw_transform_, content_rect_);
+ if (owning_layer_->has_replica())
+ drawable_content_rect.Union(
+ MathUtil::MapClippedRect(replica_draw_transform_, content_rect_));
+ return drawable_content_rect;
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/render_surface.h b/chromium/cc/layers/render_surface.h
new file mode 100644
index 00000000000..adce0a8a201
--- /dev/null
+++ b/chromium/cc/layers/render_surface.h
@@ -0,0 +1,150 @@
+// Copyright 2010 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.
+
+
+#ifndef CC_LAYERS_RENDER_SURFACE_H_
+#define CC_LAYERS_RENDER_SURFACE_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "cc/base/cc_export.h"
+#include "cc/layers/layer_lists.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/rect_f.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+
+class Layer;
+
+class CC_EXPORT RenderSurface {
+ public:
+ explicit RenderSurface(Layer* owning_layer);
+ ~RenderSurface();
+
+ // Returns the rect that encloses the RenderSurfaceImpl including any
+ // reflection.
+ gfx::RectF DrawableContentRect() const;
+
+ void SetContentRect(gfx::Rect content_rect) { content_rect_ = content_rect; }
+ gfx::Rect content_rect() const { return content_rect_; }
+
+ void SetDrawOpacity(float opacity) { draw_opacity_ = opacity; }
+ float draw_opacity() const { return draw_opacity_; }
+
+ void SetDrawOpacityIsAnimating(bool draw_opacity_is_animating) {
+ draw_opacity_is_animating_ = draw_opacity_is_animating;
+ }
+ bool draw_opacity_is_animating() const { return draw_opacity_is_animating_; }
+
+ void SetDrawTransform(const gfx::Transform& draw_transform) {
+ draw_transform_ = draw_transform;
+ }
+ const gfx::Transform& draw_transform() const { return draw_transform_; }
+
+ void SetScreenSpaceTransform(const gfx::Transform& screen_space_transform) {
+ screen_space_transform_ = screen_space_transform;
+ }
+ const gfx::Transform& screen_space_transform() const {
+ return screen_space_transform_;
+ }
+
+ void SetReplicaDrawTransform(const gfx::Transform& replica_draw_transform) {
+ replica_draw_transform_ = replica_draw_transform;
+ }
+ const gfx::Transform& replica_draw_transform() const {
+ return replica_draw_transform_;
+ }
+
+ void SetReplicaScreenSpaceTransform(
+ const gfx::Transform& replica_screen_space_transform) {
+ replica_screen_space_transform_ = replica_screen_space_transform;
+ }
+ const gfx::Transform& replica_screen_space_transform() const {
+ return replica_screen_space_transform_;
+ }
+
+ void SetTargetSurfaceTransformsAreAnimating(bool animating) {
+ target_surface_transforms_are_animating_ = animating;
+ }
+ bool target_surface_transforms_are_animating() const {
+ return target_surface_transforms_are_animating_;
+ }
+ void SetScreenSpaceTransformsAreAnimating(bool animating) {
+ screen_space_transforms_are_animating_ = animating;
+ }
+ bool screen_space_transforms_are_animating() const {
+ return screen_space_transforms_are_animating_;
+ }
+
+ bool is_clipped() const { return is_clipped_; }
+ void SetIsClipped(bool is_clipped) { is_clipped_ = is_clipped; }
+
+ gfx::Rect clip_rect() const { return clip_rect_; }
+ void SetClipRect(gfx::Rect clip_rect) { clip_rect_ = clip_rect; }
+
+ // When false, the RenderSurface does not contribute to another target
+ // RenderSurface that is being drawn for the current frame. It could still be
+ // drawn to as a target, but its output will not be a part of any other
+ // surface.
+ bool contributes_to_drawn_surface() const {
+ return contributes_to_drawn_surface_;
+ }
+ void set_contributes_to_drawn_surface(bool contributes_to_drawn_surface) {
+ contributes_to_drawn_surface_ = contributes_to_drawn_surface;
+ }
+
+ RenderSurfaceLayerList& layer_list() { return layer_list_; }
+ // A no-op since DelegatedRendererLayers on the main thread don't have any
+ // RenderPasses so they can't contribute to a surface.
+ void AddContributingDelegatedRenderPassLayer(Layer* layer) {}
+
+ void SetNearestAncestorThatMovesPixels(RenderSurface* surface) {
+ nearest_ancestor_that_moves_pixels_ = surface;
+ }
+ const RenderSurface* nearest_ancestor_that_moves_pixels() const {
+ return nearest_ancestor_that_moves_pixels_;
+ }
+
+ private:
+ friend struct LayerIteratorActions;
+
+ Layer* owning_layer_;
+
+ // Uses this surface's space.
+ gfx::Rect content_rect_;
+
+ float draw_opacity_;
+ bool draw_opacity_is_animating_;
+ gfx::Transform draw_transform_;
+ gfx::Transform screen_space_transform_;
+ gfx::Transform replica_draw_transform_;
+ gfx::Transform replica_screen_space_transform_;
+ bool target_surface_transforms_are_animating_;
+ bool screen_space_transforms_are_animating_;
+
+ bool is_clipped_;
+ bool contributes_to_drawn_surface_;
+
+ // Uses the space of the surface's target surface.
+ gfx::Rect clip_rect_;
+
+ RenderSurfaceLayerList layer_list_;
+
+ // The nearest ancestor target surface that will contain the contents of this
+ // surface, and that is going to move pixels within the surface (such as with
+ // a blur). This can point to itself.
+ RenderSurface* nearest_ancestor_that_moves_pixels_;
+
+ // For LayerIteratorActions
+ int target_render_surface_layer_index_history_;
+ int current_layer_index_history_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderSurface);
+};
+
+} // namespace cc
+#endif // CC_LAYERS_RENDER_SURFACE_H_
diff --git a/chromium/cc/layers/render_surface_impl.cc b/chromium/cc/layers/render_surface_impl.cc
new file mode 100644
index 00000000000..3262e281bbd
--- /dev/null
+++ b/chromium/cc/layers/render_surface_impl.cc
@@ -0,0 +1,236 @@
+// Copyright 2011 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 "cc/layers/render_surface_impl.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "cc/base/math_util.h"
+#include "cc/debug/debug_colors.h"
+#include "cc/layers/delegated_renderer_layer_impl.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/layers/quad_sink.h"
+#include "cc/layers/render_pass_sink.h"
+#include "cc/quads/debug_border_draw_quad.h"
+#include "cc/quads/render_pass.h"
+#include "cc/quads/render_pass_draw_quad.h"
+#include "cc/quads/shared_quad_state.h"
+#include "cc/trees/damage_tracker.h"
+#include "third_party/skia/include/core/SkImageFilter.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+
+RenderSurfaceImpl::RenderSurfaceImpl(LayerImpl* owning_layer)
+ : owning_layer_(owning_layer),
+ surface_property_changed_(false),
+ draw_opacity_(1),
+ draw_opacity_is_animating_(false),
+ target_surface_transforms_are_animating_(false),
+ screen_space_transforms_are_animating_(false),
+ is_clipped_(false),
+ contributes_to_drawn_surface_(false),
+ nearest_ancestor_that_moves_pixels_(NULL),
+ target_render_surface_layer_index_history_(0),
+ current_layer_index_history_(0) {
+ damage_tracker_ = DamageTracker::Create();
+}
+
+RenderSurfaceImpl::~RenderSurfaceImpl() {}
+
+gfx::RectF RenderSurfaceImpl::DrawableContentRect() const {
+ gfx::RectF drawable_content_rect =
+ MathUtil::MapClippedRect(draw_transform_, content_rect_);
+ if (owning_layer_->has_replica()) {
+ drawable_content_rect.Union(
+ MathUtil::MapClippedRect(replica_draw_transform_, content_rect_));
+ }
+
+ return drawable_content_rect;
+}
+
+std::string RenderSurfaceImpl::Name() const {
+ return base::StringPrintf("RenderSurfaceImpl(id=%i,owner=%s)",
+ owning_layer_->id(),
+ owning_layer_->debug_name().data());
+}
+
+int RenderSurfaceImpl::OwningLayerId() const {
+ return owning_layer_ ? owning_layer_->id() : 0;
+}
+
+
+void RenderSurfaceImpl::SetClipRect(gfx::Rect clip_rect) {
+ if (clip_rect_ == clip_rect)
+ return;
+
+ surface_property_changed_ = true;
+ clip_rect_ = clip_rect;
+}
+
+bool RenderSurfaceImpl::ContentsChanged() const {
+ return !damage_tracker_->current_damage_rect().IsEmpty();
+}
+
+void RenderSurfaceImpl::SetContentRect(gfx::Rect content_rect) {
+ if (content_rect_ == content_rect)
+ return;
+
+ surface_property_changed_ = true;
+ content_rect_ = content_rect;
+}
+
+bool RenderSurfaceImpl::SurfacePropertyChanged() const {
+ // Surface property changes are tracked as follows:
+ //
+ // - surface_property_changed_ is flagged when the clip_rect or content_rect
+ // change. As of now, these are the only two properties that can be affected
+ // by descendant layers.
+ //
+ // - all other property changes come from the owning layer (or some ancestor
+ // layer that propagates its change to the owning layer).
+ //
+ DCHECK(owning_layer_);
+ return surface_property_changed_ || owning_layer_->LayerPropertyChanged();
+}
+
+bool RenderSurfaceImpl::SurfacePropertyChangedOnlyFromDescendant() const {
+ return surface_property_changed_ && !owning_layer_->LayerPropertyChanged();
+}
+
+void RenderSurfaceImpl::AddContributingDelegatedRenderPassLayer(
+ LayerImpl* layer) {
+ DCHECK(std::find(layer_list_.begin(), layer_list_.end(), layer) !=
+ layer_list_.end());
+ DelegatedRendererLayerImpl* delegated_renderer_layer =
+ static_cast<DelegatedRendererLayerImpl*>(layer);
+ contributing_delegated_render_pass_layer_list_.push_back(
+ delegated_renderer_layer);
+}
+
+void RenderSurfaceImpl::ClearLayerLists() {
+ layer_list_.clear();
+ contributing_delegated_render_pass_layer_list_.clear();
+}
+
+RenderPass::Id RenderSurfaceImpl::RenderPassId() {
+ int layer_id = owning_layer_->id();
+ int sub_id = 0;
+ DCHECK_GT(layer_id, 0);
+ return RenderPass::Id(layer_id, sub_id);
+}
+
+void RenderSurfaceImpl::AppendRenderPasses(RenderPassSink* pass_sink) {
+ for (size_t i = 0;
+ i < contributing_delegated_render_pass_layer_list_.size();
+ ++i) {
+ DelegatedRendererLayerImpl* delegated_renderer_layer =
+ contributing_delegated_render_pass_layer_list_[i];
+ delegated_renderer_layer->AppendContributingRenderPasses(pass_sink);
+ }
+
+ scoped_ptr<RenderPass> pass = RenderPass::Create();
+ pass->SetNew(RenderPassId(),
+ content_rect_,
+ damage_tracker_->current_damage_rect(),
+ screen_space_transform_);
+ pass_sink->AppendRenderPass(pass.Pass());
+}
+
+void RenderSurfaceImpl::AppendQuads(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data,
+ bool for_replica,
+ RenderPass::Id render_pass_id) {
+ DCHECK(!for_replica || owning_layer_->has_replica());
+
+ const gfx::Transform& draw_transform =
+ for_replica ? replica_draw_transform_ : draw_transform_;
+ SharedQuadState* shared_quad_state =
+ quad_sink->UseSharedQuadState(SharedQuadState::Create());
+ shared_quad_state->SetAll(draw_transform,
+ content_rect_.size(),
+ content_rect_,
+ clip_rect_,
+ is_clipped_,
+ draw_opacity_);
+
+ if (owning_layer_->ShowDebugBorders()) {
+ SkColor color = for_replica ?
+ DebugColors::SurfaceReplicaBorderColor() :
+ DebugColors::SurfaceBorderColor();
+ float width = for_replica ?
+ DebugColors::SurfaceReplicaBorderWidth(
+ owning_layer_->layer_tree_impl()) :
+ DebugColors::SurfaceBorderWidth(
+ owning_layer_->layer_tree_impl());
+ scoped_ptr<DebugBorderDrawQuad> debug_border_quad =
+ DebugBorderDrawQuad::Create();
+ debug_border_quad->SetNew(shared_quad_state, content_rect_, color, width);
+ quad_sink->Append(debug_border_quad.PassAs<DrawQuad>(), append_quads_data);
+ }
+
+ // TODO(shawnsingh): By using the same RenderSurfaceImpl for both the content
+ // and its reflection, it's currently not possible to apply a separate mask to
+ // the reflection layer or correctly handle opacity in reflections (opacity
+ // must be applied after drawing both the layer and its reflection). The
+ // solution is to introduce yet another RenderSurfaceImpl to draw the layer
+ // and its reflection in. For now we only apply a separate reflection mask if
+ // the contents don't have a mask of their own.
+ LayerImpl* mask_layer = owning_layer_->mask_layer();
+ if (mask_layer &&
+ (!mask_layer->DrawsContent() || mask_layer->bounds().IsEmpty()))
+ mask_layer = NULL;
+
+ if (!mask_layer && for_replica) {
+ mask_layer = owning_layer_->replica_layer()->mask_layer();
+ if (mask_layer &&
+ (!mask_layer->DrawsContent() || mask_layer->bounds().IsEmpty()))
+ mask_layer = NULL;
+ }
+
+ gfx::RectF mask_uv_rect(0.f, 0.f, 1.f, 1.f);
+ if (mask_layer) {
+ gfx::Vector2dF owning_layer_draw_scale =
+ MathUtil::ComputeTransform2dScaleComponents(
+ owning_layer_->draw_transform(), 1.f);
+ gfx::SizeF unclipped_mask_target_size = gfx::ScaleSize(
+ owning_layer_->content_bounds(),
+ owning_layer_draw_scale.x(),
+ owning_layer_draw_scale.y());
+
+ float uv_scale_x =
+ content_rect_.width() / unclipped_mask_target_size.width();
+ float uv_scale_y =
+ content_rect_.height() / unclipped_mask_target_size.height();
+
+ mask_uv_rect = gfx::RectF(
+ uv_scale_x * content_rect_.x() / content_rect_.width(),
+ uv_scale_y * content_rect_.y() / content_rect_.height(),
+ uv_scale_x,
+ uv_scale_y);
+ }
+
+ ResourceProvider::ResourceId mask_resource_id =
+ mask_layer ? mask_layer->ContentsResourceId() : 0;
+ gfx::Rect contents_changed_since_last_frame =
+ ContentsChanged() ? content_rect_ : gfx::Rect();
+
+ scoped_ptr<RenderPassDrawQuad> quad = RenderPassDrawQuad::Create();
+ quad->SetNew(shared_quad_state,
+ content_rect_,
+ render_pass_id,
+ for_replica,
+ mask_resource_id,
+ contents_changed_since_last_frame,
+ mask_uv_rect,
+ owning_layer_->filters(),
+ owning_layer_->filter(),
+ owning_layer_->background_filters());
+ quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data);
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/render_surface_impl.h b/chromium/cc/layers/render_surface_impl.h
new file mode 100644
index 00000000000..f887a659394
--- /dev/null
+++ b/chromium/cc/layers/render_surface_impl.h
@@ -0,0 +1,186 @@
+// Copyright 2011 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.
+
+#ifndef CC_LAYERS_RENDER_SURFACE_IMPL_H_
+#define CC_LAYERS_RENDER_SURFACE_IMPL_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/layers/layer_lists.h"
+#include "cc/quads/render_pass.h"
+#include "cc/quads/shared_quad_state.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/rect_f.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+
+class DamageTracker;
+class DelegatedRendererLayerImpl;
+class QuadSink;
+class RenderPassSink;
+class LayerImpl;
+
+struct AppendQuadsData;
+
+class CC_EXPORT RenderSurfaceImpl {
+ public:
+ explicit RenderSurfaceImpl(LayerImpl* owning_layer);
+ virtual ~RenderSurfaceImpl();
+
+ std::string Name() const;
+
+ gfx::PointF ContentRectCenter() const {
+ return gfx::RectF(content_rect_).CenterPoint();
+ }
+
+ // Returns the rect that encloses the RenderSurfaceImpl including any
+ // reflection.
+ gfx::RectF DrawableContentRect() const;
+
+ void SetDrawOpacity(float opacity) { draw_opacity_ = opacity; }
+ float draw_opacity() const { return draw_opacity_; }
+
+ void SetNearestAncestorThatMovesPixels(RenderSurfaceImpl* surface) {
+ nearest_ancestor_that_moves_pixels_ = surface;
+ }
+ const RenderSurfaceImpl* nearest_ancestor_that_moves_pixels() const {
+ return nearest_ancestor_that_moves_pixels_;
+ }
+
+ void SetDrawOpacityIsAnimating(bool draw_opacity_is_animating) {
+ draw_opacity_is_animating_ = draw_opacity_is_animating;
+ }
+ bool draw_opacity_is_animating() const { return draw_opacity_is_animating_; }
+
+ void SetDrawTransform(const gfx::Transform& draw_transform) {
+ draw_transform_ = draw_transform;
+ }
+ const gfx::Transform& draw_transform() const { return draw_transform_; }
+
+ void SetScreenSpaceTransform(const gfx::Transform& screen_space_transform) {
+ screen_space_transform_ = screen_space_transform;
+ }
+ const gfx::Transform& screen_space_transform() const {
+ return screen_space_transform_;
+ }
+
+ void SetReplicaDrawTransform(const gfx::Transform& replica_draw_transform) {
+ replica_draw_transform_ = replica_draw_transform;
+ }
+ const gfx::Transform& replica_draw_transform() const {
+ return replica_draw_transform_;
+ }
+
+ void SetReplicaScreenSpaceTransform(
+ const gfx::Transform& replica_screen_space_transform) {
+ replica_screen_space_transform_ = replica_screen_space_transform;
+ }
+ const gfx::Transform& replica_screen_space_transform() const {
+ return replica_screen_space_transform_;
+ }
+
+ void SetTargetSurfaceTransformsAreAnimating(bool animating) {
+ target_surface_transforms_are_animating_ = animating;
+ }
+ bool target_surface_transforms_are_animating() const {
+ return target_surface_transforms_are_animating_;
+ }
+ void SetScreenSpaceTransformsAreAnimating(bool animating) {
+ screen_space_transforms_are_animating_ = animating;
+ }
+ bool screen_space_transforms_are_animating() const {
+ return screen_space_transforms_are_animating_;
+ }
+
+ void SetIsClipped(bool is_clipped) { is_clipped_ = is_clipped; }
+ bool is_clipped() const { return is_clipped_; }
+
+ void SetClipRect(gfx::Rect clip_rect);
+ gfx::Rect clip_rect() const { return clip_rect_; }
+
+ // When false, the RenderSurface does not contribute to another target
+ // RenderSurface that is being drawn for the current frame. It could still be
+ // drawn to as a target, but its output will not be a part of any other
+ // surface.
+ bool contributes_to_drawn_surface() const {
+ return contributes_to_drawn_surface_;
+ }
+ void set_contributes_to_drawn_surface(bool contributes_to_drawn_surface) {
+ contributes_to_drawn_surface_ = contributes_to_drawn_surface;
+ }
+
+ bool ContentsChanged() const;
+
+ void SetContentRect(gfx::Rect content_rect);
+ gfx::Rect content_rect() const { return content_rect_; }
+
+ LayerImplList& layer_list() { return layer_list_; }
+ void AddContributingDelegatedRenderPassLayer(LayerImpl* layer);
+ void ClearLayerLists();
+
+ int OwningLayerId() const;
+
+ void ResetPropertyChangedFlag() { surface_property_changed_ = false; }
+ bool SurfacePropertyChanged() const;
+ bool SurfacePropertyChangedOnlyFromDescendant() const;
+
+ DamageTracker* damage_tracker() const { return damage_tracker_.get(); }
+
+ RenderPass::Id RenderPassId();
+
+ void AppendRenderPasses(RenderPassSink* pass_sink);
+ void AppendQuads(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data,
+ bool for_replica,
+ RenderPass::Id render_pass_id);
+
+ private:
+ LayerImpl* owning_layer_;
+
+ // Uses this surface's space.
+ gfx::Rect content_rect_;
+ bool surface_property_changed_;
+
+ float draw_opacity_;
+ bool draw_opacity_is_animating_;
+ gfx::Transform draw_transform_;
+ gfx::Transform screen_space_transform_;
+ gfx::Transform replica_draw_transform_;
+ gfx::Transform replica_screen_space_transform_;
+ bool target_surface_transforms_are_animating_;
+ bool screen_space_transforms_are_animating_;
+
+ bool is_clipped_;
+ bool contributes_to_drawn_surface_;
+
+ // Uses the space of the surface's target surface.
+ gfx::Rect clip_rect_;
+
+ LayerImplList layer_list_;
+ std::vector<DelegatedRendererLayerImpl*>
+ contributing_delegated_render_pass_layer_list_;
+
+ // The nearest ancestor target surface that will contain the contents of this
+ // surface, and that is going to move pixels within the surface (such as with
+ // a blur). This can point to itself.
+ RenderSurfaceImpl* nearest_ancestor_that_moves_pixels_;
+
+ scoped_ptr<DamageTracker> damage_tracker_;
+
+ // For LayerIteratorActions
+ int target_render_surface_layer_index_history_;
+ int current_layer_index_history_;
+
+ friend struct LayerIteratorActions;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderSurfaceImpl);
+};
+
+} // namespace cc
+#endif // CC_LAYERS_RENDER_SURFACE_IMPL_H_
diff --git a/chromium/cc/layers/render_surface_unittest.cc b/chromium/cc/layers/render_surface_unittest.cc
new file mode 100644
index 00000000000..70f63f7304c
--- /dev/null
+++ b/chromium/cc/layers/render_surface_unittest.cc
@@ -0,0 +1,179 @@
+// Copyright 2011 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 "cc/base/scoped_ptr_vector.h"
+#include "cc/layers/append_quads_data.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/layers/render_pass_sink.h"
+#include "cc/layers/render_surface_impl.h"
+#include "cc/quads/shared_quad_state.h"
+#include "cc/test/fake_impl_proxy.h"
+#include "cc/test/fake_layer_tree_host_impl.h"
+#include "cc/test/geometry_test_utils.h"
+#include "cc/test/mock_quad_culler.h"
+#include "cc/trees/single_thread_proxy.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+namespace {
+
+#define EXECUTE_AND_VERIFY_SURFACE_CHANGED(code_to_test) \
+ render_surface->ResetPropertyChangedFlag(); \
+ code_to_test; \
+ EXPECT_TRUE(render_surface->SurfacePropertyChanged())
+
+#define EXECUTE_AND_VERIFY_SURFACE_DID_NOT_CHANGE(code_to_test) \
+ render_surface->ResetPropertyChangedFlag(); \
+ code_to_test; \
+ EXPECT_FALSE(render_surface->SurfacePropertyChanged())
+
+TEST(RenderSurfaceTest, VerifySurfaceChangesAreTrackedProperly) {
+ //
+ // This test checks that SurfacePropertyChanged() has the correct behavior.
+ //
+
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ scoped_ptr<LayerImpl> owning_layer =
+ LayerImpl::Create(host_impl.active_tree(), 1);
+ owning_layer->CreateRenderSurface();
+ ASSERT_TRUE(owning_layer->render_surface());
+ RenderSurfaceImpl* render_surface = owning_layer->render_surface();
+ gfx::Rect test_rect(3, 4, 5, 6);
+ owning_layer->ResetAllChangeTrackingForSubtree();
+
+ // Currently, the content_rect, clip_rect, and
+ // owning_layer->layerPropertyChanged() are the only sources of change.
+ EXECUTE_AND_VERIFY_SURFACE_CHANGED(render_surface->SetClipRect(test_rect));
+ EXECUTE_AND_VERIFY_SURFACE_CHANGED(render_surface->SetContentRect(test_rect));
+
+ owning_layer->SetOpacity(0.5f);
+ EXPECT_TRUE(render_surface->SurfacePropertyChanged());
+ owning_layer->ResetAllChangeTrackingForSubtree();
+
+ // Setting the surface properties to the same values again should not be
+ // considered "change".
+ EXECUTE_AND_VERIFY_SURFACE_DID_NOT_CHANGE(
+ render_surface->SetClipRect(test_rect));
+ EXECUTE_AND_VERIFY_SURFACE_DID_NOT_CHANGE(
+ render_surface->SetContentRect(test_rect));
+
+ scoped_ptr<LayerImpl> dummy_mask =
+ LayerImpl::Create(host_impl.active_tree(), 2);
+ gfx::Transform dummy_matrix;
+ dummy_matrix.Translate(1.0, 2.0);
+
+ // The rest of the surface properties are either internal and should not cause
+ // change, or they are already accounted for by the
+ // owninglayer->layerPropertyChanged().
+ EXECUTE_AND_VERIFY_SURFACE_DID_NOT_CHANGE(
+ render_surface->SetDrawOpacity(0.5f));
+ EXECUTE_AND_VERIFY_SURFACE_DID_NOT_CHANGE(
+ render_surface->SetDrawTransform(dummy_matrix));
+ EXECUTE_AND_VERIFY_SURFACE_DID_NOT_CHANGE(
+ render_surface->SetReplicaDrawTransform(dummy_matrix));
+ EXECUTE_AND_VERIFY_SURFACE_DID_NOT_CHANGE(render_surface->ClearLayerLists());
+}
+
+TEST(RenderSurfaceTest, SanityCheckSurfaceCreatesCorrectSharedQuadState) {
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ scoped_ptr<LayerImpl> root_layer =
+ LayerImpl::Create(host_impl.active_tree(), 1);
+
+ scoped_ptr<LayerImpl> owning_layer =
+ LayerImpl::Create(host_impl.active_tree(), 2);
+ owning_layer->CreateRenderSurface();
+ ASSERT_TRUE(owning_layer->render_surface());
+ owning_layer->draw_properties().render_target = owning_layer.get();
+ RenderSurfaceImpl* render_surface = owning_layer->render_surface();
+
+ root_layer->AddChild(owning_layer.Pass());
+
+ gfx::Rect content_rect(0, 0, 50, 50);
+ gfx::Rect clip_rect(5, 5, 40, 40);
+ gfx::Transform origin;
+
+ origin.Translate(30, 40);
+
+ render_surface->SetDrawTransform(origin);
+ render_surface->SetContentRect(content_rect);
+ render_surface->SetClipRect(clip_rect);
+ render_surface->SetDrawOpacity(1.f);
+
+ QuadList quad_list;
+ SharedQuadStateList shared_state_list;
+ MockQuadCuller mock_quad_culler(&quad_list, &shared_state_list);
+ AppendQuadsData append_quads_data;
+
+ bool for_replica = false;
+ render_surface->AppendQuads(
+ &mock_quad_culler, &append_quads_data, for_replica, RenderPass::Id(2, 0));
+
+ ASSERT_EQ(1u, shared_state_list.size());
+ SharedQuadState* shared_quad_state = shared_state_list[0];
+
+ EXPECT_EQ(
+ 30.0,
+ shared_quad_state->content_to_target_transform.matrix().getDouble(0, 3));
+ EXPECT_EQ(
+ 40.0,
+ shared_quad_state->content_to_target_transform.matrix().getDouble(1, 3));
+ EXPECT_RECT_EQ(content_rect,
+ gfx::Rect(shared_quad_state->visible_content_rect));
+ EXPECT_EQ(1.f, shared_quad_state->opacity);
+}
+
+class TestRenderPassSink : public RenderPassSink {
+ public:
+ virtual void AppendRenderPass(scoped_ptr<RenderPass> render_pass) OVERRIDE {
+ render_passes_.push_back(render_pass.Pass());
+ }
+
+ const ScopedPtrVector<RenderPass>& RenderPasses() const {
+ return render_passes_;
+ }
+
+ private:
+ ScopedPtrVector<RenderPass> render_passes_;
+};
+
+TEST(RenderSurfaceTest, SanityCheckSurfaceCreatesCorrectRenderPass) {
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ scoped_ptr<LayerImpl> root_layer =
+ LayerImpl::Create(host_impl.active_tree(), 1);
+
+ scoped_ptr<LayerImpl> owning_layer =
+ LayerImpl::Create(host_impl.active_tree(), 2);
+ owning_layer->CreateRenderSurface();
+ ASSERT_TRUE(owning_layer->render_surface());
+ owning_layer->draw_properties().render_target = owning_layer.get();
+ RenderSurfaceImpl* render_surface = owning_layer->render_surface();
+
+ root_layer->AddChild(owning_layer.Pass());
+
+ gfx::Rect content_rect(0, 0, 50, 50);
+ gfx::Transform origin;
+ origin.Translate(30.0, 40.0);
+
+ render_surface->SetScreenSpaceTransform(origin);
+ render_surface->SetContentRect(content_rect);
+
+ TestRenderPassSink pass_sink;
+
+ render_surface->AppendRenderPasses(&pass_sink);
+
+ ASSERT_EQ(1u, pass_sink.RenderPasses().size());
+ RenderPass* pass = pass_sink.RenderPasses()[0];
+
+ EXPECT_EQ(RenderPass::Id(2, 0), pass->id);
+ EXPECT_RECT_EQ(content_rect, pass->output_rect);
+ EXPECT_EQ(origin, pass->transform_to_root_target);
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/layers/scrollbar_layer.cc b/chromium/cc/layers/scrollbar_layer.cc
new file mode 100644
index 00000000000..a2fdd58ee36
--- /dev/null
+++ b/chromium/cc/layers/scrollbar_layer.cc
@@ -0,0 +1,247 @@
+// 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 "cc/layers/scrollbar_layer.h"
+
+#include "base/auto_reset.h"
+#include "base/basictypes.h"
+#include "base/debug/trace_event.h"
+#include "cc/layers/scrollbar_layer_impl.h"
+#include "cc/resources/ui_resource_bitmap.h"
+#include "cc/trees/layer_tree_host.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "skia/ext/platform_canvas.h"
+#include "skia/ext/refptr.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkSize.h"
+#include "ui/gfx/skia_util.h"
+
+namespace cc {
+
+scoped_ptr<LayerImpl> ScrollbarLayer::CreateLayerImpl(
+ LayerTreeImpl* tree_impl) {
+ return ScrollbarLayerImpl::Create(
+ tree_impl, id(), scrollbar_->Orientation()).PassAs<LayerImpl>();
+}
+
+scoped_refptr<ScrollbarLayer> ScrollbarLayer::Create(
+ scoped_ptr<Scrollbar> scrollbar,
+ int scroll_layer_id) {
+ return make_scoped_refptr(
+ new ScrollbarLayer(scrollbar.Pass(), scroll_layer_id));
+}
+
+ScrollbarLayer::ScrollbarLayer(
+ scoped_ptr<Scrollbar> scrollbar,
+ int scroll_layer_id)
+ : scrollbar_(scrollbar.Pass()),
+ scroll_layer_id_(scroll_layer_id) {
+ if (!scrollbar_->IsOverlay())
+ SetShouldScrollOnMainThread(true);
+}
+
+ScrollbarLayer::~ScrollbarLayer() {}
+
+void ScrollbarLayer::SetScrollLayerId(int id) {
+ if (id == scroll_layer_id_)
+ return;
+
+ scroll_layer_id_ = id;
+ SetNeedsFullTreeSync();
+}
+
+bool ScrollbarLayer::OpacityCanAnimateOnImplThread() const {
+ return scrollbar_->IsOverlay();
+}
+
+ScrollbarOrientation ScrollbarLayer::Orientation() const {
+ return scrollbar_->Orientation();
+}
+
+int ScrollbarLayer::MaxTextureSize() {
+ DCHECK(layer_tree_host());
+ return layer_tree_host()->GetRendererCapabilities().max_texture_size;
+}
+
+float ScrollbarLayer::ClampScaleToMaxTextureSize(float scale) {
+ if (layer_tree_host()->settings().solid_color_scrollbars)
+ return scale;
+
+ // If the scaled content_bounds() is bigger than the max texture size of the
+ // device, we need to clamp it by rescaling, since content_bounds() is used
+ // below to set the texture size.
+ gfx::Size scaled_bounds = ComputeContentBoundsForScale(scale, scale);
+ if (scaled_bounds.width() > MaxTextureSize() ||
+ scaled_bounds.height() > MaxTextureSize()) {
+ if (scaled_bounds.width() > scaled_bounds.height())
+ return (MaxTextureSize() - 1) / static_cast<float>(bounds().width());
+ else
+ return (MaxTextureSize() - 1) / static_cast<float>(bounds().height());
+ }
+ return scale;
+}
+
+void ScrollbarLayer::CalculateContentsScale(float ideal_contents_scale,
+ float device_scale_factor,
+ float page_scale_factor,
+ bool animating_transform_to_screen,
+ float* contents_scale_x,
+ float* contents_scale_y,
+ gfx::Size* content_bounds) {
+ ContentsScalingLayer::CalculateContentsScale(
+ ClampScaleToMaxTextureSize(ideal_contents_scale),
+ device_scale_factor,
+ page_scale_factor,
+ animating_transform_to_screen,
+ contents_scale_x,
+ contents_scale_y,
+ content_bounds);
+}
+
+void ScrollbarLayer::PushPropertiesTo(LayerImpl* layer) {
+ ContentsScalingLayer::PushPropertiesTo(layer);
+
+ ScrollbarLayerImpl* scrollbar_layer = static_cast<ScrollbarLayerImpl*>(layer);
+
+ if (layer_tree_host() &&
+ layer_tree_host()->settings().solid_color_scrollbars) {
+ int thickness_override =
+ layer_tree_host()->settings().solid_color_scrollbar_thickness_dip;
+ if (thickness_override != -1) {
+ scrollbar_layer->SetThumbThickness(thickness_override);
+ } else {
+ if (Orientation() == HORIZONTAL)
+ scrollbar_layer->SetThumbThickness(bounds().height());
+ else
+ scrollbar_layer->SetThumbThickness(bounds().width());
+ }
+ } else {
+ scrollbar_layer->SetThumbThickness(thumb_thickness_);
+ }
+ scrollbar_layer->SetThumbLength(thumb_length_);
+ if (Orientation() == HORIZONTAL) {
+ scrollbar_layer->SetTrackStart(track_rect_.x());
+ scrollbar_layer->SetTrackLength(track_rect_.width());
+ } else {
+ scrollbar_layer->SetTrackStart(track_rect_.y());
+ scrollbar_layer->SetTrackLength(track_rect_.height());
+ }
+
+ if (track_resource_.get())
+ scrollbar_layer->set_track_ui_resource_id(track_resource_->id());
+ if (thumb_resource_.get())
+ scrollbar_layer->set_thumb_ui_resource_id(thumb_resource_->id());
+
+ scrollbar_layer->set_is_overlay_scrollbar(scrollbar_->IsOverlay());
+
+ // ScrollbarLayer must push properties every frame. crbug.com/259095
+ needs_push_properties_ = true;
+}
+
+ScrollbarLayer* ScrollbarLayer::ToScrollbarLayer() {
+ return this;
+}
+
+void ScrollbarLayer::SetLayerTreeHost(LayerTreeHost* host) {
+ // When the LTH is set to null or has changed, then this layer should remove
+ // all of its associated resources.
+ if (!host || host != layer_tree_host()) {
+ track_resource_.reset();
+ thumb_resource_.reset();
+ }
+
+ ContentsScalingLayer::SetLayerTreeHost(host);
+}
+
+gfx::Rect ScrollbarLayer::ScrollbarLayerRectToContentRect(
+ gfx::Rect layer_rect) const {
+ // Don't intersect with the bounds as in LayerRectToContentRect() because
+ // layer_rect here might be in coordinates of the containing layer.
+ gfx::Rect expanded_rect = gfx::ScaleToEnclosingRect(
+ layer_rect, contents_scale_y(), contents_scale_y());
+ // We should never return a rect bigger than the content_bounds().
+ gfx::Size clamped_size = expanded_rect.size();
+ clamped_size.SetToMin(content_bounds());
+ expanded_rect.set_size(clamped_size);
+ return expanded_rect;
+}
+
+gfx::Rect ScrollbarLayer::OriginThumbRect() const {
+ gfx::Size thumb_size;
+ if (Orientation() == HORIZONTAL) {
+ thumb_size =
+ gfx::Size(scrollbar_->ThumbLength(), scrollbar_->ThumbThickness());
+ } else {
+ thumb_size =
+ gfx::Size(scrollbar_->ThumbThickness(), scrollbar_->ThumbLength());
+ }
+ return ScrollbarLayerRectToContentRect(gfx::Rect(thumb_size));
+}
+
+bool ScrollbarLayer::Update(ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion) {
+ track_rect_ = scrollbar_->TrackRect();
+ gfx::Rect scaled_track_rect = ScrollbarLayerRectToContentRect(
+ gfx::Rect(scrollbar_->Location(), bounds()));
+
+ if (layer_tree_host()->settings().solid_color_scrollbars ||
+ track_rect_.IsEmpty() || scaled_track_rect.IsEmpty())
+ return false;
+
+ {
+ base::AutoReset<bool> ignore_set_needs_commit(&ignore_set_needs_commit_,
+ true);
+ ContentsScalingLayer::Update(queue, occlusion);
+ }
+
+ track_resource_ = ScopedUIResource::Create(
+ layer_tree_host(), RasterizeScrollbarPart(scaled_track_rect, TRACK));
+ gfx::Rect thumb_rect = OriginThumbRect();
+
+ if (scrollbar_->HasThumb() && !thumb_rect.IsEmpty()) {
+ thumb_thickness_ = scrollbar_->ThumbThickness();
+ thumb_length_ = scrollbar_->ThumbLength();
+ thumb_resource_ = ScopedUIResource::Create(
+ layer_tree_host(), RasterizeScrollbarPart(thumb_rect, THUMB));
+ }
+
+ return true;
+}
+
+scoped_refptr<UIResourceBitmap> ScrollbarLayer::RasterizeScrollbarPart(
+ gfx::Rect rect,
+ ScrollbarPart part) {
+ DCHECK(!layer_tree_host()->settings().solid_color_scrollbars);
+ DCHECK(!rect.size().IsEmpty());
+
+ scoped_refptr<UIResourceBitmap> bitmap =
+ UIResourceBitmap::Create(new uint8_t[rect.width() * rect.height() * 4],
+ UIResourceBitmap::RGBA8,
+ rect.size());
+
+ SkBitmap skbitmap;
+ skbitmap.setConfig(SkBitmap::kARGB_8888_Config, rect.width(), rect.height());
+ skbitmap.setPixels(bitmap->GetPixels());
+
+ SkCanvas skcanvas(skbitmap);
+ skcanvas.translate(SkFloatToScalar(-rect.x()), SkFloatToScalar(-rect.y()));
+ skcanvas.scale(SkFloatToScalar(contents_scale_x()),
+ SkFloatToScalar(contents_scale_y()));
+
+ gfx::Rect layer_rect = gfx::ScaleToEnclosingRect(
+ rect, 1.f / contents_scale_x(), 1.f / contents_scale_y());
+ SkRect layer_skrect = RectToSkRect(layer_rect);
+ SkPaint paint;
+ paint.setAntiAlias(false);
+ paint.setXfermodeMode(SkXfermode::kClear_Mode);
+ skcanvas.drawRect(layer_skrect, paint);
+ skcanvas.clipRect(layer_skrect);
+
+ scrollbar_->PaintPart(&skcanvas, part, layer_rect);
+
+ return bitmap;
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/scrollbar_layer.h b/chromium/cc/layers/scrollbar_layer.h
new file mode 100644
index 00000000000..a162a7f00f5
--- /dev/null
+++ b/chromium/cc/layers/scrollbar_layer.h
@@ -0,0 +1,86 @@
+// 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.
+
+#ifndef CC_LAYERS_SCROLLBAR_LAYER_H_
+#define CC_LAYERS_SCROLLBAR_LAYER_H_
+
+#include "cc/base/cc_export.h"
+#include "cc/input/scrollbar.h"
+#include "cc/layers/contents_scaling_layer.h"
+#include "cc/layers/scrollbar_theme_painter.h"
+#include "cc/resources/layer_updater.h"
+#include "cc/resources/scoped_ui_resource.h"
+
+namespace cc {
+class ScrollbarThemeComposite;
+
+class CC_EXPORT ScrollbarLayer : public ContentsScalingLayer {
+ public:
+ virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl)
+ OVERRIDE;
+
+ static scoped_refptr<ScrollbarLayer> Create(
+ scoped_ptr<Scrollbar> scrollbar,
+ int scroll_layer_id);
+
+ int scroll_layer_id() const { return scroll_layer_id_; }
+ void SetScrollLayerId(int id);
+
+ virtual bool OpacityCanAnimateOnImplThread() const OVERRIDE;
+
+ ScrollbarOrientation Orientation() const;
+
+ // Layer interface
+ virtual bool Update(ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion) OVERRIDE;
+ virtual void SetLayerTreeHost(LayerTreeHost* host) OVERRIDE;
+ virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE;
+ virtual void CalculateContentsScale(float ideal_contents_scale,
+ float device_scale_factor,
+ float page_scale_factor,
+ bool animating_transform_to_screen,
+ float* contents_scale_x,
+ float* contents_scale_y,
+ gfx::Size* content_bounds) OVERRIDE;
+
+ virtual ScrollbarLayer* ToScrollbarLayer() OVERRIDE;
+
+ protected:
+ ScrollbarLayer(scoped_ptr<Scrollbar> scrollbar, int scroll_layer_id);
+ virtual ~ScrollbarLayer();
+
+ // For unit tests
+ UIResourceId track_resource_id() {
+ return track_resource_.get() ? track_resource_->id() : 0;
+ }
+ UIResourceId thumb_resource_id() {
+ return thumb_resource_.get() ? thumb_resource_->id() : 0;
+ }
+
+ private:
+ gfx::Rect ScrollbarLayerRectToContentRect(gfx::Rect layer_rect) const;
+ gfx::Rect OriginThumbRect() const;
+
+ int MaxTextureSize();
+ float ClampScaleToMaxTextureSize(float scale);
+
+ scoped_refptr<UIResourceBitmap> RasterizeScrollbarPart(gfx::Rect rect,
+ ScrollbarPart part);
+
+ scoped_ptr<Scrollbar> scrollbar_;
+
+ int thumb_thickness_;
+ int thumb_length_;
+ gfx::Rect track_rect_;
+ int scroll_layer_id_;
+
+ scoped_ptr<ScopedUIResource> track_resource_;
+ scoped_ptr<ScopedUIResource> thumb_resource_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScrollbarLayer);
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_SCROLLBAR_LAYER_H_
diff --git a/chromium/cc/layers/scrollbar_layer_impl.cc b/chromium/cc/layers/scrollbar_layer_impl.cc
new file mode 100644
index 00000000000..c326d42ff24
--- /dev/null
+++ b/chromium/cc/layers/scrollbar_layer_impl.cc
@@ -0,0 +1,322 @@
+// 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 "cc/layers/scrollbar_layer_impl.h"
+
+#include <algorithm>
+
+#include "cc/animation/scrollbar_animation_controller.h"
+#include "cc/layers/layer.h"
+#include "cc/layers/quad_sink.h"
+#include "cc/quads/solid_color_draw_quad.h"
+#include "cc/quads/texture_draw_quad.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "cc/trees/layer_tree_settings.h"
+#include "ui/gfx/rect_conversions.h"
+
+namespace cc {
+
+scoped_ptr<ScrollbarLayerImpl> ScrollbarLayerImpl::Create(
+ LayerTreeImpl* tree_impl,
+ int id,
+ ScrollbarOrientation orientation) {
+ return make_scoped_ptr(new ScrollbarLayerImpl(tree_impl,
+ id,
+ orientation));
+}
+
+ScrollbarLayerImpl::ScrollbarLayerImpl(
+ LayerTreeImpl* tree_impl,
+ int id,
+ ScrollbarOrientation orientation)
+ : LayerImpl(tree_impl, id),
+ track_ui_resource_id_(0),
+ thumb_ui_resource_id_(0),
+ current_pos_(0.f),
+ maximum_(0),
+ thumb_thickness_(0),
+ thumb_length_(0),
+ track_start_(0),
+ track_length_(0),
+ orientation_(orientation),
+ vertical_adjust_(0.f),
+ visible_to_total_length_ratio_(1.f),
+ scroll_layer_id_(Layer::INVALID_ID),
+ is_overlay_scrollbar_(false) {}
+
+ScrollbarLayerImpl::~ScrollbarLayerImpl() {}
+
+ScrollbarLayerImpl* ScrollbarLayerImpl::ToScrollbarLayer() {
+ return this;
+}
+
+scoped_ptr<LayerImpl> ScrollbarLayerImpl::CreateLayerImpl(
+ LayerTreeImpl* tree_impl) {
+ return ScrollbarLayerImpl::Create(tree_impl,
+ id(),
+ orientation_).PassAs<LayerImpl>();
+}
+
+void ScrollbarLayerImpl::PushPropertiesTo(LayerImpl* layer) {
+ LayerImpl::PushPropertiesTo(layer);
+
+ ScrollbarLayerImpl* scrollbar_layer = static_cast<ScrollbarLayerImpl*>(layer);
+
+ scrollbar_layer->SetThumbThickness(thumb_thickness_);
+ scrollbar_layer->SetThumbLength(thumb_length_);
+ scrollbar_layer->SetTrackStart(track_start_);
+ scrollbar_layer->SetTrackLength(track_length_);
+ scrollbar_layer->set_is_overlay_scrollbar(is_overlay_scrollbar_);
+
+ scrollbar_layer->set_track_ui_resource_id(track_ui_resource_id_);
+ scrollbar_layer->set_thumb_ui_resource_id(thumb_ui_resource_id_);
+}
+
+bool ScrollbarLayerImpl::WillDraw(DrawMode draw_mode,
+ ResourceProvider* resource_provider) {
+ if (draw_mode == DRAW_MODE_RESOURCELESS_SOFTWARE &&
+ !layer_tree_impl()->settings().solid_color_scrollbars)
+ return false;
+ return LayerImpl::WillDraw(draw_mode, resource_provider);
+}
+
+void ScrollbarLayerImpl::AppendQuads(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data) {
+ bool premultipled_alpha = true;
+ bool flipped = false;
+ gfx::PointF uv_top_left(0.f, 0.f);
+ gfx::PointF uv_bottom_right(1.f, 1.f);
+ gfx::Rect bounds_rect(bounds());
+ gfx::Rect content_bounds_rect(content_bounds());
+
+ SharedQuadState* shared_quad_state =
+ quad_sink->UseSharedQuadState(CreateSharedQuadState());
+ AppendDebugBorderQuad(quad_sink, shared_quad_state, append_quads_data);
+
+ gfx::Rect thumb_quad_rect = ComputeThumbQuadRect();
+
+ if (layer_tree_impl()->settings().solid_color_scrollbars) {
+ scoped_ptr<SolidColorDrawQuad> quad = SolidColorDrawQuad::Create();
+ quad->SetNew(shared_quad_state,
+ thumb_quad_rect,
+ layer_tree_impl()->settings().solid_color_scrollbar_color,
+ false);
+ quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data);
+ return;
+ }
+
+ ResourceProvider::ResourceId thumb_resource_id =
+ layer_tree_impl()->ResourceIdForUIResource(thumb_ui_resource_id_);
+ ResourceProvider::ResourceId track_resource_id =
+ layer_tree_impl()->ResourceIdForUIResource(track_ui_resource_id_);
+
+ if (thumb_resource_id && !thumb_quad_rect.IsEmpty()) {
+ gfx::Rect opaque_rect;
+ const float opacity[] = {1.0f, 1.0f, 1.0f, 1.0f};
+ scoped_ptr<TextureDrawQuad> quad = TextureDrawQuad::Create();
+ quad->SetNew(shared_quad_state,
+ thumb_quad_rect,
+ opaque_rect,
+ thumb_resource_id,
+ premultipled_alpha,
+ uv_top_left,
+ uv_bottom_right,
+ SK_ColorTRANSPARENT,
+ opacity,
+ flipped);
+ quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data);
+ }
+
+ gfx::Rect track_quad_rect = content_bounds_rect;
+ if (track_resource_id && !track_quad_rect.IsEmpty()) {
+ gfx::Rect opaque_rect(contents_opaque() ? track_quad_rect : gfx::Rect());
+ const float opacity[] = {1.0f, 1.0f, 1.0f, 1.0f};
+ scoped_ptr<TextureDrawQuad> quad = TextureDrawQuad::Create();
+ quad->SetNew(shared_quad_state,
+ track_quad_rect,
+ opaque_rect,
+ track_resource_id,
+ premultipled_alpha,
+ uv_top_left,
+ uv_bottom_right,
+ SK_ColorTRANSPARENT,
+ opacity,
+ flipped);
+ quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data);
+ }
+}
+
+ScrollbarOrientation ScrollbarLayerImpl::Orientation() const {
+ return orientation_;
+}
+
+float ScrollbarLayerImpl::CurrentPos() const {
+ return current_pos_;
+}
+
+int ScrollbarLayerImpl::Maximum() const {
+ return maximum_;
+}
+
+gfx::Rect ScrollbarLayerImpl::ScrollbarLayerRectToContentRect(
+ gfx::RectF layer_rect) const {
+ // Don't intersect with the bounds as in layerRectToContentRect() because
+ // layer_rect here might be in coordinates of the containing layer.
+ gfx::RectF content_rect = gfx::ScaleRect(layer_rect,
+ contents_scale_x(),
+ contents_scale_y());
+ return gfx::ToEnclosingRect(content_rect);
+}
+
+void ScrollbarLayerImpl::SetThumbThickness(int thumb_thickness) {
+ if (thumb_thickness_ == thumb_thickness)
+ return;
+ thumb_thickness_ = thumb_thickness;
+ NoteLayerPropertyChanged();
+}
+
+void ScrollbarLayerImpl::SetThumbLength(int thumb_length) {
+ if (thumb_length_ == thumb_length)
+ return;
+ thumb_length_ = thumb_length;
+ NoteLayerPropertyChanged();
+}
+void ScrollbarLayerImpl::SetTrackStart(int track_start) {
+ if (track_start_ == track_start)
+ return;
+ track_start_ = track_start;
+ NoteLayerPropertyChanged();
+}
+
+void ScrollbarLayerImpl::SetTrackLength(int track_length) {
+ if (track_length_ == track_length)
+ return;
+ track_length_ = track_length;
+ NoteLayerPropertyChanged();
+}
+
+void ScrollbarLayerImpl::SetVerticalAdjust(float vertical_adjust) {
+ if (vertical_adjust_ == vertical_adjust)
+ return;
+ vertical_adjust_ = vertical_adjust;
+ NoteLayerPropertyChanged();
+}
+
+void ScrollbarLayerImpl::SetVisibleToTotalLengthRatio(float ratio) {
+ if (visible_to_total_length_ratio_ == ratio)
+ return;
+ visible_to_total_length_ratio_ = ratio;
+ NoteLayerPropertyChanged();
+}
+
+void ScrollbarLayerImpl::SetCurrentPos(float current_pos) {
+ if (current_pos_ == current_pos)
+ return;
+ current_pos_ = current_pos;
+ NoteLayerPropertyChanged();
+}
+
+void ScrollbarLayerImpl::SetMaximum(int maximum) {
+ if (maximum_ == maximum)
+ return;
+ maximum_ = maximum;
+ NoteLayerPropertyChanged();
+}
+
+gfx::Rect ScrollbarLayerImpl::ComputeThumbQuadRect() const {
+ // Thumb extent is the length of the thumb in the scrolling direction, thumb
+ // thickness is in the perpendicular direction. Here's an example of a
+ // horizontal scrollbar - inputs are above the scrollbar, computed values
+ // below:
+ //
+ // |<------------------- track_length_ ------------------->|
+ //
+ // |--| <-- start_offset
+ //
+ // +--+----------------------------+------------------+-------+--+
+ // |<|| |##################| ||>|
+ // +--+----------------------------+------------------+-------+--+
+ //
+ // |<- thumb_length ->|
+ //
+ // |<------- thumb_offset -------->|
+ //
+ // For painted, scrollbars, the length is fixed. For solid color scrollbars we
+ // have to compute it. The ratio of the thumb's length to the track's length
+ // is the same as that of the visible viewport to the total viewport, unless
+ // that would make the thumb's length less than its thickness.
+ //
+ // vertical_adjust_ is used when the layer geometry from the main thread is
+ // not in sync with what the user sees. For instance on Android scrolling the
+ // top bar controls out of view reveals more of the page content. We want the
+ // root layer scrollbars to reflect what the user sees even if we haven't
+ // received new layer geometry from the main thread. If the user has scrolled
+ // down by 50px and the initial viewport size was 950px the geometry would
+ // look something like this:
+ //
+ // vertical_adjust_ = 50, scroll position 0, visible ratios 99%
+ // Layer geometry: Desired thumb positions:
+ // +--------------------+-+ +----------------------+ <-- 0px
+ // | |v| | #|
+ // | |e| | #|
+ // | |r| | #|
+ // | |t| | #|
+ // | |i| | #|
+ // | |c| | #|
+ // | |a| | #|
+ // | |l| | #|
+ // | | | | #|
+ // | |l| | #|
+ // | |a| | #|
+ // | |y| | #|
+ // | |e| | #|
+ // | |r| | #|
+ // +--------------------+-+ | #|
+ // | horizontal layer | | | #|
+ // +--------------------+-+ | #| <-- 950px
+ // | | | #|
+ // | | |##################### |
+ // +----------------------+ +----------------------+ <-- 1000px
+ //
+ // The layer geometry is set up for a 950px tall viewport, but the user can
+ // actually see down to 1000px. Thus we have to move the quad for the
+ // horizontal scrollbar down by the vertical_adjust_ factor and lay the
+ // vertical thumb out on a track lengthed by the vertical_adjust_ factor. This
+ // means the quads may extend outside the layer's bounds.
+
+ int thumb_length = thumb_length_;
+ float track_length = track_length_;
+ if (orientation_ == VERTICAL)
+ track_length += vertical_adjust_;
+
+ if (layer_tree_impl()->settings().solid_color_scrollbars) {
+ thumb_length = std::max(
+ static_cast<int>(visible_to_total_length_ratio_ * track_length),
+ thumb_thickness_);
+ }
+
+ // With the length known, we can compute the thumb's position.
+ float clamped_current_pos =
+ std::min(std::max(current_pos_, 0.f), static_cast<float>(maximum_));
+ float ratio = clamped_current_pos / maximum_;
+ float max_offset = track_length - thumb_length;
+ int thumb_offset = static_cast<int>(ratio * max_offset) + track_start_;
+
+ gfx::RectF thumb_rect;
+ if (orientation_ == HORIZONTAL) {
+ thumb_rect = gfx::RectF(thumb_offset, vertical_adjust_,
+ thumb_length, thumb_thickness_);
+ } else {
+ thumb_rect = gfx::RectF(0.f, thumb_offset,
+ thumb_thickness_, thumb_length);
+ }
+
+ return ScrollbarLayerRectToContentRect(thumb_rect);
+}
+
+const char* ScrollbarLayerImpl::LayerTypeAsString() const {
+ return "cc::ScrollbarLayerImpl";
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/scrollbar_layer_impl.h b/chromium/cc/layers/scrollbar_layer_impl.h
new file mode 100644
index 00000000000..86bf25558a0
--- /dev/null
+++ b/chromium/cc/layers/scrollbar_layer_impl.h
@@ -0,0 +1,102 @@
+// 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.
+
+#ifndef CC_LAYERS_SCROLLBAR_LAYER_IMPL_H_
+#define CC_LAYERS_SCROLLBAR_LAYER_IMPL_H_
+
+#include "cc/base/cc_export.h"
+#include "cc/input/scrollbar.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/resources/ui_resource_client.h"
+
+namespace cc {
+
+class LayerTreeImpl;
+class ScrollView;
+
+class CC_EXPORT ScrollbarLayerImpl : public LayerImpl {
+ public:
+ static scoped_ptr<ScrollbarLayerImpl> Create(
+ LayerTreeImpl* tree_impl,
+ int id,
+ ScrollbarOrientation orientation);
+ virtual ~ScrollbarLayerImpl();
+
+ // LayerImpl implementation.
+ virtual ScrollbarLayerImpl* ToScrollbarLayer() OVERRIDE;
+ virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl)
+ OVERRIDE;
+ virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE;
+
+ virtual bool WillDraw(DrawMode draw_mode,
+ ResourceProvider* resource_provider) OVERRIDE;
+ virtual void AppendQuads(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data) OVERRIDE;
+
+ int scroll_layer_id() const { return scroll_layer_id_; }
+ void set_scroll_layer_id(int id) { scroll_layer_id_ = id; }
+
+ ScrollbarOrientation Orientation() const;
+ float CurrentPos() const;
+ int Maximum() const;
+
+ void SetThumbThickness(int thumb_thickness);
+ int thumb_thickness() const { return thumb_thickness_; }
+ void SetThumbLength(int thumb_length);
+ void SetTrackStart(int track_start);
+ void SetTrackLength(int track_length);
+ void SetVerticalAdjust(float vertical_adjust);
+ void set_track_ui_resource_id(UIResourceId uid) {
+ track_ui_resource_id_ = uid;
+ }
+ void set_thumb_ui_resource_id(UIResourceId uid) {
+ thumb_ui_resource_id_ = uid;
+ }
+ void SetVisibleToTotalLengthRatio(float ratio);
+ void set_is_overlay_scrollbar(bool is_overlay_scrollbar) {
+ is_overlay_scrollbar_ = is_overlay_scrollbar;
+ }
+ bool is_overlay_scrollbar() const { return is_overlay_scrollbar_; }
+
+ void SetCurrentPos(float current_pos);
+ void SetMaximum(int maximum);
+
+ gfx::Rect ComputeThumbQuadRect() const;
+
+ protected:
+ ScrollbarLayerImpl(LayerTreeImpl* tree_impl,
+ int id,
+ ScrollbarOrientation orientation);
+
+ private:
+ virtual const char* LayerTypeAsString() const OVERRIDE;
+
+ gfx::Rect ScrollbarLayerRectToContentRect(gfx::RectF layer_rect) const;
+
+ UIResourceId track_ui_resource_id_;
+ UIResourceId thumb_ui_resource_id_;
+
+ float current_pos_;
+ int maximum_;
+ int thumb_thickness_;
+ int thumb_length_;
+ int track_start_;
+ int track_length_;
+ ScrollbarOrientation orientation_;
+
+ // Difference between the clip layer's height and the visible viewport
+ // height (which may differ in the presence of top-controls hiding).
+ float vertical_adjust_;
+
+ float visible_to_total_length_ratio_;
+
+ int scroll_layer_id_;
+
+ bool is_overlay_scrollbar_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScrollbarLayerImpl);
+};
+
+} // namespace cc
+#endif // CC_LAYERS_SCROLLBAR_LAYER_IMPL_H_
diff --git a/chromium/cc/layers/scrollbar_layer_unittest.cc b/chromium/cc/layers/scrollbar_layer_unittest.cc
new file mode 100644
index 00000000000..79cfbb1f26d
--- /dev/null
+++ b/chromium/cc/layers/scrollbar_layer_unittest.cc
@@ -0,0 +1,614 @@
+// 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 "cc/layers/scrollbar_layer.h"
+
+#include "base/containers/hash_tables.h"
+#include "cc/animation/scrollbar_animation_controller.h"
+#include "cc/debug/test_web_graphics_context_3d.h"
+#include "cc/layers/append_quads_data.h"
+#include "cc/layers/scrollbar_layer_impl.h"
+#include "cc/quads/solid_color_draw_quad.h"
+#include "cc/resources/resource_update_queue.h"
+#include "cc/test/fake_impl_proxy.h"
+#include "cc/test/fake_layer_tree_host.h"
+#include "cc/test/fake_layer_tree_host_client.h"
+#include "cc/test/fake_layer_tree_host_impl.h"
+#include "cc/test/fake_scrollbar.h"
+#include "cc/test/fake_scrollbar_layer.h"
+#include "cc/test/geometry_test_utils.h"
+#include "cc/test/layer_tree_test.h"
+#include "cc/test/mock_quad_culler.h"
+#include "cc/trees/layer_tree_host.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "cc/trees/single_thread_proxy.h"
+#include "cc/trees/tree_synchronizer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+LayerImpl* LayerImplForScrollAreaAndScrollbar(
+ FakeLayerTreeHost* host,
+ scoped_ptr<Scrollbar> scrollbar,
+ bool reverse_order) {
+ scoped_refptr<Layer> layer_tree_root = Layer::Create();
+ scoped_refptr<Layer> child1 = Layer::Create();
+ scoped_refptr<Layer> child2 =
+ ScrollbarLayer::Create(scrollbar.Pass(),
+ child1->id());
+ layer_tree_root->AddChild(child1);
+ layer_tree_root->InsertChild(child2, reverse_order ? 0 : 1);
+ host->SetRootLayer(layer_tree_root);
+ return host->CommitAndCreateLayerImplTree();
+}
+
+TEST(ScrollbarLayerTest, ResolveScrollLayerPointer) {
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ scoped_ptr<Scrollbar> scrollbar(new FakeScrollbar);
+ LayerImpl* layer_impl_tree_root =
+ LayerImplForScrollAreaAndScrollbar(host.get(), scrollbar.Pass(), false);
+
+ LayerImpl* cc_child1 = layer_impl_tree_root->children()[0];
+ ScrollbarLayerImpl* cc_child2 = static_cast<ScrollbarLayerImpl*>(
+ layer_impl_tree_root->children()[1]);
+
+ EXPECT_EQ(cc_child1->horizontal_scrollbar_layer(), cc_child2);
+}
+
+TEST(ScrollbarLayerTest, ResolveScrollLayerPointer_ReverseOrder) {
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ scoped_ptr<Scrollbar> scrollbar(new FakeScrollbar);
+ LayerImpl* layer_impl_tree_root =
+ LayerImplForScrollAreaAndScrollbar(host.get(), scrollbar.Pass(), true);
+
+ ScrollbarLayerImpl* cc_child1 = static_cast<ScrollbarLayerImpl*>(
+ layer_impl_tree_root->children()[0]);
+ LayerImpl* cc_child2 = layer_impl_tree_root->children()[1];
+
+ EXPECT_EQ(cc_child2->horizontal_scrollbar_layer(), cc_child1);
+}
+
+TEST(ScrollbarLayerTest, ShouldScrollNonOverlayOnMainThread) {
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+
+ // Create and attach a non-overlay scrollbar.
+ scoped_ptr<Scrollbar> scrollbar(new FakeScrollbar);
+ LayerImpl* layer_impl_tree_root =
+ LayerImplForScrollAreaAndScrollbar(host.get(), scrollbar.Pass(), false);
+ ScrollbarLayerImpl* scrollbar_layer_impl =
+ static_cast<ScrollbarLayerImpl*>(layer_impl_tree_root->children()[1]);
+
+ // When the scrollbar is not an overlay scrollbar, the scroll should be
+ // responded to on the main thread as the compositor does not yet implement
+ // scrollbar scrolling.
+ EXPECT_EQ(InputHandler::ScrollOnMainThread,
+ scrollbar_layer_impl->TryScroll(gfx::Point(0, 0),
+ InputHandler::Gesture));
+
+ // Create and attach an overlay scrollbar.
+ scrollbar.reset(new FakeScrollbar(false, false, true));
+
+ layer_impl_tree_root =
+ LayerImplForScrollAreaAndScrollbar(host.get(), scrollbar.Pass(), false);
+ scrollbar_layer_impl =
+ static_cast<ScrollbarLayerImpl*>(layer_impl_tree_root->children()[1]);
+
+ // The user shouldn't be able to drag an overlay scrollbar and the scroll
+ // may be handled in the compositor.
+ EXPECT_EQ(InputHandler::ScrollIgnored,
+ scrollbar_layer_impl->TryScroll(gfx::Point(0, 0),
+ InputHandler::Gesture));
+}
+
+TEST(ScrollbarLayerTest, ScrollOffsetSynchronization) {
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+
+ scoped_ptr<Scrollbar> scrollbar(new FakeScrollbar);
+ scoped_refptr<Layer> layer_tree_root = Layer::Create();
+ scoped_refptr<Layer> content_layer = Layer::Create();
+ scoped_refptr<Layer> scrollbar_layer =
+ ScrollbarLayer::Create(scrollbar.Pass(),
+ layer_tree_root->id());
+
+ layer_tree_root->SetScrollable(true);
+ layer_tree_root->SetScrollOffset(gfx::Vector2d(10, 20));
+ layer_tree_root->SetMaxScrollOffset(gfx::Vector2d(30, 50));
+ layer_tree_root->SetBounds(gfx::Size(100, 200));
+ content_layer->SetBounds(gfx::Size(100, 200));
+
+ host->SetRootLayer(layer_tree_root);
+ layer_tree_root->AddChild(content_layer);
+ layer_tree_root->AddChild(scrollbar_layer);
+
+ layer_tree_root->SavePaintProperties();
+ content_layer->SavePaintProperties();
+
+ LayerImpl* layer_impl_tree_root = host->CommitAndCreateLayerImplTree();
+
+ ScrollbarLayerImpl* cc_scrollbar_layer =
+ static_cast<ScrollbarLayerImpl*>(layer_impl_tree_root->children()[1]);
+
+ EXPECT_EQ(10.f, cc_scrollbar_layer->CurrentPos());
+ EXPECT_EQ(30, cc_scrollbar_layer->Maximum());
+
+ layer_tree_root->SetScrollOffset(gfx::Vector2d(100, 200));
+ layer_tree_root->SetMaxScrollOffset(gfx::Vector2d(300, 500));
+ layer_tree_root->SetBounds(gfx::Size(1000, 2000));
+ layer_tree_root->SavePaintProperties();
+ content_layer->SetBounds(gfx::Size(1000, 2000));
+ content_layer->SavePaintProperties();
+
+ ScrollbarAnimationController* scrollbar_controller =
+ layer_impl_tree_root->scrollbar_animation_controller();
+ layer_impl_tree_root = host->CommitAndCreateLayerImplTree();
+ EXPECT_EQ(scrollbar_controller,
+ layer_impl_tree_root->scrollbar_animation_controller());
+
+ EXPECT_EQ(100.f, cc_scrollbar_layer->CurrentPos());
+ EXPECT_EQ(300, cc_scrollbar_layer->Maximum());
+
+ layer_impl_tree_root->ScrollBy(gfx::Vector2d(12, 34));
+
+ EXPECT_EQ(112.f, cc_scrollbar_layer->CurrentPos());
+ EXPECT_EQ(300, cc_scrollbar_layer->Maximum());
+}
+
+TEST(ScrollbarLayerTest, SolidColorDrawQuads) {
+ LayerTreeSettings layer_tree_settings;
+ layer_tree_settings.solid_color_scrollbars = true;
+ layer_tree_settings.solid_color_scrollbar_thickness_dip = 3;
+ scoped_ptr<FakeLayerTreeHost> host =
+ FakeLayerTreeHost::Create(layer_tree_settings);
+
+ scoped_ptr<Scrollbar> scrollbar(new FakeScrollbar(false, true, true));
+ LayerImpl* layer_impl_tree_root =
+ LayerImplForScrollAreaAndScrollbar(host.get(), scrollbar.Pass(), false);
+ ScrollbarLayerImpl* scrollbar_layer_impl =
+ static_cast<ScrollbarLayerImpl*>(layer_impl_tree_root->children()[1]);
+ scrollbar_layer_impl->SetThumbThickness(3);
+ scrollbar_layer_impl->SetCurrentPos(10.f);
+ scrollbar_layer_impl->SetMaximum(100);
+ scrollbar_layer_impl->SetTrackLength(100);
+ scrollbar_layer_impl->SetVisibleToTotalLengthRatio(0.4f);
+
+ // Thickness should be overridden to 3.
+ {
+ MockQuadCuller quad_culler;
+ AppendQuadsData data;
+ scrollbar_layer_impl->AppendQuads(&quad_culler, &data);
+
+ const QuadList& quads = quad_culler.quad_list();
+ ASSERT_EQ(1u, quads.size());
+ EXPECT_EQ(DrawQuad::SOLID_COLOR, quads[0]->material);
+ EXPECT_RECT_EQ(gfx::Rect(6, 0, 40, 3), quads[0]->rect);
+ }
+
+ // Contents scale should scale the draw quad.
+ scrollbar_layer_impl->draw_properties().contents_scale_x = 2.f;
+ scrollbar_layer_impl->draw_properties().contents_scale_y = 2.f;
+ {
+ MockQuadCuller quad_culler;
+ AppendQuadsData data;
+ scrollbar_layer_impl->AppendQuads(&quad_culler, &data);
+
+ const QuadList& quads = quad_culler.quad_list();
+ ASSERT_EQ(1u, quads.size());
+ EXPECT_EQ(DrawQuad::SOLID_COLOR, quads[0]->material);
+ EXPECT_RECT_EQ(gfx::Rect(12, 0, 80, 6), quads[0]->rect);
+ }
+ scrollbar_layer_impl->draw_properties().contents_scale_x = 1.f;
+ scrollbar_layer_impl->draw_properties().contents_scale_y = 1.f;
+
+ // For solid color scrollbars, position and size should reflect the
+ // current viewport state.
+ scrollbar_layer_impl->SetVisibleToTotalLengthRatio(0.2f);
+ {
+ MockQuadCuller quad_culler;
+ AppendQuadsData data;
+ scrollbar_layer_impl->AppendQuads(&quad_culler, &data);
+
+ const QuadList& quads = quad_culler.quad_list();
+ ASSERT_EQ(1u, quads.size());
+ EXPECT_EQ(DrawQuad::SOLID_COLOR, quads[0]->material);
+ EXPECT_RECT_EQ(gfx::Rect(8, 0, 20, 3), quads[0]->rect);
+ }
+}
+
+TEST(ScrollbarLayerTest, LayerDrivenSolidColorDrawQuads) {
+ LayerTreeSettings layer_tree_settings;
+ layer_tree_settings.solid_color_scrollbars = true;
+ layer_tree_settings.solid_color_scrollbar_thickness_dip = 3;
+ scoped_ptr<FakeLayerTreeHost> host =
+ FakeLayerTreeHost::Create(layer_tree_settings);
+
+ scoped_ptr<Scrollbar> scrollbar(new FakeScrollbar(false, true, true));
+ LayerImpl* layer_impl_tree_root =
+ LayerImplForScrollAreaAndScrollbar(host.get(), scrollbar.Pass(), false);
+ ScrollbarLayerImpl* scrollbar_layer_impl =
+ static_cast<ScrollbarLayerImpl*>(layer_impl_tree_root->children()[1]);
+
+ scrollbar_layer_impl->SetThumbThickness(3);
+ scrollbar_layer_impl->SetTrackLength(10);
+ scrollbar_layer_impl->SetCurrentPos(4.f);
+ scrollbar_layer_impl->SetMaximum(8);
+
+ layer_impl_tree_root->SetScrollable(true);
+ layer_impl_tree_root->SetHorizontalScrollbarLayer(scrollbar_layer_impl);
+ layer_impl_tree_root->SetMaxScrollOffset(gfx::Vector2d(8, 8));
+ layer_impl_tree_root->SetBounds(gfx::Size(2, 2));
+ layer_impl_tree_root->ScrollBy(gfx::Vector2dF(4.f, 0.f));
+
+ {
+ MockQuadCuller quad_culler;
+ AppendQuadsData data;
+ scrollbar_layer_impl->AppendQuads(&quad_culler, &data);
+
+ const QuadList& quads = quad_culler.quad_list();
+ ASSERT_EQ(1u, quads.size());
+ EXPECT_EQ(DrawQuad::SOLID_COLOR, quads[0]->material);
+ EXPECT_RECT_EQ(gfx::Rect(3, 0, 3, 3), quads[0]->rect);
+ }
+}
+
+class ScrollbarLayerSolidColorThumbTest : public testing::Test {
+ public:
+ ScrollbarLayerSolidColorThumbTest() {
+ LayerTreeSettings layer_tree_settings;
+ layer_tree_settings.solid_color_scrollbars = true;
+ host_impl_.reset(new FakeLayerTreeHostImpl(layer_tree_settings, &proxy_));
+
+ horizontal_scrollbar_layer_ = ScrollbarLayerImpl::Create(
+ host_impl_->active_tree(), 1, HORIZONTAL);
+ vertical_scrollbar_layer_ = ScrollbarLayerImpl::Create(
+ host_impl_->active_tree(), 2, VERTICAL);
+ }
+
+ protected:
+ FakeImplProxy proxy_;
+ scoped_ptr<FakeLayerTreeHostImpl> host_impl_;
+ scoped_ptr<ScrollbarLayerImpl> horizontal_scrollbar_layer_;
+ scoped_ptr<ScrollbarLayerImpl> vertical_scrollbar_layer_;
+};
+
+TEST_F(ScrollbarLayerSolidColorThumbTest, SolidColorThumbLength) {
+ horizontal_scrollbar_layer_->SetCurrentPos(0);
+ horizontal_scrollbar_layer_->SetMaximum(10);
+ horizontal_scrollbar_layer_->SetThumbThickness(3);
+
+ // Simple case - one third of the scrollable area is visible, so the thumb
+ // should be one third as long as the track.
+ horizontal_scrollbar_layer_->SetVisibleToTotalLengthRatio(0.33f);
+ horizontal_scrollbar_layer_->SetTrackLength(100);
+ EXPECT_EQ(33, horizontal_scrollbar_layer_->ComputeThumbQuadRect().width());
+
+ // The thumb's length should never be less than its thickness.
+ horizontal_scrollbar_layer_->SetVisibleToTotalLengthRatio(0.01f);
+ horizontal_scrollbar_layer_->SetTrackLength(100);
+ EXPECT_EQ(3, horizontal_scrollbar_layer_->ComputeThumbQuadRect().width());
+}
+
+TEST_F(ScrollbarLayerSolidColorThumbTest, SolidColorThumbPosition) {
+ horizontal_scrollbar_layer_->SetTrackLength(100);
+ horizontal_scrollbar_layer_->SetVisibleToTotalLengthRatio(0.1f);
+ horizontal_scrollbar_layer_->SetThumbThickness(3);
+
+ horizontal_scrollbar_layer_->SetCurrentPos(0);
+ horizontal_scrollbar_layer_->SetMaximum(100);
+ EXPECT_EQ(0, horizontal_scrollbar_layer_->ComputeThumbQuadRect().x());
+ EXPECT_EQ(10, horizontal_scrollbar_layer_->ComputeThumbQuadRect().width());
+
+ horizontal_scrollbar_layer_->SetCurrentPos(100);
+ // The thumb is 10px long and the track is 100px, so the maximum thumb
+ // position is 90px.
+ EXPECT_EQ(90, horizontal_scrollbar_layer_->ComputeThumbQuadRect().x());
+
+ horizontal_scrollbar_layer_->SetCurrentPos(80);
+ // The scroll position is 80% of the maximum, so the thumb's position should
+ // be at 80% of its maximum or 72px.
+ EXPECT_EQ(72, horizontal_scrollbar_layer_->ComputeThumbQuadRect().x());
+}
+
+TEST_F(ScrollbarLayerSolidColorThumbTest, SolidColorThumbVerticalAdjust) {
+ ScrollbarLayerImpl* layers[2] =
+ { horizontal_scrollbar_layer_.get(), vertical_scrollbar_layer_.get() };
+ for (size_t i = 0; i < 2; ++i) {
+ layers[i]->SetTrackLength(100);
+ layers[i]->SetVisibleToTotalLengthRatio(0.2f);
+ layers[i]->SetThumbThickness(3);
+ layers[i]->SetCurrentPos(25);
+ layers[i]->SetMaximum(100);
+ }
+
+ EXPECT_RECT_EQ(gfx::RectF(20.f, 0.f, 20.f, 3.f),
+ horizontal_scrollbar_layer_->ComputeThumbQuadRect());
+ EXPECT_RECT_EQ(gfx::RectF(0.f, 20.f, 3.f, 20.f),
+ vertical_scrollbar_layer_->ComputeThumbQuadRect());
+
+ horizontal_scrollbar_layer_->SetVerticalAdjust(10.f);
+ vertical_scrollbar_layer_->SetVerticalAdjust(10.f);
+
+ // The vertical adjustment factor has two effects:
+ // 1.) Moves the horizontal scrollbar down
+ // 2.) Increases the vertical scrollbar's effective track length which both
+ // increases the thumb's length and its position within the track.
+ EXPECT_RECT_EQ(gfx::Rect(20.f, 10.f, 20.f, 3.f),
+ horizontal_scrollbar_layer_->ComputeThumbQuadRect());
+ EXPECT_RECT_EQ(gfx::Rect(0.f, 22, 3.f, 22.f),
+ vertical_scrollbar_layer_->ComputeThumbQuadRect());
+}
+
+class ScrollbarLayerTestMaxTextureSize : public LayerTreeTest {
+ public:
+ ScrollbarLayerTestMaxTextureSize() {}
+
+ void SetScrollbarBounds(gfx::Size bounds) { bounds_ = bounds; }
+
+ virtual void BeginTest() OVERRIDE {
+ scoped_ptr<Scrollbar> scrollbar(new FakeScrollbar);
+ scrollbar_layer_ = ScrollbarLayer::Create(scrollbar.Pass(), 1);
+ scrollbar_layer_->SetLayerTreeHost(layer_tree_host());
+ scrollbar_layer_->SetBounds(bounds_);
+ layer_tree_host()->root_layer()->AddChild(scrollbar_layer_);
+
+ scroll_layer_ = Layer::Create();
+ scrollbar_layer_->SetScrollLayerId(scroll_layer_->id());
+ layer_tree_host()->root_layer()->AddChild(scroll_layer_);
+
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ const int kMaxTextureSize =
+ layer_tree_host()->GetRendererCapabilities().max_texture_size;
+
+ // Check first that we're actually testing something.
+ EXPECT_GT(scrollbar_layer_->bounds().width(), kMaxTextureSize);
+
+ EXPECT_EQ(scrollbar_layer_->content_bounds().width(),
+ kMaxTextureSize - 1);
+ EXPECT_EQ(scrollbar_layer_->content_bounds().height(),
+ kMaxTextureSize - 1);
+
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ scoped_refptr<ScrollbarLayer> scrollbar_layer_;
+ scoped_refptr<Layer> scroll_layer_;
+ gfx::Size bounds_;
+};
+
+TEST_F(ScrollbarLayerTestMaxTextureSize, DirectRenderer) {
+ scoped_ptr<TestWebGraphicsContext3D> context =
+ TestWebGraphicsContext3D::Create();
+ int max_size = 0;
+ context->getIntegerv(GL_MAX_TEXTURE_SIZE, &max_size);
+ SetScrollbarBounds(gfx::Size(max_size + 100, max_size + 100));
+ RunTest(true, false, true);
+}
+
+TEST_F(ScrollbarLayerTestMaxTextureSize, DelegatingRenderer) {
+ scoped_ptr<TestWebGraphicsContext3D> context =
+ TestWebGraphicsContext3D::Create();
+ int max_size = 0;
+ context->getIntegerv(GL_MAX_TEXTURE_SIZE, &max_size);
+ SetScrollbarBounds(gfx::Size(max_size + 100, max_size + 100));
+ RunTest(true, true, true);
+}
+
+class MockLayerTreeHost : public LayerTreeHost {
+ public:
+ MockLayerTreeHost(LayerTreeHostClient* client,
+ const LayerTreeSettings& settings)
+ : LayerTreeHost(client, settings),
+ next_id_(1),
+ total_ui_resource_created_(0),
+ total_ui_resource_deleted_(0) {
+ Initialize(NULL);
+ }
+
+ virtual UIResourceId CreateUIResource(UIResourceClient* content) OVERRIDE {
+ total_ui_resource_created_++;
+ UIResourceId nid = next_id_++;
+ ui_resource_bitmap_map_[nid] = content->GetBitmap(nid, false);
+ return nid;
+ }
+
+ // Deletes a UI resource. May safely be called more than once.
+ virtual void DeleteUIResource(UIResourceId id) OVERRIDE {
+ UIResourceBitmapMap::iterator iter = ui_resource_bitmap_map_.find(id);
+ if (iter != ui_resource_bitmap_map_.end()) {
+ ui_resource_bitmap_map_.erase(iter);
+ total_ui_resource_deleted_++;
+ }
+ }
+
+ size_t UIResourceCount() { return ui_resource_bitmap_map_.size(); }
+ int TotalUIResourceDeleted() { return total_ui_resource_deleted_; }
+ int TotalUIResourceCreated() { return total_ui_resource_created_; }
+
+ gfx::Size ui_resource_size(UIResourceId id) {
+ UIResourceBitmapMap::iterator iter = ui_resource_bitmap_map_.find(id);
+ if (iter != ui_resource_bitmap_map_.end() && iter->second.get())
+ return iter->second->GetSize();
+ return gfx::Size();
+ }
+
+ private:
+ typedef base::hash_map<UIResourceId, scoped_refptr<UIResourceBitmap> >
+ UIResourceBitmapMap;
+ UIResourceBitmapMap ui_resource_bitmap_map_;
+
+ int next_id_;
+ int total_ui_resource_created_;
+ int total_ui_resource_deleted_;
+};
+
+
+class ScrollbarLayerTestResourceCreation : public testing::Test {
+ public:
+ ScrollbarLayerTestResourceCreation()
+ : fake_client_(FakeLayerTreeHostClient::DIRECT_3D) {}
+
+ void TestResourceUpload(int num_updates,
+ size_t expected_resources,
+ int expected_created,
+ int expected_deleted) {
+ layer_tree_host_.reset(
+ new MockLayerTreeHost(&fake_client_, layer_tree_settings_));
+
+ scoped_ptr<Scrollbar> scrollbar(new FakeScrollbar(false, true, false));
+ scoped_refptr<Layer> layer_tree_root = Layer::Create();
+ scoped_refptr<Layer> content_layer = Layer::Create();
+ scoped_refptr<Layer> scrollbar_layer =
+ ScrollbarLayer::Create(scrollbar.Pass(), layer_tree_root->id());
+ layer_tree_root->AddChild(content_layer);
+ layer_tree_root->AddChild(scrollbar_layer);
+
+ layer_tree_host_->InitializeOutputSurfaceIfNeeded();
+ layer_tree_host_->SetRootLayer(layer_tree_root);
+
+ scrollbar_layer->SetIsDrawable(true);
+ scrollbar_layer->SetBounds(gfx::Size(100, 100));
+ layer_tree_root->SetScrollOffset(gfx::Vector2d(10, 20));
+ layer_tree_root->SetMaxScrollOffset(gfx::Vector2d(30, 50));
+ layer_tree_root->SetBounds(gfx::Size(100, 200));
+ content_layer->SetBounds(gfx::Size(100, 200));
+ scrollbar_layer->draw_properties().content_bounds = gfx::Size(100, 200);
+ scrollbar_layer->draw_properties().visible_content_rect =
+ gfx::Rect(0, 0, 100, 200);
+ scrollbar_layer->CreateRenderSurface();
+ scrollbar_layer->draw_properties().render_target = scrollbar_layer.get();
+
+ testing::Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+ EXPECT_EQ(scrollbar_layer->layer_tree_host(), layer_tree_host_.get());
+
+ ResourceUpdateQueue queue;
+ OcclusionTracker occlusion_tracker(gfx::Rect(), false);
+
+ scrollbar_layer->SavePaintProperties();
+ for (int update_counter = 0; update_counter < num_updates; update_counter++)
+ scrollbar_layer->Update(&queue, &occlusion_tracker);
+
+ // A non-solid-color scrollbar should have requested two textures.
+ EXPECT_EQ(expected_resources, layer_tree_host_->UIResourceCount());
+ EXPECT_EQ(expected_created, layer_tree_host_->TotalUIResourceCreated());
+ EXPECT_EQ(expected_deleted, layer_tree_host_->TotalUIResourceDeleted());
+
+ testing::Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+
+ scrollbar_layer->ClearRenderSurface();
+ }
+
+ protected:
+ FakeLayerTreeHostClient fake_client_;
+ LayerTreeSettings layer_tree_settings_;
+ scoped_ptr<MockLayerTreeHost> layer_tree_host_;
+};
+
+TEST_F(ScrollbarLayerTestResourceCreation, ResourceUpload) {
+ layer_tree_settings_.solid_color_scrollbars = false;
+ TestResourceUpload(0, 0, 0, 0);
+ int num_updates[3] = {1, 5, 10};
+ for (int j = 0; j < 3; j++) {
+ TestResourceUpload(
+ num_updates[j], 2, num_updates[j] * 2, (num_updates[j] - 1) * 2);
+ }
+}
+
+TEST_F(ScrollbarLayerTestResourceCreation, SolidColorNoResourceUpload) {
+ layer_tree_settings_.solid_color_scrollbars = true;
+ TestResourceUpload(0, 0, 0, 0);
+ TestResourceUpload(1, 0, 0, 0);
+}
+
+class ScaledScrollbarLayerTestResourceCreation : public testing::Test {
+ public:
+ ScaledScrollbarLayerTestResourceCreation()
+ : fake_client_(FakeLayerTreeHostClient::DIRECT_3D) {}
+
+ void TestResourceUpload(const float test_scale) {
+ layer_tree_host_.reset(
+ new MockLayerTreeHost(&fake_client_, layer_tree_settings_));
+
+ gfx::Point scrollbar_location(0, 185);
+ scoped_refptr<Layer> layer_tree_root = Layer::Create();
+ scoped_refptr<Layer> content_layer = Layer::Create();
+ scoped_refptr<FakeScrollbarLayer> scrollbar_layer =
+ FakeScrollbarLayer::Create(false, true, layer_tree_root->id());
+
+ layer_tree_root->AddChild(content_layer);
+ layer_tree_root->AddChild(scrollbar_layer);
+
+ layer_tree_host_->InitializeOutputSurfaceIfNeeded();
+ layer_tree_host_->SetRootLayer(layer_tree_root);
+
+ scrollbar_layer->SetIsDrawable(true);
+ scrollbar_layer->SetBounds(gfx::Size(100, 15));
+ scrollbar_layer->SetPosition(scrollbar_location);
+ layer_tree_root->SetBounds(gfx::Size(100, 200));
+ content_layer->SetBounds(gfx::Size(100, 200));
+ gfx::SizeF scaled_size =
+ gfx::ScaleSize(scrollbar_layer->bounds(), test_scale, test_scale);
+ gfx::PointF scaled_location =
+ gfx::ScalePoint(scrollbar_layer->position(), test_scale, test_scale);
+ scrollbar_layer->draw_properties().content_bounds =
+ gfx::Size(scaled_size.width(), scaled_size.height());
+ scrollbar_layer->draw_properties().contents_scale_x = test_scale;
+ scrollbar_layer->draw_properties().contents_scale_y = test_scale;
+ scrollbar_layer->draw_properties().visible_content_rect =
+ gfx::Rect(scaled_location.x(),
+ scaled_location.y(),
+ scaled_size.width(),
+ scaled_size.height());
+ scrollbar_layer->CreateRenderSurface();
+ scrollbar_layer->draw_properties().render_target = scrollbar_layer.get();
+
+ testing::Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+ EXPECT_EQ(scrollbar_layer->layer_tree_host(), layer_tree_host_.get());
+
+ ResourceUpdateQueue queue;
+ OcclusionTracker occlusion_tracker(gfx::Rect(), false);
+ scrollbar_layer->SavePaintProperties();
+ scrollbar_layer->Update(&queue, &occlusion_tracker);
+
+ // Verify that we have not generated any content uploads that are larger
+ // than their destination textures.
+
+ gfx::Size track_size = layer_tree_host_->ui_resource_size(
+ scrollbar_layer->track_resource_id());
+ gfx::Size thumb_size = layer_tree_host_->ui_resource_size(
+ scrollbar_layer->thumb_resource_id());
+
+ EXPECT_LE(track_size.width(), scrollbar_layer->content_bounds().width());
+ EXPECT_LE(track_size.height(), scrollbar_layer->content_bounds().height());
+ EXPECT_LE(thumb_size.width(), scrollbar_layer->content_bounds().width());
+ EXPECT_LE(thumb_size.height(), scrollbar_layer->content_bounds().height());
+
+ testing::Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+
+ scrollbar_layer->ClearRenderSurface();
+ }
+
+ protected:
+ FakeLayerTreeHostClient fake_client_;
+ LayerTreeSettings layer_tree_settings_;
+ scoped_ptr<MockLayerTreeHost> layer_tree_host_;
+};
+
+TEST_F(ScaledScrollbarLayerTestResourceCreation, ScaledResourceUpload) {
+ layer_tree_settings_.solid_color_scrollbars = false;
+ // Pick a test scale that moves the scrollbar's (non-zero) position to
+ // a non-pixel-aligned location.
+ TestResourceUpload(.041f);
+ TestResourceUpload(1.41f);
+ TestResourceUpload(4.1f);
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/layers/scrollbar_theme_painter.h b/chromium/cc/layers/scrollbar_theme_painter.h
new file mode 100644
index 00000000000..f7553ac0e28
--- /dev/null
+++ b/chromium/cc/layers/scrollbar_theme_painter.h
@@ -0,0 +1,36 @@
+// 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.
+
+#ifndef CC_LAYERS_SCROLLBAR_THEME_PAINTER_H_
+#define CC_LAYERS_SCROLLBAR_THEME_PAINTER_H_
+
+#include "cc/base/cc_export.h"
+
+class SkCanvas;
+
+namespace gfx {
+class Rect;
+}
+
+namespace cc {
+
+class CC_EXPORT ScrollbarThemePainter {
+ public:
+ virtual ~ScrollbarThemePainter() {}
+
+ virtual void PaintScrollbarBackground(SkCanvas* canvas, gfx::Rect rect) = 0;
+ virtual void PaintTrackBackground(SkCanvas* canvas, gfx::Rect rect) = 0;
+ virtual void PaintBackTrackPart(SkCanvas* canvas, gfx::Rect rect) = 0;
+ virtual void PaintForwardTrackPart(SkCanvas* canvas, gfx::Rect rect) = 0;
+ virtual void PaintBackButtonStart(SkCanvas* canvas, gfx::Rect rect) = 0;
+ virtual void PaintBackButtonEnd(SkCanvas* canvas, gfx::Rect rect) = 0;
+ virtual void PaintForwardButtonStart(SkCanvas* canvas, gfx::Rect rect) = 0;
+ virtual void PaintForwardButtonEnd(SkCanvas* canvas, gfx::Rect rect) = 0;
+ virtual void PaintTickmarks(SkCanvas* canvas, gfx::Rect rect) = 0;
+ virtual void PaintThumb(SkCanvas* canvas, gfx::Rect rect) = 0;
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_SCROLLBAR_THEME_PAINTER_H_
diff --git a/chromium/cc/layers/solid_color_layer.cc b/chromium/cc/layers/solid_color_layer.cc
new file mode 100644
index 00000000000..5a1e26f085f
--- /dev/null
+++ b/chromium/cc/layers/solid_color_layer.cc
@@ -0,0 +1,30 @@
+// 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 "cc/layers/solid_color_layer.h"
+
+#include "cc/layers/solid_color_layer_impl.h"
+
+namespace cc {
+
+scoped_ptr<LayerImpl> SolidColorLayer::CreateLayerImpl(
+ LayerTreeImpl* tree_impl) {
+ return SolidColorLayerImpl::Create(tree_impl, id()).PassAs<LayerImpl>();
+}
+
+scoped_refptr<SolidColorLayer> SolidColorLayer::Create() {
+ return make_scoped_refptr(new SolidColorLayer());
+}
+
+SolidColorLayer::SolidColorLayer()
+ : Layer() {}
+
+SolidColorLayer::~SolidColorLayer() {}
+
+void SolidColorLayer::SetBackgroundColor(SkColor color) {
+ SetContentsOpaque(SkColorGetA(color) == 255);
+ Layer::SetBackgroundColor(color);
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/solid_color_layer.h b/chromium/cc/layers/solid_color_layer.h
new file mode 100644
index 00000000000..d391070f58f
--- /dev/null
+++ b/chromium/cc/layers/solid_color_layer.h
@@ -0,0 +1,35 @@
+// 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.
+
+
+#ifndef CC_LAYERS_SOLID_COLOR_LAYER_H_
+#define CC_LAYERS_SOLID_COLOR_LAYER_H_
+
+#include "cc/base/cc_export.h"
+#include "cc/layers/layer.h"
+
+namespace cc {
+
+// A Layer that renders a solid color. The color is specified by using
+// SetBackgroundColor() on the base class.
+class CC_EXPORT SolidColorLayer : public Layer {
+ public:
+ static scoped_refptr<SolidColorLayer> Create();
+
+ virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl)
+ OVERRIDE;
+
+ virtual void SetBackgroundColor(SkColor color) OVERRIDE;
+
+ protected:
+ SolidColorLayer();
+
+ private:
+ virtual ~SolidColorLayer();
+
+ DISALLOW_COPY_AND_ASSIGN(SolidColorLayer);
+};
+
+} // namespace cc
+#endif // CC_LAYERS_SOLID_COLOR_LAYER_H_
diff --git a/chromium/cc/layers/solid_color_layer_impl.cc b/chromium/cc/layers/solid_color_layer_impl.cc
new file mode 100644
index 00000000000..faa967be279
--- /dev/null
+++ b/chromium/cc/layers/solid_color_layer_impl.cc
@@ -0,0 +1,53 @@
+// 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 "cc/layers/solid_color_layer_impl.h"
+
+#include <algorithm>
+
+#include "cc/layers/quad_sink.h"
+#include "cc/quads/solid_color_draw_quad.h"
+
+namespace cc {
+
+SolidColorLayerImpl::SolidColorLayerImpl(LayerTreeImpl* tree_impl, int id)
+ : LayerImpl(tree_impl, id),
+ tile_size_(256) {}
+
+SolidColorLayerImpl::~SolidColorLayerImpl() {}
+
+scoped_ptr<LayerImpl> SolidColorLayerImpl::CreateLayerImpl(
+ LayerTreeImpl* tree_impl) {
+ return SolidColorLayerImpl::Create(tree_impl, id()).PassAs<LayerImpl>();
+}
+
+void SolidColorLayerImpl::AppendQuads(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data) {
+ SharedQuadState* shared_quad_state =
+ quad_sink->UseSharedQuadState(CreateSharedQuadState());
+ AppendDebugBorderQuad(quad_sink, shared_quad_state, append_quads_data);
+
+ // We create a series of smaller quads instead of just one large one so that
+ // the culler can reduce the total pixels drawn.
+ int width = content_bounds().width();
+ int height = content_bounds().height();
+ for (int x = 0; x < width; x += tile_size_) {
+ for (int y = 0; y < height; y += tile_size_) {
+ gfx::Rect solid_tile_rect(x,
+ y,
+ std::min(width - x, tile_size_),
+ std::min(height - y, tile_size_));
+ scoped_ptr<SolidColorDrawQuad> quad = SolidColorDrawQuad::Create();
+ quad->SetNew(
+ shared_quad_state, solid_tile_rect, background_color(), false);
+ quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data);
+ }
+ }
+}
+
+const char* SolidColorLayerImpl::LayerTypeAsString() const {
+ return "cc::SolidColorLayerImpl";
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/solid_color_layer_impl.h b/chromium/cc/layers/solid_color_layer_impl.h
new file mode 100644
index 00000000000..d83c7b02d98
--- /dev/null
+++ b/chromium/cc/layers/solid_color_layer_impl.h
@@ -0,0 +1,41 @@
+// 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.
+
+#ifndef CC_LAYERS_SOLID_COLOR_LAYER_IMPL_H_
+#define CC_LAYERS_SOLID_COLOR_LAYER_IMPL_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/layers/layer_impl.h"
+
+namespace cc {
+
+class CC_EXPORT SolidColorLayerImpl : public LayerImpl {
+ public:
+ static scoped_ptr<SolidColorLayerImpl> Create(LayerTreeImpl* tree_impl,
+ int id) {
+ return make_scoped_ptr(new SolidColorLayerImpl(tree_impl, id));
+ }
+ virtual ~SolidColorLayerImpl();
+
+ // LayerImpl overrides.
+ virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl)
+ OVERRIDE;
+ virtual void AppendQuads(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data) OVERRIDE;
+
+ protected:
+ SolidColorLayerImpl(LayerTreeImpl* tree_impl, int id);
+
+ private:
+ virtual const char* LayerTypeAsString() const OVERRIDE;
+
+ const int tile_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(SolidColorLayerImpl);
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_SOLID_COLOR_LAYER_IMPL_H_
diff --git a/chromium/cc/layers/solid_color_layer_impl_unittest.cc b/chromium/cc/layers/solid_color_layer_impl_unittest.cc
new file mode 100644
index 00000000000..94dd2ffbe9e
--- /dev/null
+++ b/chromium/cc/layers/solid_color_layer_impl_unittest.cc
@@ -0,0 +1,169 @@
+// 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 "cc/layers/solid_color_layer_impl.h"
+
+#include <vector>
+
+#include "cc/layers/append_quads_data.h"
+#include "cc/layers/solid_color_layer.h"
+#include "cc/quads/solid_color_draw_quad.h"
+#include "cc/test/fake_impl_proxy.h"
+#include "cc/test/fake_layer_tree_host.h"
+#include "cc/test/layer_test_common.h"
+#include "cc/test/mock_quad_culler.h"
+#include "cc/trees/single_thread_proxy.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+TEST(SolidColorLayerImplTest, VerifyTilingCompleteAndNoOverlap) {
+ MockQuadCuller quad_culler;
+ gfx::Size layer_size = gfx::Size(800, 600);
+ gfx::Rect visible_content_rect = gfx::Rect(layer_size);
+
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ scoped_ptr<SolidColorLayerImpl> layer =
+ SolidColorLayerImpl::Create(host_impl.active_tree(), 1);
+ layer->draw_properties().visible_content_rect = visible_content_rect;
+ layer->SetBounds(layer_size);
+ layer->SetContentBounds(layer_size);
+ layer->CreateRenderSurface();
+ layer->draw_properties().render_target = layer.get();
+
+ AppendQuadsData data;
+ layer->AppendQuads(&quad_culler, &data);
+
+ LayerTestCommon::VerifyQuadsExactlyCoverRect(quad_culler.quad_list(),
+ visible_content_rect);
+}
+
+TEST(SolidColorLayerImplTest, VerifyCorrectBackgroundColorInQuad) {
+ SkColor test_color = 0xFFA55AFF;
+
+ MockQuadCuller quad_culler;
+ gfx::Size layer_size = gfx::Size(100, 100);
+ gfx::Rect visible_content_rect = gfx::Rect(layer_size);
+
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ scoped_ptr<SolidColorLayerImpl> layer =
+ SolidColorLayerImpl::Create(host_impl.active_tree(), 1);
+ layer->draw_properties().visible_content_rect = visible_content_rect;
+ layer->SetBounds(layer_size);
+ layer->SetContentBounds(layer_size);
+ layer->SetBackgroundColor(test_color);
+ layer->CreateRenderSurface();
+ layer->draw_properties().render_target = layer.get();
+
+ AppendQuadsData data;
+ layer->AppendQuads(&quad_culler, &data);
+
+ ASSERT_EQ(quad_culler.quad_list().size(), 1U);
+ EXPECT_EQ(SolidColorDrawQuad::MaterialCast(quad_culler.quad_list()[0])->color,
+ test_color);
+}
+
+TEST(SolidColorLayerImplTest, VerifyCorrectOpacityInQuad) {
+ const float opacity = 0.5f;
+
+ MockQuadCuller quad_culler;
+ gfx::Size layer_size = gfx::Size(100, 100);
+ gfx::Rect visible_content_rect = gfx::Rect(layer_size);
+
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ scoped_ptr<SolidColorLayerImpl> layer =
+ SolidColorLayerImpl::Create(host_impl.active_tree(), 1);
+ layer->draw_properties().visible_content_rect = visible_content_rect;
+ layer->SetBounds(layer_size);
+ layer->SetContentBounds(layer_size);
+ layer->draw_properties().opacity = opacity;
+ layer->CreateRenderSurface();
+ layer->draw_properties().render_target = layer.get();
+
+ AppendQuadsData data;
+ layer->AppendQuads(&quad_culler, &data);
+
+ ASSERT_EQ(quad_culler.quad_list().size(), 1U);
+ EXPECT_EQ(opacity,
+ SolidColorDrawQuad::MaterialCast(quad_culler.quad_list()[0])
+ ->opacity());
+}
+
+TEST(SolidColorLayerImplTest, VerifyOpaqueRect) {
+ gfx::Size layer_size = gfx::Size(100, 100);
+ gfx::Rect visible_content_rect = gfx::Rect(layer_size);
+
+ scoped_refptr<SolidColorLayer> layer = SolidColorLayer::Create();
+ layer->SetBounds(layer_size);
+ layer->SetForceRenderSurface(true);
+
+ scoped_refptr<Layer> root = Layer::Create();
+ root->AddChild(layer);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root, gfx::Size(500, 500), &render_surface_layer_list);
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ EXPECT_FALSE(layer->contents_opaque());
+ layer->SetBackgroundColor(SkColorSetARGBInline(255, 10, 20, 30));
+ EXPECT_TRUE(layer->contents_opaque());
+ {
+ scoped_ptr<SolidColorLayerImpl> layer_impl =
+ SolidColorLayerImpl::Create(host->host_impl()->active_tree(),
+ layer->id());
+ layer->PushPropertiesTo(layer_impl.get());
+
+ // The impl layer should call itself opaque as well.
+ EXPECT_TRUE(layer_impl->contents_opaque());
+
+ // Impl layer has 1 opacity, and the color is opaque, so the opaque_rect
+ // should be the full tile.
+ layer_impl->draw_properties().opacity = 1;
+
+ MockQuadCuller quad_culler;
+ AppendQuadsData data;
+ layer_impl->AppendQuads(&quad_culler, &data);
+
+ ASSERT_EQ(quad_culler.quad_list().size(), 1U);
+ EXPECT_EQ(visible_content_rect.ToString(),
+ quad_culler.quad_list()[0]->opaque_rect.ToString());
+ }
+
+ EXPECT_TRUE(layer->contents_opaque());
+ layer->SetBackgroundColor(SkColorSetARGBInline(254, 10, 20, 30));
+ EXPECT_FALSE(layer->contents_opaque());
+ {
+ scoped_ptr<SolidColorLayerImpl> layer_impl =
+ SolidColorLayerImpl::Create(host->host_impl()->active_tree(),
+ layer->id());
+ layer->PushPropertiesTo(layer_impl.get());
+
+ // The impl layer should callnot itself opaque anymore.
+ EXPECT_FALSE(layer_impl->contents_opaque());
+
+ // Impl layer has 1 opacity, but the color is not opaque, so the opaque_rect
+ // should be empty.
+ layer_impl->draw_properties().opacity = 1;
+
+ MockQuadCuller quad_culler;
+ AppendQuadsData data;
+ layer_impl->AppendQuads(&quad_culler, &data);
+
+ ASSERT_EQ(quad_culler.quad_list().size(), 1U);
+ EXPECT_EQ(gfx::Rect().ToString(),
+ quad_culler.quad_list()[0]->opaque_rect.ToString());
+ }
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/layers/texture_layer.cc b/chromium/cc/layers/texture_layer.cc
new file mode 100644
index 00000000000..316042b861a
--- /dev/null
+++ b/chromium/cc/layers/texture_layer.cc
@@ -0,0 +1,322 @@
+// Copyright 2010 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 "cc/layers/texture_layer.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "cc/layers/texture_layer_client.h"
+#include "cc/layers/texture_layer_impl.h"
+#include "cc/trees/layer_tree_host.h"
+#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h"
+
+namespace cc {
+
+scoped_refptr<TextureLayer> TextureLayer::Create(TextureLayerClient* client) {
+ return scoped_refptr<TextureLayer>(new TextureLayer(client, false));
+}
+
+scoped_refptr<TextureLayer> TextureLayer::CreateForMailbox(
+ TextureLayerClient* client) {
+ return scoped_refptr<TextureLayer>(new TextureLayer(client, true));
+}
+
+TextureLayer::TextureLayer(TextureLayerClient* client, bool uses_mailbox)
+ : Layer(),
+ client_(client),
+ uses_mailbox_(uses_mailbox),
+ flipped_(true),
+ uv_top_left_(0.f, 0.f),
+ uv_bottom_right_(1.f, 1.f),
+ premultiplied_alpha_(true),
+ blend_background_color_(false),
+ rate_limit_context_(false),
+ content_committed_(false),
+ texture_id_(0),
+ needs_set_mailbox_(false) {
+ vertex_opacity_[0] = 1.0f;
+ vertex_opacity_[1] = 1.0f;
+ vertex_opacity_[2] = 1.0f;
+ vertex_opacity_[3] = 1.0f;
+}
+
+TextureLayer::~TextureLayer() {
+}
+
+void TextureLayer::ClearClient() {
+ if (rate_limit_context_ && client_ && layer_tree_host())
+ layer_tree_host()->StopRateLimiter(client_->Context3d());
+ client_ = NULL;
+ if (uses_mailbox_)
+ SetTextureMailbox(TextureMailbox());
+ else
+ SetTextureId(0);
+}
+
+scoped_ptr<LayerImpl> TextureLayer::CreateLayerImpl(LayerTreeImpl* tree_impl) {
+ return TextureLayerImpl::Create(tree_impl, id(), uses_mailbox_).
+ PassAs<LayerImpl>();
+}
+
+void TextureLayer::SetFlipped(bool flipped) {
+ if (flipped_ == flipped)
+ return;
+ flipped_ = flipped;
+ SetNeedsCommit();
+}
+
+void TextureLayer::SetUV(gfx::PointF top_left, gfx::PointF bottom_right) {
+ if (uv_top_left_ == top_left && uv_bottom_right_ == bottom_right)
+ return;
+ uv_top_left_ = top_left;
+ uv_bottom_right_ = bottom_right;
+ SetNeedsCommit();
+}
+
+void TextureLayer::SetVertexOpacity(float bottom_left,
+ float top_left,
+ float top_right,
+ float bottom_right) {
+ // Indexing according to the quad vertex generation:
+ // 1--2
+ // | |
+ // 0--3
+ if (vertex_opacity_[0] == bottom_left &&
+ vertex_opacity_[1] == top_left &&
+ vertex_opacity_[2] == top_right &&
+ vertex_opacity_[3] == bottom_right)
+ return;
+ vertex_opacity_[0] = bottom_left;
+ vertex_opacity_[1] = top_left;
+ vertex_opacity_[2] = top_right;
+ vertex_opacity_[3] = bottom_right;
+ SetNeedsCommit();
+}
+
+void TextureLayer::SetPremultipliedAlpha(bool premultiplied_alpha) {
+ if (premultiplied_alpha_ == premultiplied_alpha)
+ return;
+ premultiplied_alpha_ = premultiplied_alpha;
+ SetNeedsCommit();
+}
+
+void TextureLayer::SetBlendBackgroundColor(bool blend) {
+ if (blend_background_color_ == blend)
+ return;
+ blend_background_color_ = blend;
+ SetNeedsCommit();
+}
+
+void TextureLayer::SetRateLimitContext(bool rate_limit) {
+ if (!rate_limit && rate_limit_context_ && client_ && layer_tree_host())
+ layer_tree_host()->StopRateLimiter(client_->Context3d());
+
+ rate_limit_context_ = rate_limit;
+}
+
+void TextureLayer::SetTextureId(unsigned id) {
+ DCHECK(!uses_mailbox_);
+ if (texture_id_ == id)
+ return;
+ if (texture_id_ && layer_tree_host())
+ layer_tree_host()->AcquireLayerTextures();
+ texture_id_ = id;
+ SetNeedsCommit();
+}
+
+void TextureLayer::SetTextureMailbox(const TextureMailbox& mailbox) {
+ DCHECK(uses_mailbox_);
+ DCHECK(!mailbox.IsValid() || !holder_ref_ ||
+ !mailbox.Equals(holder_ref_->holder()->mailbox()));
+ // If we never commited the mailbox, we need to release it here.
+ if (mailbox.IsValid())
+ holder_ref_ = MailboxHolder::Create(mailbox);
+ else
+ holder_ref_.reset();
+ needs_set_mailbox_ = true;
+ SetNeedsCommit();
+}
+
+void TextureLayer::WillModifyTexture() {
+ if (layer_tree_host() && (DrawsContent() || content_committed_)) {
+ layer_tree_host()->AcquireLayerTextures();
+ content_committed_ = false;
+ }
+}
+
+void TextureLayer::SetNeedsDisplayRect(const gfx::RectF& dirty_rect) {
+ Layer::SetNeedsDisplayRect(dirty_rect);
+
+ if (rate_limit_context_ && client_ && layer_tree_host() && DrawsContent())
+ layer_tree_host()->StartRateLimiter(client_->Context3d());
+}
+
+void TextureLayer::SetLayerTreeHost(LayerTreeHost* host) {
+ if (layer_tree_host() == host) {
+ Layer::SetLayerTreeHost(host);
+ return;
+ }
+
+ if (layer_tree_host()) {
+ if (texture_id_)
+ layer_tree_host()->AcquireLayerTextures();
+ if (rate_limit_context_ && client_)
+ layer_tree_host()->StopRateLimiter(client_->Context3d());
+ }
+ // If we're removed from the tree, the TextureLayerImpl will be destroyed, and
+ // we will need to set the mailbox again on a new TextureLayerImpl the next
+ // time we push.
+ if (!host && uses_mailbox_ && holder_ref_)
+ needs_set_mailbox_ = true;
+ Layer::SetLayerTreeHost(host);
+}
+
+bool TextureLayer::DrawsContent() const {
+ return (client_ || texture_id_ || holder_ref_) && Layer::DrawsContent();
+}
+
+bool TextureLayer::Update(ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion) {
+ bool updated = Layer::Update(queue, occlusion);
+ if (client_) {
+ if (uses_mailbox_) {
+ TextureMailbox mailbox;
+ if (client_->PrepareTextureMailbox(
+ &mailbox, layer_tree_host()->UsingSharedMemoryResources())) {
+ SetTextureMailbox(mailbox);
+ updated = true;
+ }
+ } else {
+ DCHECK(client_->Context3d());
+ texture_id_ = client_->PrepareTexture();
+ if (client_->Context3d() &&
+ client_->Context3d()->getGraphicsResetStatusARB() != GL_NO_ERROR)
+ texture_id_ = 0;
+ updated = true;
+ }
+ }
+
+ // SetTextureMailbox could be called externally and the same mailbox used for
+ // different textures. Such callers notify this layer that the texture has
+ // changed by calling SetNeedsDisplay, so check for that here.
+ return updated || !update_rect_.IsEmpty();
+}
+
+void TextureLayer::PushPropertiesTo(LayerImpl* layer) {
+ Layer::PushPropertiesTo(layer);
+
+ TextureLayerImpl* texture_layer = static_cast<TextureLayerImpl*>(layer);
+ texture_layer->set_flipped(flipped_);
+ texture_layer->set_uv_top_left(uv_top_left_);
+ texture_layer->set_uv_bottom_right(uv_bottom_right_);
+ texture_layer->set_vertex_opacity(vertex_opacity_);
+ texture_layer->set_premultiplied_alpha(premultiplied_alpha_);
+ texture_layer->set_blend_background_color(blend_background_color_);
+ if (uses_mailbox_ && needs_set_mailbox_) {
+ TextureMailbox texture_mailbox;
+ if (holder_ref_) {
+ MailboxHolder* holder = holder_ref_->holder();
+ TextureMailbox::ReleaseCallback callback =
+ holder->GetCallbackForImplThread();
+ texture_mailbox = holder->mailbox().CopyWithNewCallback(callback);
+ }
+ texture_layer->SetTextureMailbox(texture_mailbox);
+ needs_set_mailbox_ = false;
+ } else {
+ texture_layer->set_texture_id(texture_id_);
+ }
+ content_committed_ = DrawsContent();
+}
+
+Region TextureLayer::VisibleContentOpaqueRegion() const {
+ if (contents_opaque())
+ return visible_content_rect();
+
+ if (blend_background_color_ && (SkColorGetA(background_color()) == 0xFF))
+ return visible_content_rect();
+
+ return Region();
+}
+
+bool TextureLayer::BlocksPendingCommit() const {
+ // Double-buffered texture layers need to be blocked until they can be made
+ // triple-buffered. Single-buffered layers already prevent draws, so
+ // can block too for simplicity.
+ return DrawsContent();
+}
+
+bool TextureLayer::CanClipSelf() const {
+ return true;
+}
+
+TextureLayer::MailboxHolder::MainThreadReference::MainThreadReference(
+ MailboxHolder* holder)
+ : holder_(holder) {
+ holder_->InternalAddRef();
+}
+
+TextureLayer::MailboxHolder::MainThreadReference::~MainThreadReference() {
+ holder_->InternalRelease();
+}
+
+TextureLayer::MailboxHolder::MailboxHolder(const TextureMailbox& mailbox)
+ : message_loop_(base::MessageLoopProxy::current()),
+ internal_references_(0),
+ mailbox_(mailbox),
+ sync_point_(mailbox.sync_point()),
+ is_lost_(false) {
+}
+
+TextureLayer::MailboxHolder::~MailboxHolder() {
+ DCHECK_EQ(0u, internal_references_);
+}
+
+scoped_ptr<TextureLayer::MailboxHolder::MainThreadReference>
+TextureLayer::MailboxHolder::Create(const TextureMailbox& mailbox) {
+ return scoped_ptr<MainThreadReference>(new MainThreadReference(
+ new MailboxHolder(mailbox)));
+}
+
+void TextureLayer::MailboxHolder::Return(unsigned sync_point, bool is_lost) {
+ sync_point_ = sync_point;
+ is_lost_ = is_lost;
+}
+
+TextureMailbox::ReleaseCallback
+TextureLayer::MailboxHolder::GetCallbackForImplThread() {
+ // We can't call GetCallbackForImplThread if we released the main thread
+ // reference.
+ DCHECK_GT(internal_references_, 0u);
+ InternalAddRef();
+ return base::Bind(&MailboxHolder::ReturnAndReleaseOnImplThread, this);
+}
+
+void TextureLayer::MailboxHolder::InternalAddRef() {
+ ++internal_references_;
+}
+
+void TextureLayer::MailboxHolder::InternalRelease() {
+ DCHECK(message_loop_->BelongsToCurrentThread());
+ if (!--internal_references_) {
+ mailbox_.RunReleaseCallback(sync_point_, is_lost_);
+ mailbox_ = TextureMailbox();
+ }
+}
+
+void TextureLayer::MailboxHolder::ReturnAndReleaseOnMainThread(
+ unsigned sync_point, bool is_lost) {
+ DCHECK(message_loop_->BelongsToCurrentThread());
+ Return(sync_point, is_lost);
+ InternalRelease();
+}
+
+void TextureLayer::MailboxHolder::ReturnAndReleaseOnImplThread(
+ unsigned sync_point, bool is_lost) {
+ message_loop_->PostTask(FROM_HERE, base::Bind(
+ &MailboxHolder::ReturnAndReleaseOnMainThread,
+ this, sync_point, is_lost));
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/texture_layer.h b/chromium/cc/layers/texture_layer.h
new file mode 100644
index 00000000000..9e63b6825eb
--- /dev/null
+++ b/chromium/cc/layers/texture_layer.h
@@ -0,0 +1,160 @@
+// Copyright 2010 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.
+
+#ifndef CC_LAYERS_TEXTURE_LAYER_H_
+#define CC_LAYERS_TEXTURE_LAYER_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "cc/base/cc_export.h"
+#include "cc/layers/layer.h"
+#include "cc/resources/texture_mailbox.h"
+
+namespace WebKit { class WebGraphicsContext3D; }
+
+namespace base {
+class MessageLoopProxy;
+}
+
+namespace cc {
+
+class TextureLayerClient;
+
+// A Layer containing a the rendered output of a plugin instance.
+class CC_EXPORT TextureLayer : public Layer {
+ public:
+ // If this texture layer requires special preparation logic for each frame
+ // driven by the compositor, pass in a non-nil client. Pass in a nil client
+ // pointer if texture updates are driven by an external process.
+ static scoped_refptr<TextureLayer> Create(TextureLayerClient* client);
+
+ // Used when mailbox names are specified instead of texture IDs.
+ static scoped_refptr<TextureLayer> CreateForMailbox(
+ TextureLayerClient* client);
+
+ void ClearClient();
+
+ virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl)
+ OVERRIDE;
+
+ // Sets whether this texture should be Y-flipped at draw time. Defaults to
+ // true.
+ void SetFlipped(bool flipped);
+
+ // Sets a UV transform to be used at draw time. Defaults to (0, 0) and (1, 1).
+ void SetUV(gfx::PointF top_left, gfx::PointF bottom_right);
+
+ // Sets an opacity value per vertex. It will be multiplied by the layer
+ // opacity value.
+ void SetVertexOpacity(float bottom_left,
+ float top_left,
+ float top_right,
+ float bottom_right);
+
+ // Sets whether the alpha channel is premultiplied or unpremultiplied.
+ // Defaults to true.
+ void SetPremultipliedAlpha(bool premultiplied_alpha);
+
+ // Sets whether the texture should be blended with the background color
+ // at draw time. Defaults to false.
+ void SetBlendBackgroundColor(bool blend);
+
+ // Sets whether this context should rate limit on damage to prevent too many
+ // frames from being queued up before the compositor gets a chance to run.
+ // Requires a non-nil client. Defaults to false.
+ void SetRateLimitContext(bool rate_limit);
+
+ // Code path for plugins which supply their own texture ID.
+ void SetTextureId(unsigned texture_id);
+
+ // Code path for plugins which supply their own mailbox.
+ bool uses_mailbox() const { return uses_mailbox_; }
+ void SetTextureMailbox(const TextureMailbox& mailbox);
+
+ void WillModifyTexture();
+
+ virtual void SetNeedsDisplayRect(const gfx::RectF& dirty_rect) OVERRIDE;
+
+ virtual void SetLayerTreeHost(LayerTreeHost* layer_tree_host) OVERRIDE;
+ virtual bool DrawsContent() const OVERRIDE;
+ virtual bool Update(ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion) OVERRIDE;
+ virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE;
+ virtual Region VisibleContentOpaqueRegion() const OVERRIDE;
+ virtual bool BlocksPendingCommit() const OVERRIDE;
+
+ virtual bool CanClipSelf() const OVERRIDE;
+
+ protected:
+ TextureLayer(TextureLayerClient* client, bool uses_mailbox);
+ virtual ~TextureLayer();
+
+ private:
+ class MailboxHolder : public base::RefCountedThreadSafe<MailboxHolder> {
+ public:
+ class MainThreadReference {
+ public:
+ explicit MainThreadReference(MailboxHolder* holder);
+ ~MainThreadReference();
+ MailboxHolder* holder() { return holder_.get(); }
+
+ private:
+ scoped_refptr<MailboxHolder> holder_;
+ DISALLOW_COPY_AND_ASSIGN(MainThreadReference);
+ };
+
+ static scoped_ptr<MainThreadReference> Create(
+ const TextureMailbox& mailbox);
+
+ const TextureMailbox& mailbox() const { return mailbox_; }
+ void Return(unsigned sync_point, bool is_lost);
+
+ // Gets a ReleaseCallback that can be called from another thread. Note: the
+ // caller must ensure the callback is called.
+ TextureMailbox::ReleaseCallback GetCallbackForImplThread();
+
+ private:
+ friend class base::RefCountedThreadSafe<MailboxHolder>;
+ friend class MainThreadReference;
+ explicit MailboxHolder(const TextureMailbox& mailbox);
+ ~MailboxHolder();
+ void InternalAddRef();
+ void InternalRelease();
+ void ReturnAndReleaseOnMainThread(unsigned sync_point, bool is_lost);
+ void ReturnAndReleaseOnImplThread(unsigned sync_point, bool is_lost);
+
+ // Thread safety notes: except for the thread-safe message_loop_, all fields
+ // are only used on the main thread, or on the impl thread during commit
+ // where the main thread is blocked.
+ const scoped_refptr<base::MessageLoopProxy> message_loop_;
+ unsigned internal_references_;
+ TextureMailbox mailbox_;
+ unsigned sync_point_;
+ bool is_lost_;
+ DISALLOW_COPY_AND_ASSIGN(MailboxHolder);
+ };
+
+ TextureLayerClient* client_;
+ bool uses_mailbox_;
+
+ bool flipped_;
+ gfx::PointF uv_top_left_;
+ gfx::PointF uv_bottom_right_;
+ // [bottom left, top left, top right, bottom right]
+ float vertex_opacity_[4];
+ bool premultiplied_alpha_;
+ bool blend_background_color_;
+ bool rate_limit_context_;
+ bool content_committed_;
+
+ unsigned texture_id_;
+ scoped_ptr<MailboxHolder::MainThreadReference> holder_ref_;
+ bool needs_set_mailbox_;
+
+ DISALLOW_COPY_AND_ASSIGN(TextureLayer);
+};
+
+} // namespace cc
+#endif // CC_LAYERS_TEXTURE_LAYER_H_
diff --git a/chromium/cc/layers/texture_layer_client.h b/chromium/cc/layers/texture_layer_client.h
new file mode 100644
index 00000000000..73d34b30775
--- /dev/null
+++ b/chromium/cc/layers/texture_layer_client.h
@@ -0,0 +1,36 @@
+// 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.
+
+#ifndef CC_LAYERS_TEXTURE_LAYER_CLIENT_H_
+#define CC_LAYERS_TEXTURE_LAYER_CLIENT_H_
+
+namespace WebKit { class WebGraphicsContext3D; }
+
+namespace cc {
+class ResourceUpdateQueue;
+class TextureMailbox;
+
+class TextureLayerClient {
+ public:
+ // Called to prepare this layer's texture for compositing.
+ // Returns the texture ID to be used for compositing.
+ virtual unsigned PrepareTexture() = 0;
+
+ // Returns the context that is providing the texture. Used for rate limiting
+ // and detecting lost context.
+ virtual WebKit::WebGraphicsContext3D* Context3d() = 0;
+
+ // Returns true and provides a mailbox if a new frame is available.
+ // Returns false if no new data is available
+ // and the old mailbox is to be reused.
+ virtual bool PrepareTextureMailbox(TextureMailbox* mailbox,
+ bool use_shared_memory) = 0;
+
+ protected:
+ virtual ~TextureLayerClient() {}
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_TEXTURE_LAYER_CLIENT_H_
diff --git a/chromium/cc/layers/texture_layer_impl.cc b/chromium/cc/layers/texture_layer_impl.cc
new file mode 100644
index 00000000000..851bf9a145b
--- /dev/null
+++ b/chromium/cc/layers/texture_layer_impl.cc
@@ -0,0 +1,242 @@
+// Copyright 2011 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 "cc/layers/texture_layer_impl.h"
+
+#include "base/strings/stringprintf.h"
+#include "cc/layers/quad_sink.h"
+#include "cc/output/renderer.h"
+#include "cc/quads/texture_draw_quad.h"
+#include "cc/resources/platform_color.h"
+#include "cc/resources/scoped_resource.h"
+#include "cc/trees/layer_tree_impl.h"
+
+namespace cc {
+
+TextureLayerImpl::TextureLayerImpl(LayerTreeImpl* tree_impl,
+ int id,
+ bool uses_mailbox)
+ : LayerImpl(tree_impl, id),
+ texture_id_(0),
+ external_texture_resource_(0),
+ premultiplied_alpha_(true),
+ blend_background_color_(false),
+ flipped_(true),
+ uv_top_left_(0.f, 0.f),
+ uv_bottom_right_(1.f, 1.f),
+ uses_mailbox_(uses_mailbox),
+ own_mailbox_(false),
+ valid_texture_copy_(false) {
+ vertex_opacity_[0] = 1.0f;
+ vertex_opacity_[1] = 1.0f;
+ vertex_opacity_[2] = 1.0f;
+ vertex_opacity_[3] = 1.0f;
+}
+
+TextureLayerImpl::~TextureLayerImpl() { FreeTextureMailbox(); }
+
+void TextureLayerImpl::SetTextureMailbox(const TextureMailbox& mailbox) {
+ DCHECK(uses_mailbox_);
+ FreeTextureMailbox();
+ texture_mailbox_ = mailbox;
+ own_mailbox_ = true;
+ valid_texture_copy_ = false;
+}
+
+scoped_ptr<LayerImpl> TextureLayerImpl::CreateLayerImpl(
+ LayerTreeImpl* tree_impl) {
+ return TextureLayerImpl::Create(tree_impl, id(), uses_mailbox_).
+ PassAs<LayerImpl>();
+}
+
+void TextureLayerImpl::PushPropertiesTo(LayerImpl* layer) {
+ LayerImpl::PushPropertiesTo(layer);
+
+ TextureLayerImpl* texture_layer = static_cast<TextureLayerImpl*>(layer);
+ texture_layer->set_flipped(flipped_);
+ texture_layer->set_uv_top_left(uv_top_left_);
+ texture_layer->set_uv_bottom_right(uv_bottom_right_);
+ texture_layer->set_vertex_opacity(vertex_opacity_);
+ texture_layer->set_premultiplied_alpha(premultiplied_alpha_);
+ if (uses_mailbox_ && own_mailbox_) {
+ texture_layer->SetTextureMailbox(texture_mailbox_);
+ own_mailbox_ = false;
+ } else {
+ texture_layer->set_texture_id(texture_id_);
+ }
+}
+
+bool TextureLayerImpl::WillDraw(DrawMode draw_mode,
+ ResourceProvider* resource_provider) {
+ if (draw_mode == DRAW_MODE_RESOURCELESS_SOFTWARE)
+ return false;
+
+ if (uses_mailbox_) {
+ if (own_mailbox_) {
+ DCHECK(!external_texture_resource_);
+ if ((draw_mode == DRAW_MODE_HARDWARE && texture_mailbox_.IsTexture()) ||
+ (draw_mode == DRAW_MODE_SOFTWARE &&
+ texture_mailbox_.IsSharedMemory())) {
+ external_texture_resource_ =
+ resource_provider->CreateResourceFromTextureMailbox(
+ texture_mailbox_);
+ DCHECK(external_texture_resource_);
+ texture_copy_.reset();
+ valid_texture_copy_ = false;
+ }
+ if (external_texture_resource_)
+ own_mailbox_ = false;
+ }
+
+ if (!valid_texture_copy_ && draw_mode == DRAW_MODE_HARDWARE &&
+ texture_mailbox_.IsSharedMemory()) {
+ DCHECK(!external_texture_resource_);
+ // Have to upload a copy to a texture for it to be used in a
+ // hardware draw.
+ if (!texture_copy_)
+ texture_copy_ = ScopedResource::create(resource_provider);
+ if (texture_copy_->size() != texture_mailbox_.shared_memory_size() ||
+ resource_provider->InUseByConsumer(texture_copy_->id()))
+ texture_copy_->Free();
+
+ if (!texture_copy_->id()) {
+ texture_copy_->Allocate(texture_mailbox_.shared_memory_size(),
+ resource_provider->best_texture_format(),
+ ResourceProvider::TextureUsageAny);
+ }
+
+ if (texture_copy_->id()) {
+ std::vector<uint8> swizzled;
+ uint8* pixels =
+ static_cast<uint8*>(texture_mailbox_.shared_memory()->memory());
+
+ if (!PlatformColor::SameComponentOrder(texture_copy_->format())) {
+ // Swizzle colors. This is slow, but should be really uncommon.
+ swizzled.resize(texture_mailbox_.shared_memory_size_in_bytes());
+ for (size_t i = 0; i < texture_mailbox_.shared_memory_size_in_bytes();
+ i += 4) {
+ swizzled[i] = pixels[i + 2];
+ swizzled[i + 1] = pixels[i + 1];
+ swizzled[i + 2] = pixels[i];
+ swizzled[i + 3] = pixels[i + 3];
+ }
+ pixels = &swizzled[0];
+ }
+
+ resource_provider->SetPixels(
+ texture_copy_->id(),
+ pixels,
+ gfx::Rect(texture_mailbox_.shared_memory_size()),
+ gfx::Rect(texture_mailbox_.shared_memory_size()),
+ gfx::Vector2d());
+
+ valid_texture_copy_ = true;
+ }
+ }
+ } else if (texture_id_) {
+ DCHECK(!external_texture_resource_);
+ if (draw_mode == DRAW_MODE_HARDWARE) {
+ external_texture_resource_ =
+ resource_provider->CreateResourceFromExternalTexture(
+ GL_TEXTURE_2D,
+ texture_id_);
+ }
+ }
+ return (external_texture_resource_ || valid_texture_copy_) &&
+ LayerImpl::WillDraw(draw_mode, resource_provider);
+}
+
+void TextureLayerImpl::AppendQuads(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data) {
+ DCHECK(external_texture_resource_ || valid_texture_copy_);
+
+ SharedQuadState* shared_quad_state =
+ quad_sink->UseSharedQuadState(CreateSharedQuadState());
+ AppendDebugBorderQuad(quad_sink, shared_quad_state, append_quads_data);
+
+ SkColor bg_color = blend_background_color_ ?
+ background_color() : SK_ColorTRANSPARENT;
+ bool opaque = contents_opaque() || (SkColorGetA(bg_color) == 0xFF);
+
+ gfx::Rect quad_rect(content_bounds());
+ gfx::Rect opaque_rect = opaque ? quad_rect : gfx::Rect();
+ scoped_ptr<TextureDrawQuad> quad = TextureDrawQuad::Create();
+ ResourceProvider::ResourceId id =
+ valid_texture_copy_ ? texture_copy_->id() : external_texture_resource_;
+ quad->SetNew(shared_quad_state,
+ quad_rect,
+ opaque_rect,
+ id,
+ premultiplied_alpha_,
+ uv_top_left_,
+ uv_bottom_right_,
+ bg_color,
+ vertex_opacity_,
+ flipped_);
+
+ // Perform explicit clipping on a quad to avoid setting a scissor later.
+ if (shared_quad_state->is_clipped && quad->PerformClipping())
+ shared_quad_state->is_clipped = false;
+ if (!quad->rect.IsEmpty())
+ quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data);
+}
+
+void TextureLayerImpl::DidDraw(ResourceProvider* resource_provider) {
+ LayerImpl::DidDraw(resource_provider);
+ if (uses_mailbox_ || !external_texture_resource_)
+ return;
+ // TODO(danakj): the following assert will not be true when sending resources
+ // to a parent compositor. A synchronization scheme (double-buffering or
+ // pipelining of updates) for the client will need to exist to solve this.
+ DCHECK(!resource_provider->InUseByConsumer(external_texture_resource_));
+ resource_provider->DeleteResource(external_texture_resource_);
+ external_texture_resource_ = 0;
+}
+
+Region TextureLayerImpl::VisibleContentOpaqueRegion() const {
+ if (contents_opaque())
+ return visible_content_rect();
+
+ if (blend_background_color_ && (SkColorGetA(background_color()) == 0xFF))
+ return visible_content_rect();
+
+ return Region();
+}
+
+void TextureLayerImpl::DidLoseOutputSurface() {
+ if (external_texture_resource_ && !uses_mailbox_) {
+ ResourceProvider* resource_provider =
+ layer_tree_impl()->resource_provider();
+ resource_provider->DeleteResource(external_texture_resource_);
+ }
+ texture_copy_.reset();
+ texture_id_ = 0;
+ external_texture_resource_ = 0;
+ valid_texture_copy_ = false;
+}
+
+const char* TextureLayerImpl::LayerTypeAsString() const {
+ return "cc::TextureLayerImpl";
+}
+
+bool TextureLayerImpl::CanClipSelf() const {
+ return true;
+}
+
+void TextureLayerImpl::FreeTextureMailbox() {
+ if (!uses_mailbox_)
+ return;
+ if (own_mailbox_) {
+ DCHECK(!external_texture_resource_);
+ texture_mailbox_.RunReleaseCallback(texture_mailbox_.sync_point(), false);
+ } else if (external_texture_resource_) {
+ DCHECK(!own_mailbox_);
+ ResourceProvider* resource_provider =
+ layer_tree_impl()->resource_provider();
+ resource_provider->DeleteResource(external_texture_resource_);
+ external_texture_resource_ = 0;
+ }
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/texture_layer_impl.h b/chromium/cc/layers/texture_layer_impl.h
new file mode 100644
index 00000000000..f85720688a6
--- /dev/null
+++ b/chromium/cc/layers/texture_layer_impl.h
@@ -0,0 +1,93 @@
+// 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.
+
+#ifndef CC_LAYERS_TEXTURE_LAYER_IMPL_H_
+#define CC_LAYERS_TEXTURE_LAYER_IMPL_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "cc/base/cc_export.h"
+#include "cc/layers/layer_impl.h"
+
+namespace cc {
+class ScopedResource;
+
+class CC_EXPORT TextureLayerImpl : public LayerImpl {
+ public:
+ static scoped_ptr<TextureLayerImpl> Create(LayerTreeImpl* tree_impl,
+ int id,
+ bool uses_mailbox) {
+ return make_scoped_ptr(new TextureLayerImpl(tree_impl, id, uses_mailbox));
+ }
+ virtual ~TextureLayerImpl();
+
+ virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* layer_tree_impl)
+ OVERRIDE;
+ virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE;
+
+ virtual bool WillDraw(DrawMode draw_mode,
+ ResourceProvider* resource_provider) OVERRIDE;
+ virtual void AppendQuads(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data) OVERRIDE;
+ virtual void DidDraw(ResourceProvider* resource_provider) OVERRIDE;
+ virtual Region VisibleContentOpaqueRegion() const OVERRIDE;
+ virtual void DidLoseOutputSurface() OVERRIDE;
+
+ unsigned texture_id() const { return texture_id_; }
+ void set_texture_id(unsigned id) { texture_id_ = id; }
+ void set_premultiplied_alpha(bool premultiplied_alpha) {
+ premultiplied_alpha_ = premultiplied_alpha;
+ }
+ void set_blend_background_color(bool blend) {
+ blend_background_color_ = blend;
+ }
+ void set_flipped(bool flipped) { flipped_ = flipped; }
+ void set_uv_top_left(gfx::PointF top_left) { uv_top_left_ = top_left; }
+ void set_uv_bottom_right(gfx::PointF bottom_right) {
+ uv_bottom_right_ = bottom_right;
+ }
+
+ // 1--2
+ // | |
+ // 0--3
+ void set_vertex_opacity(const float vertex_opacity[4]) {
+ vertex_opacity_[0] = vertex_opacity[0];
+ vertex_opacity_[1] = vertex_opacity[1];
+ vertex_opacity_[2] = vertex_opacity[2];
+ vertex_opacity_[3] = vertex_opacity[3];
+ }
+
+ virtual bool CanClipSelf() const OVERRIDE;
+
+ void SetTextureMailbox(const TextureMailbox& mailbox);
+
+ private:
+ TextureLayerImpl(LayerTreeImpl* tree_impl, int id, bool uses_mailbox);
+
+ virtual const char* LayerTypeAsString() const OVERRIDE;
+ void FreeTextureMailbox();
+
+ unsigned texture_id_;
+ ResourceProvider::ResourceId external_texture_resource_;
+ bool premultiplied_alpha_;
+ bool blend_background_color_;
+ bool flipped_;
+ gfx::PointF uv_top_left_;
+ gfx::PointF uv_bottom_right_;
+ float vertex_opacity_[4];
+ // This is a resource that's a GL copy of a software texture mailbox.
+ scoped_ptr<ScopedResource> texture_copy_;
+
+ TextureMailbox texture_mailbox_;
+ bool uses_mailbox_;
+ bool own_mailbox_;
+ bool valid_texture_copy_;
+
+ DISALLOW_COPY_AND_ASSIGN(TextureLayerImpl);
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_TEXTURE_LAYER_IMPL_H_
diff --git a/chromium/cc/layers/texture_layer_unittest.cc b/chromium/cc/layers/texture_layer_unittest.cc
new file mode 100644
index 00000000000..744bf004956
--- /dev/null
+++ b/chromium/cc/layers/texture_layer_unittest.cc
@@ -0,0 +1,1123 @@
+// 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 "cc/layers/texture_layer.h"
+
+#include <string>
+
+#include "base/callback.h"
+#include "cc/debug/test_web_graphics_context_3d.h"
+#include "cc/layers/texture_layer_client.h"
+#include "cc/layers/texture_layer_impl.h"
+#include "cc/test/fake_impl_proxy.h"
+#include "cc/test/fake_layer_tree_host_client.h"
+#include "cc/test/fake_layer_tree_host_impl.h"
+#include "cc/test/fake_output_surface.h"
+#include "cc/test/layer_test_common.h"
+#include "cc/test/layer_tree_test.h"
+#include "cc/trees/layer_tree_host.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "cc/trees/single_thread_proxy.h"
+#include "gpu/GLES2/gl2extchromium.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::Mock;
+using ::testing::_;
+using ::testing::AtLeast;
+using ::testing::AnyNumber;
+
+namespace cc {
+namespace {
+
+class MockLayerTreeHost : public LayerTreeHost {
+ public:
+ explicit MockLayerTreeHost(LayerTreeHostClient* client)
+ : LayerTreeHost(client, LayerTreeSettings()) {
+ Initialize(NULL);
+ }
+
+ MOCK_METHOD0(AcquireLayerTextures, void());
+ MOCK_METHOD0(SetNeedsCommit, void());
+ MOCK_METHOD0(SetNeedsUpdateLayers, void());
+ MOCK_METHOD1(StartRateLimiter, void(WebKit::WebGraphicsContext3D* context));
+ MOCK_METHOD1(StopRateLimiter, void(WebKit::WebGraphicsContext3D* context));
+};
+
+class TextureLayerTest : public testing::Test {
+ public:
+ TextureLayerTest()
+ : fake_client_(
+ FakeLayerTreeHostClient(FakeLayerTreeHostClient::DIRECT_3D)),
+ host_impl_(&proxy_) {}
+
+ protected:
+ virtual void SetUp() {
+ layer_tree_host_.reset(new MockLayerTreeHost(&fake_client_));
+ }
+
+ virtual void TearDown() {
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+ EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(AnyNumber());
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AnyNumber());
+
+ layer_tree_host_->SetRootLayer(NULL);
+ layer_tree_host_.reset();
+ }
+
+ scoped_ptr<MockLayerTreeHost> layer_tree_host_;
+ FakeImplProxy proxy_;
+ FakeLayerTreeHostClient fake_client_;
+ FakeLayerTreeHostImpl host_impl_;
+};
+
+TEST_F(TextureLayerTest, SyncImplWhenChangingTextureId) {
+ scoped_refptr<TextureLayer> test_layer = TextureLayer::Create(NULL);
+ ASSERT_TRUE(test_layer.get());
+
+ EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(AnyNumber());
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AnyNumber());
+ layer_tree_host_->SetRootLayer(test_layer);
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+ EXPECT_EQ(test_layer->layer_tree_host(), layer_tree_host_.get());
+
+ EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(0);
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1));
+ test_layer->SetTextureId(1);
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+
+ EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(AtLeast(1));
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1));
+ test_layer->SetTextureId(2);
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+
+ EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(AtLeast(1));
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1));
+ test_layer->SetTextureId(0);
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+}
+
+TEST_F(TextureLayerTest, SyncImplWhenDrawing) {
+ gfx::RectF dirty_rect(0.f, 0.f, 1.f, 1.f);
+
+ scoped_refptr<TextureLayer> test_layer = TextureLayer::Create(NULL);
+ ASSERT_TRUE(test_layer.get());
+ scoped_ptr<TextureLayerImpl> impl_layer;
+ impl_layer = TextureLayerImpl::Create(host_impl_.active_tree(), 1, false);
+ ASSERT_TRUE(impl_layer);
+
+ EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(AnyNumber());
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AnyNumber());
+ layer_tree_host_->SetRootLayer(test_layer);
+ test_layer->SetTextureId(1);
+ test_layer->SetIsDrawable(true);
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+ EXPECT_EQ(test_layer->layer_tree_host(), layer_tree_host_.get());
+
+ EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(1);
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(0);
+ test_layer->WillModifyTexture();
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+
+ EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(0);
+ EXPECT_CALL(*layer_tree_host_, SetNeedsUpdateLayers()).Times(1);
+ test_layer->SetNeedsDisplayRect(dirty_rect);
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+
+ EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(0);
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(1);
+ test_layer->PushPropertiesTo(impl_layer.get()); // fake commit
+ test_layer->SetIsDrawable(false);
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+
+ // Verify that non-drawable layers don't signal the compositor,
+ // except for the first draw after last commit, which must acquire
+ // the texture.
+ EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(1);
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(0);
+ test_layer->WillModifyTexture();
+ test_layer->SetNeedsDisplayRect(dirty_rect);
+ test_layer->PushPropertiesTo(impl_layer.get()); // fake commit
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+
+ // Second draw with layer in non-drawable state: no texture
+ // acquisition.
+ EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(0);
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(0);
+ test_layer->WillModifyTexture();
+ test_layer->SetNeedsDisplayRect(dirty_rect);
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+}
+
+TEST_F(TextureLayerTest, SyncImplWhenRemovingFromTree) {
+ scoped_refptr<Layer> root_layer = Layer::Create();
+ ASSERT_TRUE(root_layer.get());
+ scoped_refptr<Layer> child_layer = Layer::Create();
+ ASSERT_TRUE(child_layer.get());
+ root_layer->AddChild(child_layer);
+ scoped_refptr<TextureLayer> test_layer = TextureLayer::Create(NULL);
+ ASSERT_TRUE(test_layer.get());
+ test_layer->SetTextureId(0);
+ child_layer->AddChild(test_layer);
+
+ EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(AnyNumber());
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AnyNumber());
+ layer_tree_host_->SetRootLayer(root_layer);
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+
+ EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(0);
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1));
+ test_layer->RemoveFromParent();
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+
+ EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(0);
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1));
+ child_layer->AddChild(test_layer);
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+
+ EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(0);
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1));
+ test_layer->SetTextureId(1);
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+
+ EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(AtLeast(1));
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1));
+ test_layer->RemoveFromParent();
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+}
+
+TEST_F(TextureLayerTest, CheckPropertyChangeCausesCorrectBehavior) {
+ scoped_refptr<TextureLayer> test_layer = TextureLayer::Create(NULL);
+ layer_tree_host_->SetRootLayer(test_layer);
+
+ // Test properties that should call SetNeedsCommit. All properties need to
+ // be set to new values in order for SetNeedsCommit to be called.
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetFlipped(false));
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetUV(
+ gfx::PointF(0.25f, 0.25f), gfx::PointF(0.75f, 0.75f)));
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetVertexOpacity(
+ 0.5f, 0.5f, 0.5f, 0.5f));
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetPremultipliedAlpha(false));
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetBlendBackgroundColor(true));
+ EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetTextureId(1));
+
+ // Calling SetTextureId can call AcquireLayerTextures.
+ EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(AnyNumber());
+}
+
+TEST_F(TextureLayerTest, VisibleContentOpaqueRegion) {
+ const gfx::Size layer_bounds(100, 100);
+ const gfx::Rect layer_rect(layer_bounds);
+ const Region layer_region(layer_rect);
+
+ scoped_refptr<TextureLayer> layer = TextureLayer::Create(NULL);
+ layer->SetBounds(layer_bounds);
+ layer->draw_properties().visible_content_rect = layer_rect;
+ layer->SetBlendBackgroundColor(true);
+
+ // Verify initial conditions.
+ EXPECT_FALSE(layer->contents_opaque());
+ EXPECT_EQ(0u, layer->background_color());
+ EXPECT_EQ(Region().ToString(),
+ layer->VisibleContentOpaqueRegion().ToString());
+
+ // Opaque background.
+ layer->SetBackgroundColor(SK_ColorWHITE);
+ EXPECT_EQ(layer_region.ToString(),
+ layer->VisibleContentOpaqueRegion().ToString());
+
+ // Transparent background.
+ layer->SetBackgroundColor(SkColorSetARGB(100, 255, 255, 255));
+ EXPECT_EQ(Region().ToString(),
+ layer->VisibleContentOpaqueRegion().ToString());
+}
+
+class FakeTextureLayerClient : public TextureLayerClient {
+ public:
+ FakeTextureLayerClient() : context_(TestWebGraphicsContext3D::Create()) {}
+
+ virtual unsigned PrepareTexture() OVERRIDE {
+ return 0;
+ }
+
+ virtual WebKit::WebGraphicsContext3D* Context3d() OVERRIDE {
+ return context_.get();
+ }
+
+ virtual bool PrepareTextureMailbox(TextureMailbox* mailbox,
+ bool use_shared_memory) OVERRIDE {
+ *mailbox = TextureMailbox();
+ return true;
+ }
+
+ private:
+ scoped_ptr<TestWebGraphicsContext3D> context_;
+ DISALLOW_COPY_AND_ASSIGN(FakeTextureLayerClient);
+};
+
+TEST_F(TextureLayerTest, RateLimiter) {
+ FakeTextureLayerClient client;
+ scoped_refptr<TextureLayer> test_layer = TextureLayer::CreateForMailbox(
+ &client);
+ test_layer->SetIsDrawable(true);
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AnyNumber());
+ layer_tree_host_->SetRootLayer(test_layer);
+
+ // Don't rate limit until we invalidate.
+ EXPECT_CALL(*layer_tree_host_, StartRateLimiter(_)).Times(0);
+ test_layer->SetRateLimitContext(true);
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+
+ // Do rate limit after we invalidate.
+ EXPECT_CALL(*layer_tree_host_, StartRateLimiter(client.Context3d()));
+ test_layer->SetNeedsDisplay();
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+
+ // Stop rate limiter when we don't want it any more.
+ EXPECT_CALL(*layer_tree_host_, StopRateLimiter(client.Context3d()));
+ test_layer->SetRateLimitContext(false);
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+
+ // Or we clear the client.
+ test_layer->SetRateLimitContext(true);
+ EXPECT_CALL(*layer_tree_host_, StopRateLimiter(client.Context3d()));
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AnyNumber());
+ test_layer->ClearClient();
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+
+ // Reset to a layer with a client, that started the rate limiter.
+ test_layer = TextureLayer::CreateForMailbox(
+ &client);
+ test_layer->SetIsDrawable(true);
+ test_layer->SetRateLimitContext(true);
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AnyNumber());
+ layer_tree_host_->SetRootLayer(test_layer);
+ EXPECT_CALL(*layer_tree_host_, StartRateLimiter(_)).Times(0);
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+ EXPECT_CALL(*layer_tree_host_, StartRateLimiter(client.Context3d()));
+ test_layer->SetNeedsDisplay();
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+
+ // Stop rate limiter when we're removed from the tree.
+ EXPECT_CALL(*layer_tree_host_, StopRateLimiter(client.Context3d()));
+ layer_tree_host_->SetRootLayer(NULL);
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+}
+
+class MockMailboxCallback {
+ public:
+ MOCK_METHOD3(Release, void(const std::string& mailbox,
+ unsigned sync_point,
+ bool lost_resource));
+ MOCK_METHOD3(Release2, void(base::SharedMemory* shared_memory,
+ unsigned sync_point,
+ bool lost_resource));
+};
+
+struct CommonMailboxObjects {
+ CommonMailboxObjects()
+ : mailbox_name1_(64, '1'),
+ mailbox_name2_(64, '2'),
+ sync_point1_(1),
+ sync_point2_(2),
+ shared_memory_(new base::SharedMemory) {
+ release_mailbox1_ = base::Bind(&MockMailboxCallback::Release,
+ base::Unretained(&mock_callback_),
+ mailbox_name1_);
+ release_mailbox2_ = base::Bind(&MockMailboxCallback::Release,
+ base::Unretained(&mock_callback_),
+ mailbox_name2_);
+ gpu::Mailbox m1;
+ m1.SetName(reinterpret_cast<const int8*>(mailbox_name1_.data()));
+ mailbox1_ = TextureMailbox(m1, release_mailbox1_, sync_point1_);
+ gpu::Mailbox m2;
+ m2.SetName(reinterpret_cast<const int8*>(mailbox_name2_.data()));
+ mailbox2_ = TextureMailbox(m2, release_mailbox2_, sync_point2_);
+
+ gfx::Size size(128, 128);
+ EXPECT_TRUE(shared_memory_->CreateAndMapAnonymous(4 * size.GetArea()));
+ release_mailbox3_ = base::Bind(&MockMailboxCallback::Release2,
+ base::Unretained(&mock_callback_),
+ shared_memory_.get());
+ mailbox3_ = TextureMailbox(shared_memory_.get(), size, release_mailbox3_);
+ }
+
+ std::string mailbox_name1_;
+ std::string mailbox_name2_;
+ MockMailboxCallback mock_callback_;
+ TextureMailbox::ReleaseCallback release_mailbox1_;
+ TextureMailbox::ReleaseCallback release_mailbox2_;
+ TextureMailbox::ReleaseCallback release_mailbox3_;
+ TextureMailbox mailbox1_;
+ TextureMailbox mailbox2_;
+ TextureMailbox mailbox3_;
+ unsigned sync_point1_;
+ unsigned sync_point2_;
+ scoped_ptr<base::SharedMemory> shared_memory_;
+};
+
+class TextureLayerWithMailboxTest : public TextureLayerTest {
+ protected:
+ virtual void TearDown() {
+ Mock::VerifyAndClearExpectations(&test_data_.mock_callback_);
+ EXPECT_CALL(test_data_.mock_callback_,
+ Release(test_data_.mailbox_name1_,
+ test_data_.sync_point1_,
+ false)).Times(1);
+ TextureLayerTest::TearDown();
+ }
+
+ CommonMailboxObjects test_data_;
+};
+
+TEST_F(TextureLayerWithMailboxTest, ReplaceMailboxOnMainThreadBeforeCommit) {
+ scoped_refptr<TextureLayer> test_layer = TextureLayer::CreateForMailbox(NULL);
+ ASSERT_TRUE(test_layer.get());
+
+ EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(0);
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AnyNumber());
+ layer_tree_host_->SetRootLayer(test_layer);
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+
+ EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(0);
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1));
+ test_layer->SetTextureMailbox(test_data_.mailbox1_);
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+
+ EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(0);
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1));
+ EXPECT_CALL(test_data_.mock_callback_,
+ Release(test_data_.mailbox_name1_,
+ test_data_.sync_point1_,
+ false))
+ .Times(1);
+ test_layer->SetTextureMailbox(test_data_.mailbox2_);
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+ Mock::VerifyAndClearExpectations(&test_data_.mock_callback_);
+
+ EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(0);
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1));
+ EXPECT_CALL(test_data_.mock_callback_,
+ Release(test_data_.mailbox_name2_,
+ test_data_.sync_point2_,
+ false))
+ .Times(1);
+ test_layer->SetTextureMailbox(TextureMailbox());
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+ Mock::VerifyAndClearExpectations(&test_data_.mock_callback_);
+
+ test_layer->SetTextureMailbox(test_data_.mailbox3_);
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+ Mock::VerifyAndClearExpectations(&test_data_.mock_callback_);
+
+ EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(0);
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1));
+ EXPECT_CALL(test_data_.mock_callback_,
+ Release2(test_data_.shared_memory_.get(),
+ 0, false))
+ .Times(1);
+ test_layer->SetTextureMailbox(TextureMailbox());
+ Mock::VerifyAndClearExpectations(layer_tree_host_.get());
+ Mock::VerifyAndClearExpectations(&test_data_.mock_callback_);
+
+ // Test destructor.
+ EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1));
+ test_layer->SetTextureMailbox(test_data_.mailbox1_);
+}
+
+class TextureLayerImplWithMailboxThreadedCallback : public LayerTreeTest {
+ public:
+ TextureLayerImplWithMailboxThreadedCallback()
+ : callback_count_(0),
+ commit_count_(0) {}
+
+ // Make sure callback is received on main and doesn't block the impl thread.
+ void ReleaseCallback(unsigned sync_point, bool lost_resource) {
+ EXPECT_EQ(true, proxy()->IsMainThread());
+ EXPECT_FALSE(lost_resource);
+ ++callback_count_;
+ }
+
+ void SetMailbox(char mailbox_char) {
+ TextureMailbox mailbox(
+ std::string(64, mailbox_char),
+ base::Bind(
+ &TextureLayerImplWithMailboxThreadedCallback::ReleaseCallback,
+ base::Unretained(this)));
+ layer_->SetTextureMailbox(mailbox);
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ gfx::Size bounds(100, 100);
+ root_ = Layer::Create();
+ root_->SetAnchorPoint(gfx::PointF());
+ root_->SetBounds(bounds);
+
+ layer_ = TextureLayer::CreateForMailbox(NULL);
+ layer_->SetIsDrawable(true);
+ layer_->SetAnchorPoint(gfx::PointF());
+ layer_->SetBounds(bounds);
+
+ root_->AddChild(layer_);
+ layer_tree_host()->SetRootLayer(root_);
+ layer_tree_host()->SetViewportSize(bounds);
+ SetMailbox('1');
+ EXPECT_EQ(0, callback_count_);
+
+ // Case #1: change mailbox before the commit. The old mailbox should be
+ // released immediately.
+ SetMailbox('2');
+ EXPECT_EQ(1, callback_count_);
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommit() OVERRIDE {
+ ++commit_count_;
+ switch (commit_count_) {
+ case 1:
+ // Case #2: change mailbox after the commit (and draw), where the
+ // layer draws. The old mailbox should be released during the next
+ // commit.
+ SetMailbox('3');
+ EXPECT_EQ(1, callback_count_);
+ break;
+ case 2:
+ // Old mailbox was released, task was posted, but won't execute
+ // until this DidCommit returns.
+ // TODO(piman): fix this.
+ EXPECT_EQ(1, callback_count_);
+ layer_tree_host()->SetNeedsCommit();
+ break;
+ case 3:
+ EXPECT_EQ(2, callback_count_);
+ // Case #3: change mailbox when the layer doesn't draw. The old
+ // mailbox should be released during the next commit.
+ layer_->SetBounds(gfx::Size());
+ SetMailbox('4');
+ break;
+ case 4:
+ // Old mailbox was released, task was posted, but won't execute
+ // until this DidCommit returns.
+ // TODO(piman): fix this.
+ EXPECT_EQ(2, callback_count_);
+ layer_tree_host()->SetNeedsCommit();
+ break;
+ case 5:
+ EXPECT_EQ(3, callback_count_);
+ // Case #4: release mailbox that was committed but never drawn. The
+ // old mailbox should be released during the next commit.
+ layer_->SetTextureMailbox(TextureMailbox());
+ break;
+ case 6:
+ // Old mailbox was released, task was posted, but won't execute
+ // until this DidCommit returns.
+ // TODO(piman): fix this.
+ EXPECT_EQ(3, callback_count_);
+ layer_tree_host()->SetNeedsCommit();
+ break;
+ case 7:
+ EXPECT_EQ(4, callback_count_);
+ // Restore a mailbox for the next step.
+ SetMailbox('5');
+ break;
+ case 8:
+ // Case #5: remove layer from tree. Callback should *not* be called, the
+ // mailbox is returned to the main thread.
+ EXPECT_EQ(4, callback_count_);
+ layer_->RemoveFromParent();
+ break;
+ case 9:
+ // Mailbox was released to the main thread, task was posted, but won't
+ // execute until this DidCommit returns.
+ // TODO(piman): fix this.
+ EXPECT_EQ(4, callback_count_);
+ layer_tree_host()->SetNeedsCommit();
+ break;
+ case 10:
+ EXPECT_EQ(4, callback_count_);
+ // Resetting the mailbox will call the callback now.
+ layer_->SetTextureMailbox(TextureMailbox());
+ EXPECT_EQ(5, callback_count_);
+ EndTest();
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ int callback_count_;
+ int commit_count_;
+ scoped_refptr<Layer> root_;
+ scoped_refptr<TextureLayer> layer_;
+};
+
+SINGLE_AND_MULTI_THREAD_DIRECT_RENDERER_TEST_F(
+ TextureLayerImplWithMailboxThreadedCallback);
+
+class TextureLayerImplWithMailboxTest : public TextureLayerTest {
+ protected:
+ TextureLayerImplWithMailboxTest()
+ : fake_client_(
+ FakeLayerTreeHostClient(FakeLayerTreeHostClient::DIRECT_3D)) {}
+
+ virtual void SetUp() {
+ TextureLayerTest::SetUp();
+ layer_tree_host_.reset(new MockLayerTreeHost(&fake_client_));
+ EXPECT_TRUE(host_impl_.InitializeRenderer(CreateFakeOutputSurface()));
+ }
+
+ bool WillDraw(TextureLayerImpl* layer, DrawMode mode) {
+ bool will_draw = layer->WillDraw(
+ mode, host_impl_.active_tree()->resource_provider());
+ if (will_draw)
+ layer->DidDraw(host_impl_.active_tree()->resource_provider());
+ return will_draw;
+ }
+
+ CommonMailboxObjects test_data_;
+ FakeLayerTreeHostClient fake_client_;
+};
+
+// Test conditions for results of TextureLayerImpl::WillDraw under
+// different configurations of different mailbox, texture_id, and draw_mode.
+TEST_F(TextureLayerImplWithMailboxTest, TestWillDraw) {
+ EXPECT_CALL(test_data_.mock_callback_,
+ Release(test_data_.mailbox_name1_,
+ test_data_.sync_point1_,
+ false))
+ .Times(AnyNumber());
+ EXPECT_CALL(test_data_.mock_callback_,
+ Release2(test_data_.shared_memory_.get(), 0, false))
+ .Times(AnyNumber());
+ // Hardware mode.
+ {
+ scoped_ptr<TextureLayerImpl> impl_layer =
+ TextureLayerImpl::Create(host_impl_.active_tree(), 1, true);
+ impl_layer->SetTextureMailbox(test_data_.mailbox1_);
+ EXPECT_TRUE(WillDraw(impl_layer.get(), DRAW_MODE_HARDWARE));
+ }
+
+ {
+ scoped_ptr<TextureLayerImpl> impl_layer =
+ TextureLayerImpl::Create(host_impl_.active_tree(), 1, true);
+ impl_layer->SetTextureMailbox(TextureMailbox());
+ EXPECT_FALSE(WillDraw(impl_layer.get(), DRAW_MODE_HARDWARE));
+ }
+
+ {
+ // Software resource.
+ scoped_ptr<TextureLayerImpl> impl_layer =
+ TextureLayerImpl::Create(host_impl_.active_tree(), 1, true);
+ impl_layer->SetTextureMailbox(test_data_.mailbox3_);
+ EXPECT_TRUE(WillDraw(impl_layer.get(), DRAW_MODE_HARDWARE));
+ }
+
+ {
+ scoped_ptr<TextureLayerImpl> impl_layer =
+ TextureLayerImpl::Create(host_impl_.active_tree(), 1, false);
+ unsigned texture =
+ host_impl_.output_surface()->context3d()->createTexture();
+ impl_layer->set_texture_id(texture);
+ EXPECT_TRUE(WillDraw(impl_layer.get(), DRAW_MODE_HARDWARE));
+ }
+
+ {
+ scoped_ptr<TextureLayerImpl> impl_layer =
+ TextureLayerImpl::Create(host_impl_.active_tree(), 1, false);
+ impl_layer->set_texture_id(0);
+ EXPECT_FALSE(WillDraw(impl_layer.get(), DRAW_MODE_HARDWARE));
+ }
+
+ // Software mode.
+ {
+ scoped_ptr<TextureLayerImpl> impl_layer =
+ TextureLayerImpl::Create(host_impl_.active_tree(), 1, true);
+ impl_layer->SetTextureMailbox(test_data_.mailbox1_);
+ EXPECT_FALSE(WillDraw(impl_layer.get(), DRAW_MODE_SOFTWARE));
+ }
+
+ {
+ scoped_ptr<TextureLayerImpl> impl_layer =
+ TextureLayerImpl::Create(host_impl_.active_tree(), 1, true);
+ impl_layer->SetTextureMailbox(TextureMailbox());
+ EXPECT_FALSE(WillDraw(impl_layer.get(), DRAW_MODE_SOFTWARE));
+ }
+
+ {
+ // Software resource.
+ scoped_ptr<TextureLayerImpl> impl_layer =
+ TextureLayerImpl::Create(host_impl_.active_tree(), 1, true);
+ impl_layer->SetTextureMailbox(test_data_.mailbox3_);
+ EXPECT_TRUE(WillDraw(impl_layer.get(), DRAW_MODE_SOFTWARE));
+ }
+
+ {
+ scoped_ptr<TextureLayerImpl> impl_layer =
+ TextureLayerImpl::Create(host_impl_.active_tree(), 1, false);
+ unsigned texture =
+ host_impl_.output_surface()->context3d()->createTexture();
+ impl_layer->set_texture_id(texture);
+ EXPECT_FALSE(WillDraw(impl_layer.get(), DRAW_MODE_SOFTWARE));
+ }
+
+ {
+ scoped_ptr<TextureLayerImpl> impl_layer =
+ TextureLayerImpl::Create(host_impl_.active_tree(), 1, false);
+ impl_layer->set_texture_id(0);
+ EXPECT_FALSE(WillDraw(impl_layer.get(), DRAW_MODE_SOFTWARE));
+ }
+
+ // Resourceless software mode.
+ {
+ scoped_ptr<TextureLayerImpl> impl_layer =
+ TextureLayerImpl::Create(host_impl_.active_tree(), 1, true);
+ impl_layer->SetTextureMailbox(test_data_.mailbox1_);
+ EXPECT_FALSE(WillDraw(impl_layer.get(), DRAW_MODE_RESOURCELESS_SOFTWARE));
+ }
+
+ {
+ scoped_ptr<TextureLayerImpl> impl_layer =
+ TextureLayerImpl::Create(host_impl_.active_tree(), 1, false);
+ unsigned texture =
+ host_impl_.output_surface()->context3d()->createTexture();
+ impl_layer->set_texture_id(texture);
+ EXPECT_FALSE(WillDraw(impl_layer.get(), DRAW_MODE_RESOURCELESS_SOFTWARE));
+ }
+}
+
+TEST_F(TextureLayerImplWithMailboxTest, TestImplLayerCallbacks) {
+ host_impl_.CreatePendingTree();
+ scoped_ptr<TextureLayerImpl> pending_layer;
+ pending_layer = TextureLayerImpl::Create(host_impl_.pending_tree(), 1, true);
+ ASSERT_TRUE(pending_layer);
+
+ scoped_ptr<LayerImpl> active_layer(
+ pending_layer->CreateLayerImpl(host_impl_.active_tree()));
+ ASSERT_TRUE(active_layer);
+
+ pending_layer->SetTextureMailbox(test_data_.mailbox1_);
+
+ // Test multiple commits without an activation.
+ EXPECT_CALL(test_data_.mock_callback_,
+ Release(test_data_.mailbox_name1_,
+ test_data_.sync_point1_,
+ false))
+ .Times(1);
+ pending_layer->SetTextureMailbox(test_data_.mailbox2_);
+ Mock::VerifyAndClearExpectations(&test_data_.mock_callback_);
+
+ // Test callback after activation.
+ pending_layer->PushPropertiesTo(active_layer.get());
+ active_layer->DidBecomeActive();
+
+ EXPECT_CALL(test_data_.mock_callback_, Release(_, _, _)).Times(0);
+ pending_layer->SetTextureMailbox(test_data_.mailbox1_);
+ Mock::VerifyAndClearExpectations(&test_data_.mock_callback_);
+
+ EXPECT_CALL(test_data_.mock_callback_,
+ Release(test_data_.mailbox_name2_, _, false))
+ .Times(1);
+ pending_layer->PushPropertiesTo(active_layer.get());
+ active_layer->DidBecomeActive();
+ Mock::VerifyAndClearExpectations(&test_data_.mock_callback_);
+
+ // Test resetting the mailbox.
+ EXPECT_CALL(test_data_.mock_callback_,
+ Release(test_data_.mailbox_name1_, _, false))
+ .Times(1);
+ pending_layer->SetTextureMailbox(TextureMailbox());
+ pending_layer->PushPropertiesTo(active_layer.get());
+ active_layer->DidBecomeActive();
+ Mock::VerifyAndClearExpectations(&test_data_.mock_callback_);
+
+ // Test destructor.
+ EXPECT_CALL(test_data_.mock_callback_,
+ Release(test_data_.mailbox_name1_,
+ test_data_.sync_point1_,
+ false))
+ .Times(1);
+ pending_layer->SetTextureMailbox(test_data_.mailbox1_);
+}
+
+TEST_F(TextureLayerImplWithMailboxTest,
+ TestDestructorCallbackOnCreatedResource) {
+ scoped_ptr<TextureLayerImpl> impl_layer;
+ impl_layer = TextureLayerImpl::Create(host_impl_.active_tree(), 1, true);
+ ASSERT_TRUE(impl_layer);
+
+ EXPECT_CALL(test_data_.mock_callback_,
+ Release(test_data_.mailbox_name1_, _, false))
+ .Times(1);
+ impl_layer->SetTextureMailbox(test_data_.mailbox1_);
+ impl_layer->DidBecomeActive();
+ EXPECT_TRUE(impl_layer->WillDraw(
+ DRAW_MODE_HARDWARE, host_impl_.active_tree()->resource_provider()));
+ impl_layer->DidDraw(host_impl_.active_tree()->resource_provider());
+ impl_layer->SetTextureMailbox(TextureMailbox());
+}
+
+TEST_F(TextureLayerImplWithMailboxTest, TestCallbackOnInUseResource) {
+ ResourceProvider* provider = host_impl_.active_tree()->resource_provider();
+ ResourceProvider::ResourceId id =
+ provider->CreateResourceFromTextureMailbox(test_data_.mailbox1_);
+ provider->AllocateForTesting(id);
+
+ // Transfer some resources to the parent.
+ ResourceProvider::ResourceIdArray resource_ids_to_transfer;
+ resource_ids_to_transfer.push_back(id);
+ TransferableResourceArray list;
+ provider->PrepareSendToParent(resource_ids_to_transfer, &list);
+ EXPECT_TRUE(provider->InUseByConsumer(id));
+ EXPECT_CALL(test_data_.mock_callback_, Release(_, _, _)).Times(0);
+ provider->DeleteResource(id);
+ Mock::VerifyAndClearExpectations(&test_data_.mock_callback_);
+ EXPECT_CALL(test_data_.mock_callback_,
+ Release(test_data_.mailbox_name1_, _, false))
+ .Times(1);
+ provider->ReceiveFromParent(list);
+}
+
+// Check that ClearClient correctly clears the state so that the impl side
+// doesn't try to use a texture that could have been destroyed.
+class TextureLayerClientTest
+ : public LayerTreeTest,
+ public TextureLayerClient {
+ public:
+ TextureLayerClientTest()
+ : context_(NULL),
+ texture_(0),
+ commit_count_(0),
+ expected_used_textures_on_draw_(0),
+ expected_used_textures_on_commit_(0) {}
+
+ virtual scoped_ptr<OutputSurface> CreateOutputSurface(bool fallback)
+ OVERRIDE {
+ scoped_ptr<TestWebGraphicsContext3D> context(
+ TestWebGraphicsContext3D::Create());
+ context_ = context.get();
+ texture_ = context->createTexture();
+ return FakeOutputSurface::Create3d(
+ context.PassAs<WebKit::WebGraphicsContext3D>()).PassAs<OutputSurface>();
+ }
+
+ virtual unsigned PrepareTexture() OVERRIDE {
+ return texture_;
+ }
+
+ virtual WebKit::WebGraphicsContext3D* Context3d() OVERRIDE {
+ return context_;
+ }
+
+ virtual bool PrepareTextureMailbox(
+ cc::TextureMailbox* mailbox, bool use_shared_memory) OVERRIDE {
+ return false;
+ }
+
+ virtual void SetupTree() OVERRIDE {
+ scoped_refptr<Layer> root = Layer::Create();
+ root->SetBounds(gfx::Size(10, 10));
+ root->SetAnchorPoint(gfx::PointF());
+ root->SetIsDrawable(true);
+
+ texture_layer_ = TextureLayer::Create(this);
+ texture_layer_->SetBounds(gfx::Size(10, 10));
+ texture_layer_->SetAnchorPoint(gfx::PointF());
+ texture_layer_->SetIsDrawable(true);
+ root->AddChild(texture_layer_);
+
+ layer_tree_host()->SetRootLayer(root);
+ LayerTreeTest::SetupTree();
+ {
+ base::AutoLock lock(lock_);
+ expected_used_textures_on_commit_ = 1;
+ }
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ ++commit_count_;
+ switch (commit_count_) {
+ case 1:
+ texture_layer_->ClearClient();
+ texture_layer_->SetNeedsDisplay();
+ {
+ base::AutoLock lock(lock_);
+ expected_used_textures_on_commit_ = 0;
+ }
+ texture_ = 0;
+ break;
+ case 2:
+ EndTest();
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+
+ virtual void BeginCommitOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ base::AutoLock lock(lock_);
+ expected_used_textures_on_draw_ = expected_used_textures_on_commit_;
+ }
+
+ virtual bool PrepareToDrawOnThread(LayerTreeHostImpl* host_impl,
+ LayerTreeHostImpl::FrameData* frame_data,
+ bool result) OVERRIDE {
+ context_->ResetUsedTextures();
+ return true;
+ }
+
+ virtual void SwapBuffersOnThread(LayerTreeHostImpl* host_impl,
+ bool result) OVERRIDE {
+ ASSERT_TRUE(result);
+ EXPECT_EQ(expected_used_textures_on_draw_, context_->NumUsedTextures());
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ scoped_refptr<TextureLayer> texture_layer_;
+ TestWebGraphicsContext3D* context_;
+ unsigned texture_;
+ int commit_count_;
+
+ // Used only on thread.
+ unsigned expected_used_textures_on_draw_;
+
+ // Used on either thread, protected by lock_.
+ base::Lock lock_;
+ unsigned expected_used_textures_on_commit_;
+};
+
+// The TextureLayerClient does not use mailboxes, so can't use a delegating
+// renderer.
+SINGLE_AND_MULTI_THREAD_DIRECT_RENDERER_TEST_F(TextureLayerClientTest);
+
+// Test recovering from a lost context.
+class TextureLayerLostContextTest
+ : public LayerTreeTest,
+ public TextureLayerClient {
+ public:
+ TextureLayerLostContextTest()
+ : texture_(0),
+ draw_count_(0) {}
+
+ virtual scoped_ptr<OutputSurface> CreateOutputSurface(bool fallback)
+ OVERRIDE {
+ texture_context_ = TestWebGraphicsContext3D::Create();
+ texture_ = texture_context_->createTexture();
+ return CreateFakeOutputSurface();
+ }
+
+ virtual unsigned PrepareTexture() OVERRIDE {
+ if (draw_count_ == 0) {
+ texture_context_->loseContextCHROMIUM(GL_GUILTY_CONTEXT_RESET_ARB,
+ GL_INNOCENT_CONTEXT_RESET_ARB);
+ }
+ return texture_;
+ }
+
+ virtual WebKit::WebGraphicsContext3D* Context3d() OVERRIDE {
+ return texture_context_.get();
+ }
+
+ virtual bool PrepareTextureMailbox(
+ cc::TextureMailbox* mailbox, bool use_shared_memory) OVERRIDE {
+ return false;
+ }
+
+ virtual void SetupTree() OVERRIDE {
+ scoped_refptr<Layer> root = Layer::Create();
+ root->SetBounds(gfx::Size(10, 10));
+ root->SetIsDrawable(true);
+
+ texture_layer_ = TextureLayer::Create(this);
+ texture_layer_->SetBounds(gfx::Size(10, 10));
+ texture_layer_->SetIsDrawable(true);
+ root->AddChild(texture_layer_);
+
+ layer_tree_host()->SetRootLayer(root);
+ LayerTreeTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual bool PrepareToDrawOnThread(LayerTreeHostImpl* host_impl,
+ LayerTreeHostImpl::FrameData* frame_data,
+ bool result) OVERRIDE {
+ LayerImpl* root = host_impl->RootLayer();
+ TextureLayerImpl* texture_layer =
+ static_cast<TextureLayerImpl*>(root->children()[0]);
+ if (++draw_count_ == 1)
+ EXPECT_EQ(0u, texture_layer->texture_id());
+ else
+ EXPECT_EQ(texture_, texture_layer->texture_id());
+ return true;
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ scoped_refptr<TextureLayer> texture_layer_;
+ scoped_ptr<TestWebGraphicsContext3D> texture_context_;
+ unsigned texture_;
+ int draw_count_;
+};
+
+SINGLE_AND_MULTI_THREAD_DIRECT_RENDERER_TEST_F(TextureLayerLostContextTest);
+
+class TextureLayerWithMailboxMainThreadDeleted : public LayerTreeTest {
+ public:
+ void ReleaseCallback(unsigned sync_point, bool lost_resource) {
+ EXPECT_EQ(true, proxy()->IsMainThread());
+ EXPECT_FALSE(lost_resource);
+ ++callback_count_;
+ EndTest();
+ }
+
+ void SetMailbox(char mailbox_char) {
+ TextureMailbox mailbox(
+ std::string(64, mailbox_char),
+ base::Bind(
+ &TextureLayerWithMailboxMainThreadDeleted::ReleaseCallback,
+ base::Unretained(this)));
+ layer_->SetTextureMailbox(mailbox);
+ }
+
+ virtual void SetupTree() OVERRIDE {
+ gfx::Size bounds(100, 100);
+ root_ = Layer::Create();
+ root_->SetAnchorPoint(gfx::PointF());
+ root_->SetBounds(bounds);
+
+ layer_ = TextureLayer::CreateForMailbox(NULL);
+ layer_->SetIsDrawable(true);
+ layer_->SetAnchorPoint(gfx::PointF());
+ layer_->SetBounds(bounds);
+
+ root_->AddChild(layer_);
+ layer_tree_host()->SetRootLayer(root_);
+ layer_tree_host()->SetViewportSize(bounds);
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ callback_count_ = 0;
+
+ // Set the mailbox on the main thread.
+ SetMailbox('1');
+ EXPECT_EQ(0, callback_count_);
+
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ switch (layer_tree_host()->source_frame_number()) {
+ case 1:
+ // Delete the TextureLayer on the main thread while the mailbox is in
+ // the impl tree.
+ layer_->RemoveFromParent();
+ layer_ = NULL;
+ break;
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ EXPECT_EQ(1, callback_count_);
+ }
+
+ private:
+ int callback_count_;
+ scoped_refptr<Layer> root_;
+ scoped_refptr<TextureLayer> layer_;
+};
+
+SINGLE_AND_MULTI_THREAD_DIRECT_RENDERER_TEST_F(
+ TextureLayerWithMailboxMainThreadDeleted);
+
+class TextureLayerWithMailboxImplThreadDeleted : public LayerTreeTest {
+ public:
+ void ReleaseCallback(unsigned sync_point, bool lost_resource) {
+ EXPECT_EQ(true, proxy()->IsMainThread());
+ EXPECT_FALSE(lost_resource);
+ ++callback_count_;
+ EndTest();
+ }
+
+ void SetMailbox(char mailbox_char) {
+ TextureMailbox mailbox(
+ std::string(64, mailbox_char),
+ base::Bind(
+ &TextureLayerWithMailboxImplThreadDeleted::ReleaseCallback,
+ base::Unretained(this)));
+ layer_->SetTextureMailbox(mailbox);
+ }
+
+ virtual void SetupTree() OVERRIDE {
+ gfx::Size bounds(100, 100);
+ root_ = Layer::Create();
+ root_->SetAnchorPoint(gfx::PointF());
+ root_->SetBounds(bounds);
+
+ layer_ = TextureLayer::CreateForMailbox(NULL);
+ layer_->SetIsDrawable(true);
+ layer_->SetAnchorPoint(gfx::PointF());
+ layer_->SetBounds(bounds);
+
+ root_->AddChild(layer_);
+ layer_tree_host()->SetRootLayer(root_);
+ layer_tree_host()->SetViewportSize(bounds);
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ callback_count_ = 0;
+
+ // Set the mailbox on the main thread.
+ SetMailbox('1');
+ EXPECT_EQ(0, callback_count_);
+
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ switch (layer_tree_host()->source_frame_number()) {
+ case 1:
+ // Remove the TextureLayer on the main thread while the mailbox is in
+ // the impl tree, but don't delete the TextureLayer until after the impl
+ // tree side is deleted.
+ layer_->RemoveFromParent();
+ break;
+ case 2:
+ layer_ = NULL;
+ break;
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ EXPECT_EQ(1, callback_count_);
+ }
+
+ private:
+ int callback_count_;
+ scoped_refptr<Layer> root_;
+ scoped_refptr<TextureLayer> layer_;
+};
+
+SINGLE_AND_MULTI_THREAD_DIRECT_RENDERER_TEST_F(
+ TextureLayerWithMailboxImplThreadDeleted);
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/layers/tiled_layer.cc b/chromium/cc/layers/tiled_layer.cc
new file mode 100644
index 00000000000..bac12f0d422
--- /dev/null
+++ b/chromium/cc/layers/tiled_layer.cc
@@ -0,0 +1,901 @@
+// Copyright 2011 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 "cc/layers/tiled_layer.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/auto_reset.h"
+#include "base/basictypes.h"
+#include "build/build_config.h"
+#include "cc/debug/overdraw_metrics.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/layers/tiled_layer_impl.h"
+#include "cc/resources/layer_updater.h"
+#include "cc/resources/prioritized_resource.h"
+#include "cc/resources/priority_calculator.h"
+#include "cc/trees/layer_tree_host.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "ui/gfx/rect_conversions.h"
+
+namespace cc {
+
+// Maximum predictive expansion of the visible area.
+static const int kMaxPredictiveTilesCount = 2;
+
+// Number of rows/columns of tiles to pre-paint.
+// We should increase these further as all textures are
+// prioritized and we insure performance doesn't suffer.
+static const int kPrepaintRows = 4;
+static const int kPrepaintColumns = 2;
+
+class UpdatableTile : public LayerTilingData::Tile {
+ public:
+ static scoped_ptr<UpdatableTile> Create(
+ scoped_ptr<LayerUpdater::Resource> updater_resource) {
+ return make_scoped_ptr(new UpdatableTile(updater_resource.Pass()));
+ }
+
+ LayerUpdater::Resource* updater_resource() { return updater_resource_.get(); }
+ PrioritizedResource* managed_resource() {
+ return updater_resource_->texture();
+ }
+
+ bool is_dirty() const { return !dirty_rect.IsEmpty(); }
+
+ // Reset update state for the current frame. This should occur before painting
+ // for all layers. Since painting one layer can invalidate another layer after
+ // it has already painted, mark all non-dirty tiles as valid before painting
+ // such that invalidations during painting won't prevent them from being
+ // pushed.
+ void ResetUpdateState() {
+ update_rect = gfx::Rect();
+ occluded = false;
+ partial_update = false;
+ valid_for_frame = !is_dirty();
+ }
+
+ // This promises to update the tile and therefore also guarantees the tile
+ // will be valid for this frame. dirty_rect is copied into update_rect so we
+ // can continue to track re-entrant invalidations that occur during painting.
+ void MarkForUpdate() {
+ valid_for_frame = true;
+ update_rect = dirty_rect;
+ dirty_rect = gfx::Rect();
+ }
+
+ gfx::Rect dirty_rect;
+ gfx::Rect update_rect;
+ bool partial_update;
+ bool valid_for_frame;
+ bool occluded;
+
+ private:
+ explicit UpdatableTile(scoped_ptr<LayerUpdater::Resource> updater_resource)
+ : partial_update(false),
+ valid_for_frame(false),
+ occluded(false),
+ updater_resource_(updater_resource.Pass()) {}
+
+ scoped_ptr<LayerUpdater::Resource> updater_resource_;
+
+ DISALLOW_COPY_AND_ASSIGN(UpdatableTile);
+};
+
+TiledLayer::TiledLayer()
+ : ContentsScalingLayer(),
+ texture_format_(GL_INVALID_ENUM),
+ skips_draw_(false),
+ failed_update_(false),
+ tiling_option_(AUTO_TILE) {
+ tiler_ =
+ LayerTilingData::Create(gfx::Size(), LayerTilingData::HAS_BORDER_TEXELS);
+}
+
+TiledLayer::~TiledLayer() {}
+
+scoped_ptr<LayerImpl> TiledLayer::CreateLayerImpl(LayerTreeImpl* tree_impl) {
+ return TiledLayerImpl::Create(tree_impl, id()).PassAs<LayerImpl>();
+}
+
+void TiledLayer::UpdateTileSizeAndTilingOption() {
+ DCHECK(layer_tree_host());
+
+ gfx::Size default_tile_size = layer_tree_host()->settings().default_tile_size;
+ gfx::Size max_untiled_layer_size =
+ layer_tree_host()->settings().max_untiled_layer_size;
+ int layer_width = content_bounds().width();
+ int layer_height = content_bounds().height();
+
+ gfx::Size tile_size(std::min(default_tile_size.width(), layer_width),
+ std::min(default_tile_size.height(), layer_height));
+
+ // Tile if both dimensions large, or any one dimension large and the other
+ // extends into a second tile but the total layer area isn't larger than that
+ // of the largest possible untiled layer. This heuristic allows for long
+ // skinny layers (e.g. scrollbars) that are Nx1 tiles to minimize wasted
+ // texture space but still avoids creating very large tiles.
+ bool any_dimension_large = layer_width > max_untiled_layer_size.width() ||
+ layer_height > max_untiled_layer_size.height();
+ bool any_dimension_one_tile =
+ (layer_width <= default_tile_size.width() ||
+ layer_height <= default_tile_size.height()) &&
+ (layer_width * layer_height) <= (max_untiled_layer_size.width() *
+ max_untiled_layer_size.height());
+ bool auto_tiled = any_dimension_large && !any_dimension_one_tile;
+
+ bool is_tiled;
+ if (tiling_option_ == ALWAYS_TILE)
+ is_tiled = true;
+ else if (tiling_option_ == NEVER_TILE)
+ is_tiled = false;
+ else
+ is_tiled = auto_tiled;
+
+ gfx::Size requested_size = is_tiled ? tile_size : content_bounds();
+ const int max_size =
+ layer_tree_host()->GetRendererCapabilities().max_texture_size;
+ requested_size.SetToMin(gfx::Size(max_size, max_size));
+ SetTileSize(requested_size);
+}
+
+void TiledLayer::UpdateBounds() {
+ gfx::Size old_bounds = tiler_->bounds();
+ gfx::Size new_bounds = content_bounds();
+ if (old_bounds == new_bounds)
+ return;
+ tiler_->SetBounds(new_bounds);
+
+ // Invalidate any areas that the new bounds exposes.
+ Region old_region = gfx::Rect(old_bounds);
+ Region new_region = gfx::Rect(new_bounds);
+ new_region.Subtract(old_region);
+ for (Region::Iterator new_rects(new_region);
+ new_rects.has_rect();
+ new_rects.next())
+ InvalidateContentRect(new_rects.rect());
+}
+
+void TiledLayer::SetTileSize(gfx::Size size) { tiler_->SetTileSize(size); }
+
+void TiledLayer::SetBorderTexelOption(
+ LayerTilingData::BorderTexelOption border_texel_option) {
+ tiler_->SetBorderTexelOption(border_texel_option);
+}
+
+bool TiledLayer::DrawsContent() const {
+ if (!ContentsScalingLayer::DrawsContent())
+ return false;
+
+ bool has_more_than_one_tile =
+ tiler_->num_tiles_x() > 1 || tiler_->num_tiles_y() > 1;
+ if (tiling_option_ == NEVER_TILE && has_more_than_one_tile)
+ return false;
+
+ return true;
+}
+
+void TiledLayer::ReduceMemoryUsage() {
+ if (Updater())
+ Updater()->ReduceMemoryUsage();
+}
+
+void TiledLayer::SetIsMask(bool is_mask) {
+ set_tiling_option(is_mask ? NEVER_TILE : AUTO_TILE);
+}
+
+void TiledLayer::PushPropertiesTo(LayerImpl* layer) {
+ ContentsScalingLayer::PushPropertiesTo(layer);
+
+ TiledLayerImpl* tiled_layer = static_cast<TiledLayerImpl*>(layer);
+
+ tiled_layer->set_skips_draw(skips_draw_);
+ tiled_layer->SetTilingData(*tiler_);
+ std::vector<UpdatableTile*> invalid_tiles;
+
+ for (LayerTilingData::TileMap::const_iterator iter = tiler_->tiles().begin();
+ iter != tiler_->tiles().end();
+ ++iter) {
+ int i = iter->first.first;
+ int j = iter->first.second;
+ UpdatableTile* tile = static_cast<UpdatableTile*>(iter->second);
+ // TODO(enne): This should not ever be null.
+ if (!tile)
+ continue;
+
+ if (!tile->managed_resource()->have_backing_texture()) {
+ // Evicted tiles get deleted from both layers
+ invalid_tiles.push_back(tile);
+ continue;
+ }
+
+ if (!tile->valid_for_frame) {
+ // Invalidated tiles are set so they can get different debug colors.
+ tiled_layer->PushInvalidTile(i, j);
+ continue;
+ }
+
+ tiled_layer->PushTileProperties(
+ i,
+ j,
+ tile->managed_resource()->resource_id(),
+ tile->opaque_rect(),
+ tile->managed_resource()->contents_swizzled());
+ }
+ for (std::vector<UpdatableTile*>::const_iterator iter = invalid_tiles.begin();
+ iter != invalid_tiles.end();
+ ++iter)
+ tiler_->TakeTile((*iter)->i(), (*iter)->j());
+
+ // TiledLayer must push properties every frame, since viewport state and
+ // occlusion from anywhere in the tree can change what the layer decides to
+ // push to the impl tree.
+ needs_push_properties_ = true;
+}
+
+bool TiledLayer::BlocksPendingCommit() const { return true; }
+
+PrioritizedResourceManager* TiledLayer::ResourceManager() const {
+ if (!layer_tree_host())
+ return NULL;
+ return layer_tree_host()->contents_texture_manager();
+}
+
+const PrioritizedResource* TiledLayer::ResourceAtForTesting(int i,
+ int j) const {
+ UpdatableTile* tile = TileAt(i, j);
+ if (!tile)
+ return NULL;
+ return tile->managed_resource();
+}
+
+void TiledLayer::SetLayerTreeHost(LayerTreeHost* host) {
+ if (host && host != layer_tree_host()) {
+ for (LayerTilingData::TileMap::const_iterator
+ iter = tiler_->tiles().begin();
+ iter != tiler_->tiles().end();
+ ++iter) {
+ UpdatableTile* tile = static_cast<UpdatableTile*>(iter->second);
+ // TODO(enne): This should not ever be null.
+ if (!tile)
+ continue;
+ tile->managed_resource()->SetTextureManager(
+ host->contents_texture_manager());
+ }
+ }
+ ContentsScalingLayer::SetLayerTreeHost(host);
+}
+
+UpdatableTile* TiledLayer::TileAt(int i, int j) const {
+ return static_cast<UpdatableTile*>(tiler_->TileAt(i, j));
+}
+
+UpdatableTile* TiledLayer::CreateTile(int i, int j) {
+ CreateUpdaterIfNeeded();
+
+ scoped_ptr<UpdatableTile> tile(
+ UpdatableTile::Create(Updater()->CreateResource(ResourceManager())));
+ tile->managed_resource()->SetDimensions(tiler_->tile_size(), texture_format_);
+
+ UpdatableTile* added_tile = tile.get();
+ tiler_->AddTile(tile.PassAs<LayerTilingData::Tile>(), i, j);
+
+ added_tile->dirty_rect = tiler_->TileRect(added_tile);
+
+ // Temporary diagnostic crash.
+ CHECK(added_tile);
+ CHECK(TileAt(i, j));
+
+ return added_tile;
+}
+
+void TiledLayer::SetNeedsDisplayRect(const gfx::RectF& dirty_rect) {
+ InvalidateContentRect(LayerRectToContentRect(dirty_rect));
+ ContentsScalingLayer::SetNeedsDisplayRect(dirty_rect);
+}
+
+void TiledLayer::InvalidateContentRect(gfx::Rect content_rect) {
+ UpdateBounds();
+ if (tiler_->is_empty() || content_rect.IsEmpty() || skips_draw_)
+ return;
+
+ for (LayerTilingData::TileMap::const_iterator iter = tiler_->tiles().begin();
+ iter != tiler_->tiles().end();
+ ++iter) {
+ UpdatableTile* tile = static_cast<UpdatableTile*>(iter->second);
+ DCHECK(tile);
+ // TODO(enne): This should not ever be null.
+ if (!tile)
+ continue;
+ gfx::Rect bound = tiler_->TileRect(tile);
+ bound.Intersect(content_rect);
+ tile->dirty_rect.Union(bound);
+ }
+}
+
+// Returns true if tile is dirty and only part of it needs to be updated.
+bool TiledLayer::TileOnlyNeedsPartialUpdate(UpdatableTile* tile) {
+ return !tile->dirty_rect.Contains(tiler_->TileRect(tile)) &&
+ tile->managed_resource()->have_backing_texture();
+}
+
+bool TiledLayer::UpdateTiles(int left,
+ int top,
+ int right,
+ int bottom,
+ ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion,
+ bool* updated) {
+ CreateUpdaterIfNeeded();
+
+ bool ignore_occlusions = !occlusion;
+ if (!HaveTexturesForTiles(left, top, right, bottom, ignore_occlusions)) {
+ failed_update_ = true;
+ return false;
+ }
+
+ gfx::Rect paint_rect =
+ MarkTilesForUpdate(left, top, right, bottom, ignore_occlusions);
+
+ if (occlusion)
+ occlusion->overdraw_metrics()->DidPaint(paint_rect);
+
+ if (paint_rect.IsEmpty())
+ return true;
+
+ *updated = true;
+ UpdateTileTextures(
+ paint_rect, left, top, right, bottom, queue, occlusion);
+ return true;
+}
+
+void TiledLayer::MarkOcclusionsAndRequestTextures(
+ int left,
+ int top,
+ int right,
+ int bottom,
+ const OcclusionTracker* occlusion) {
+ // There is some difficult dependancies between occlusions, recording
+ // occlusion metrics and requesting memory so those are encapsulated in this
+ // function: - We only want to call RequestLate on unoccluded textures (to
+ // preserve memory for other layers when near OOM). - We only want to record
+ // occlusion metrics if all memory requests succeed.
+
+ int occluded_tile_count = 0;
+ bool succeeded = true;
+ for (int j = top; j <= bottom; ++j) {
+ for (int i = left; i <= right; ++i) {
+ UpdatableTile* tile = TileAt(i, j);
+ DCHECK(tile); // Did SetTexturePriorities get skipped?
+ // TODO(enne): This should not ever be null.
+ if (!tile)
+ continue;
+ // Did ResetUpdateState get skipped? Are we doing more than one occlusion
+ // pass?
+ DCHECK(!tile->occluded);
+ gfx::Rect visible_tile_rect = gfx::IntersectRects(
+ tiler_->tile_bounds(i, j), visible_content_rect());
+ if (occlusion && occlusion->Occluded(render_target(),
+ visible_tile_rect,
+ draw_transform(),
+ draw_transform_is_animating(),
+ is_clipped(),
+ clip_rect(),
+ NULL)) {
+ tile->occluded = true;
+ occluded_tile_count++;
+ } else {
+ succeeded &= tile->managed_resource()->RequestLate();
+ }
+ }
+ }
+
+ if (!succeeded)
+ return;
+ if (occlusion)
+ occlusion->overdraw_metrics()->DidCullTilesForUpload(occluded_tile_count);
+}
+
+bool TiledLayer::HaveTexturesForTiles(int left,
+ int top,
+ int right,
+ int bottom,
+ bool ignore_occlusions) {
+ for (int j = top; j <= bottom; ++j) {
+ for (int i = left; i <= right; ++i) {
+ UpdatableTile* tile = TileAt(i, j);
+ DCHECK(tile); // Did SetTexturePriorites get skipped?
+ // TODO(enne): This should not ever be null.
+ if (!tile)
+ continue;
+
+ // Ensure the entire tile is dirty if we don't have the texture.
+ if (!tile->managed_resource()->have_backing_texture())
+ tile->dirty_rect = tiler_->TileRect(tile);
+
+ // If using occlusion and the visible region of the tile is occluded,
+ // don't reserve a texture or update the tile.
+ if (tile->occluded && !ignore_occlusions)
+ continue;
+
+ if (!tile->managed_resource()->can_acquire_backing_texture())
+ return false;
+ }
+ }
+ return true;
+}
+
+gfx::Rect TiledLayer::MarkTilesForUpdate(int left,
+ int top,
+ int right,
+ int bottom,
+ bool ignore_occlusions) {
+ gfx::Rect paint_rect;
+ for (int j = top; j <= bottom; ++j) {
+ for (int i = left; i <= right; ++i) {
+ UpdatableTile* tile = TileAt(i, j);
+ DCHECK(tile); // Did SetTexturePriorites get skipped?
+ // TODO(enne): This should not ever be null.
+ if (!tile)
+ continue;
+ if (tile->occluded && !ignore_occlusions)
+ continue;
+ // TODO(reveman): Decide if partial update should be allowed based on cost
+ // of update. https://bugs.webkit.org/show_bug.cgi?id=77376
+ if (tile->is_dirty() && layer_tree_host() &&
+ layer_tree_host()->buffered_updates()) {
+ // If we get a partial update, we use the same texture, otherwise return
+ // the current texture backing, so we don't update visible textures
+ // non-atomically. If the current backing is in-use, it won't be
+ // deleted until after the commit as the texture manager will not allow
+ // deletion or recycling of in-use textures.
+ if (TileOnlyNeedsPartialUpdate(tile) &&
+ layer_tree_host()->RequestPartialTextureUpdate()) {
+ tile->partial_update = true;
+ } else {
+ tile->dirty_rect = tiler_->TileRect(tile);
+ tile->managed_resource()->ReturnBackingTexture();
+ }
+ }
+
+ paint_rect.Union(tile->dirty_rect);
+ tile->MarkForUpdate();
+ }
+ }
+ return paint_rect;
+}
+
+void TiledLayer::UpdateTileTextures(gfx::Rect paint_rect,
+ int left,
+ int top,
+ int right,
+ int bottom,
+ ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion) {
+ // The update_rect should be in layer space. So we have to convert the
+ // paint_rect from content space to layer space.
+ float width_scale =
+ paint_properties().bounds.width() /
+ static_cast<float>(content_bounds().width());
+ float height_scale =
+ paint_properties().bounds.height() /
+ static_cast<float>(content_bounds().height());
+ update_rect_ = gfx::ScaleRect(paint_rect, width_scale, height_scale);
+
+ // Calling PrepareToUpdate() calls into WebKit to paint, which may have the
+ // side effect of disabling compositing, which causes our reference to the
+ // texture updater to be deleted. However, we can't free the memory backing
+ // the SkCanvas until the paint finishes, so we grab a local reference here to
+ // hold the updater alive until the paint completes.
+ scoped_refptr<LayerUpdater> protector(Updater());
+ gfx::Rect painted_opaque_rect;
+ Updater()->PrepareToUpdate(paint_rect,
+ tiler_->tile_size(),
+ 1.f / width_scale,
+ 1.f / height_scale,
+ &painted_opaque_rect);
+
+ for (int j = top; j <= bottom; ++j) {
+ for (int i = left; i <= right; ++i) {
+ UpdatableTile* tile = TileAt(i, j);
+ DCHECK(tile); // Did SetTexturePriorites get skipped?
+ // TODO(enne): This should not ever be null.
+ if (!tile)
+ continue;
+
+ gfx::Rect tile_rect = tiler_->tile_bounds(i, j);
+
+ // Use update_rect as the above loop copied the dirty rect for this frame
+ // to update_rect.
+ gfx::Rect dirty_rect = tile->update_rect;
+ if (dirty_rect.IsEmpty())
+ continue;
+
+ // Save what was painted opaque in the tile. Keep the old area if the
+ // paint didn't touch it, and didn't paint some other part of the tile
+ // opaque.
+ gfx::Rect tile_painted_rect = gfx::IntersectRects(tile_rect, paint_rect);
+ gfx::Rect tile_painted_opaque_rect =
+ gfx::IntersectRects(tile_rect, painted_opaque_rect);
+ if (!tile_painted_rect.IsEmpty()) {
+ gfx::Rect paint_inside_tile_opaque_rect =
+ gfx::IntersectRects(tile->opaque_rect(), tile_painted_rect);
+ bool paint_inside_tile_opaque_rect_is_non_opaque =
+ !paint_inside_tile_opaque_rect.IsEmpty() &&
+ !tile_painted_opaque_rect.Contains(paint_inside_tile_opaque_rect);
+ bool opaque_paint_not_inside_tile_opaque_rect =
+ !tile_painted_opaque_rect.IsEmpty() &&
+ !tile->opaque_rect().Contains(tile_painted_opaque_rect);
+
+ if (paint_inside_tile_opaque_rect_is_non_opaque ||
+ opaque_paint_not_inside_tile_opaque_rect)
+ tile->set_opaque_rect(tile_painted_opaque_rect);
+ }
+
+ // source_rect starts as a full-sized tile with border texels included.
+ gfx::Rect source_rect = tiler_->TileRect(tile);
+ source_rect.Intersect(dirty_rect);
+ // Paint rect not guaranteed to line up on tile boundaries, so
+ // make sure that source_rect doesn't extend outside of it.
+ source_rect.Intersect(paint_rect);
+
+ tile->update_rect = source_rect;
+
+ if (source_rect.IsEmpty())
+ continue;
+
+ const gfx::Point anchor = tiler_->TileRect(tile).origin();
+
+ // Calculate tile-space rectangle to upload into.
+ gfx::Vector2d dest_offset = source_rect.origin() - anchor;
+ CHECK_GE(dest_offset.x(), 0);
+ CHECK_GE(dest_offset.y(), 0);
+
+ // Offset from paint rectangle to this tile's dirty rectangle.
+ gfx::Vector2d paint_offset = source_rect.origin() - paint_rect.origin();
+ CHECK_GE(paint_offset.x(), 0);
+ CHECK_GE(paint_offset.y(), 0);
+ CHECK_LE(paint_offset.x() + source_rect.width(), paint_rect.width());
+ CHECK_LE(paint_offset.y() + source_rect.height(), paint_rect.height());
+
+ tile->updater_resource()->Update(
+ queue, source_rect, dest_offset, tile->partial_update);
+ if (occlusion) {
+ occlusion->overdraw_metrics()->
+ DidUpload(gfx::Transform(), source_rect, tile->opaque_rect());
+ }
+ }
+ }
+}
+
+// This picks a small animated layer to be anything less than one viewport. This
+// is specifically for page transitions which are viewport-sized layers. The
+// extra tile of padding is due to these layers being slightly larger than the
+// viewport in some cases.
+bool TiledLayer::IsSmallAnimatedLayer() const {
+ if (!draw_transform_is_animating() && !screen_space_transform_is_animating())
+ return false;
+ gfx::Size viewport_size =
+ layer_tree_host() ? layer_tree_host()->device_viewport_size()
+ : gfx::Size();
+ gfx::Rect content_rect(content_bounds());
+ return content_rect.width() <=
+ viewport_size.width() + tiler_->tile_size().width() &&
+ content_rect.height() <=
+ viewport_size.height() + tiler_->tile_size().height();
+}
+
+namespace {
+// TODO(epenner): Remove this and make this based on distance once distance can
+// be calculated for offscreen layers. For now, prioritize all small animated
+// layers after 512 pixels of pre-painting.
+void SetPriorityForTexture(gfx::Rect visible_rect,
+ gfx::Rect tile_rect,
+ bool draws_to_root,
+ bool is_small_animated_layer,
+ PrioritizedResource* texture) {
+ int priority = PriorityCalculator::LowestPriority();
+ if (!visible_rect.IsEmpty()) {
+ priority = PriorityCalculator::PriorityFromDistance(
+ visible_rect, tile_rect, draws_to_root);
+ }
+
+ if (is_small_animated_layer) {
+ priority = PriorityCalculator::max_priority(
+ priority, PriorityCalculator::SmallAnimatedLayerMinPriority());
+ }
+
+ if (priority != PriorityCalculator::LowestPriority())
+ texture->set_request_priority(priority);
+}
+} // namespace
+
+void TiledLayer::SetTexturePriorities(const PriorityCalculator& priority_calc) {
+ UpdateBounds();
+ ResetUpdateState();
+ UpdateScrollPrediction();
+
+ if (tiler_->has_empty_bounds())
+ return;
+
+ bool draws_to_root = !render_target()->parent();
+ bool small_animated_layer = IsSmallAnimatedLayer();
+
+ // Minimally create the tiles in the desired pre-paint rect.
+ gfx::Rect create_tiles_rect = IdlePaintRect();
+ if (small_animated_layer)
+ create_tiles_rect = gfx::Rect(content_bounds());
+ if (!create_tiles_rect.IsEmpty()) {
+ int left, top, right, bottom;
+ tiler_->ContentRectToTileIndices(
+ create_tiles_rect, &left, &top, &right, &bottom);
+ for (int j = top; j <= bottom; ++j) {
+ for (int i = left; i <= right; ++i) {
+ if (!TileAt(i, j))
+ CreateTile(i, j);
+ }
+ }
+ }
+
+ // Now update priorities on all tiles we have in the layer, no matter where
+ // they are.
+ for (LayerTilingData::TileMap::const_iterator iter = tiler_->tiles().begin();
+ iter != tiler_->tiles().end();
+ ++iter) {
+ UpdatableTile* tile = static_cast<UpdatableTile*>(iter->second);
+ // TODO(enne): This should not ever be null.
+ if (!tile)
+ continue;
+ gfx::Rect tile_rect = tiler_->TileRect(tile);
+ SetPriorityForTexture(predicted_visible_rect_,
+ tile_rect,
+ draws_to_root,
+ small_animated_layer,
+ tile->managed_resource());
+ }
+}
+
+Region TiledLayer::VisibleContentOpaqueRegion() const {
+ if (skips_draw_)
+ return Region();
+ if (contents_opaque())
+ return visible_content_rect();
+ return tiler_->OpaqueRegionInContentRect(visible_content_rect());
+}
+
+void TiledLayer::ResetUpdateState() {
+ skips_draw_ = false;
+ failed_update_ = false;
+
+ LayerTilingData::TileMap::const_iterator end = tiler_->tiles().end();
+ for (LayerTilingData::TileMap::const_iterator iter = tiler_->tiles().begin();
+ iter != end;
+ ++iter) {
+ UpdatableTile* tile = static_cast<UpdatableTile*>(iter->second);
+ // TODO(enne): This should not ever be null.
+ if (!tile)
+ continue;
+ tile->ResetUpdateState();
+ }
+}
+
+namespace {
+gfx::Rect ExpandRectByDelta(gfx::Rect rect, gfx::Vector2d delta) {
+ int width = rect.width() + std::abs(delta.x());
+ int height = rect.height() + std::abs(delta.y());
+ int x = rect.x() + ((delta.x() < 0) ? delta.x() : 0);
+ int y = rect.y() + ((delta.y() < 0) ? delta.y() : 0);
+ return gfx::Rect(x, y, width, height);
+}
+}
+
+void TiledLayer::UpdateScrollPrediction() {
+ // This scroll prediction is very primitive and should be replaced by a
+ // a recursive calculation on all layers which uses actual scroll/animation
+ // velocities. To insure this doesn't miss-predict, we only use it to predict
+ // the visible_rect if:
+ // - content_bounds() hasn't changed.
+ // - visible_rect.size() hasn't changed.
+ // These two conditions prevent rotations, scales, pinch-zooms etc. where
+ // the prediction would be incorrect.
+ gfx::Vector2d delta = visible_content_rect().CenterPoint() -
+ previous_visible_rect_.CenterPoint();
+ predicted_scroll_ = -delta;
+ predicted_visible_rect_ = visible_content_rect();
+ if (previous_content_bounds_ == content_bounds() &&
+ previous_visible_rect_.size() == visible_content_rect().size()) {
+ // Only expand the visible rect in the major scroll direction, to prevent
+ // massive paints due to diagonal scrolls.
+ gfx::Vector2d major_scroll_delta =
+ (std::abs(delta.x()) > std::abs(delta.y())) ?
+ gfx::Vector2d(delta.x(), 0) :
+ gfx::Vector2d(0, delta.y());
+ predicted_visible_rect_ =
+ ExpandRectByDelta(visible_content_rect(), major_scroll_delta);
+
+ // Bound the prediction to prevent unbounded paints, and clamp to content
+ // bounds.
+ gfx::Rect bound = visible_content_rect();
+ bound.Inset(-tiler_->tile_size().width() * kMaxPredictiveTilesCount,
+ -tiler_->tile_size().height() * kMaxPredictiveTilesCount);
+ bound.Intersect(gfx::Rect(content_bounds()));
+ predicted_visible_rect_.Intersect(bound);
+ }
+ previous_content_bounds_ = content_bounds();
+ previous_visible_rect_ = visible_content_rect();
+}
+
+bool TiledLayer::Update(ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion) {
+ DCHECK(!skips_draw_ && !failed_update_); // Did ResetUpdateState get skipped?
+
+ bool updated = false;
+
+ {
+ base::AutoReset<bool> ignore_set_needs_commit(&ignore_set_needs_commit_,
+ true);
+
+ updated |= ContentsScalingLayer::Update(queue, occlusion);
+ UpdateBounds();
+ }
+
+ if (tiler_->has_empty_bounds() || !DrawsContent())
+ return false;
+
+ // Animation pre-paint. If the layer is small, try to paint it all
+ // immediately whether or not it is occluded, to avoid paint/upload
+ // hiccups while it is animating.
+ if (IsSmallAnimatedLayer()) {
+ int left, top, right, bottom;
+ tiler_->ContentRectToTileIndices(gfx::Rect(content_bounds()),
+ &left,
+ &top,
+ &right,
+ &bottom);
+ UpdateTiles(left, top, right, bottom, queue, NULL, &updated);
+ if (updated)
+ return updated;
+ // This was an attempt to paint the entire layer so if we fail it's okay,
+ // just fallback on painting visible etc. below.
+ failed_update_ = false;
+ }
+
+ if (predicted_visible_rect_.IsEmpty())
+ return updated;
+
+ // Visible painting. First occlude visible tiles and paint the non-occluded
+ // tiles.
+ int left, top, right, bottom;
+ tiler_->ContentRectToTileIndices(
+ predicted_visible_rect_, &left, &top, &right, &bottom);
+ MarkOcclusionsAndRequestTextures(left, top, right, bottom, occlusion);
+ skips_draw_ = !UpdateTiles(
+ left, top, right, bottom, queue, occlusion, &updated);
+ if (skips_draw_)
+ tiler_->reset();
+ if (skips_draw_ || updated)
+ return true;
+
+ // If we have already painting everything visible. Do some pre-painting while
+ // idle.
+ gfx::Rect idle_paint_content_rect = IdlePaintRect();
+ if (idle_paint_content_rect.IsEmpty())
+ return updated;
+
+ // Prepaint anything that was occluded but inside the layer's visible region.
+ if (!UpdateTiles(left, top, right, bottom, queue, NULL, &updated) ||
+ updated)
+ return updated;
+
+ int prepaint_left, prepaint_top, prepaint_right, prepaint_bottom;
+ tiler_->ContentRectToTileIndices(idle_paint_content_rect,
+ &prepaint_left,
+ &prepaint_top,
+ &prepaint_right,
+ &prepaint_bottom);
+
+ // Then expand outwards one row/column at a time until we find a dirty
+ // row/column to update. Increment along the major and minor scroll directions
+ // first.
+ gfx::Vector2d delta = -predicted_scroll_;
+ delta = gfx::Vector2d(delta.x() == 0 ? 1 : delta.x(),
+ delta.y() == 0 ? 1 : delta.y());
+ gfx::Vector2d major_delta =
+ (std::abs(delta.x()) > std::abs(delta.y())) ? gfx::Vector2d(delta.x(), 0)
+ : gfx::Vector2d(0, delta.y());
+ gfx::Vector2d minor_delta =
+ (std::abs(delta.x()) <= std::abs(delta.y())) ? gfx::Vector2d(delta.x(), 0)
+ : gfx::Vector2d(0, delta.y());
+ gfx::Vector2d deltas[4] = { major_delta, minor_delta, -major_delta,
+ -minor_delta };
+ for (int i = 0; i < 4; i++) {
+ if (deltas[i].y() > 0) {
+ while (bottom < prepaint_bottom) {
+ ++bottom;
+ if (!UpdateTiles(
+ left, bottom, right, bottom, queue, NULL, &updated) ||
+ updated)
+ return updated;
+ }
+ }
+ if (deltas[i].y() < 0) {
+ while (top > prepaint_top) {
+ --top;
+ if (!UpdateTiles(
+ left, top, right, top, queue, NULL, &updated) ||
+ updated)
+ return updated;
+ }
+ }
+ if (deltas[i].x() < 0) {
+ while (left > prepaint_left) {
+ --left;
+ if (!UpdateTiles(
+ left, top, left, bottom, queue, NULL, &updated) ||
+ updated)
+ return updated;
+ }
+ }
+ if (deltas[i].x() > 0) {
+ while (right < prepaint_right) {
+ ++right;
+ if (!UpdateTiles(
+ right, top, right, bottom, queue, NULL, &updated) ||
+ updated)
+ return updated;
+ }
+ }
+ }
+ return updated;
+}
+
+bool TiledLayer::NeedsIdlePaint() {
+ // Don't trigger more paints if we failed (as we'll just fail again).
+ if (failed_update_ || visible_content_rect().IsEmpty() ||
+ tiler_->has_empty_bounds() || !DrawsContent())
+ return false;
+
+ gfx::Rect idle_paint_content_rect = IdlePaintRect();
+ if (idle_paint_content_rect.IsEmpty())
+ return false;
+
+ int left, top, right, bottom;
+ tiler_->ContentRectToTileIndices(
+ idle_paint_content_rect, &left, &top, &right, &bottom);
+
+ for (int j = top; j <= bottom; ++j) {
+ for (int i = left; i <= right; ++i) {
+ UpdatableTile* tile = TileAt(i, j);
+ DCHECK(tile); // Did SetTexturePriorities get skipped?
+ if (!tile)
+ continue;
+
+ bool updated = !tile->update_rect.IsEmpty();
+ bool can_acquire =
+ tile->managed_resource()->can_acquire_backing_texture();
+ bool dirty =
+ tile->is_dirty() || !tile->managed_resource()->have_backing_texture();
+ if (!updated && can_acquire && dirty)
+ return true;
+ }
+ }
+ return false;
+}
+
+gfx::Rect TiledLayer::IdlePaintRect() {
+ // Don't inflate an empty rect.
+ if (visible_content_rect().IsEmpty())
+ return gfx::Rect();
+
+ gfx::Rect prepaint_rect = visible_content_rect();
+ prepaint_rect.Inset(-tiler_->tile_size().width() * kPrepaintColumns,
+ -tiler_->tile_size().height() * kPrepaintRows);
+ gfx::Rect content_rect(content_bounds());
+ prepaint_rect.Intersect(content_rect);
+
+ return prepaint_rect;
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/tiled_layer.h b/chromium/cc/layers/tiled_layer.h
new file mode 100644
index 00000000000..8ad7df95e38
--- /dev/null
+++ b/chromium/cc/layers/tiled_layer.h
@@ -0,0 +1,142 @@
+// Copyright 2011 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.
+
+#ifndef CC_LAYERS_TILED_LAYER_H_
+#define CC_LAYERS_TILED_LAYER_H_
+
+#include "cc/base/cc_export.h"
+#include "cc/layers/contents_scaling_layer.h"
+#include "cc/resources/layer_tiling_data.h"
+
+namespace cc {
+class LayerUpdater;
+class PrioritizedResourceManager;
+class PrioritizedResource;
+class UpdatableTile;
+
+class CC_EXPORT TiledLayer : public ContentsScalingLayer {
+ public:
+ enum TilingOption {
+ ALWAYS_TILE,
+ NEVER_TILE,
+ AUTO_TILE,
+ };
+
+ // Layer implementation.
+ virtual void SetIsMask(bool is_mask) OVERRIDE;
+ virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE;
+ virtual bool BlocksPendingCommit() const OVERRIDE;
+ virtual bool DrawsContent() const OVERRIDE;
+ virtual void ReduceMemoryUsage() OVERRIDE;
+ virtual void SetNeedsDisplayRect(const gfx::RectF& dirty_rect) OVERRIDE;
+ virtual void SetLayerTreeHost(LayerTreeHost* layer_tree_host) OVERRIDE;
+ virtual void SetTexturePriorities(const PriorityCalculator& priority_calc)
+ OVERRIDE;
+ virtual Region VisibleContentOpaqueRegion() const OVERRIDE;
+ virtual bool Update(ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion) OVERRIDE;
+
+ protected:
+ TiledLayer();
+ virtual ~TiledLayer();
+
+ void UpdateTileSizeAndTilingOption();
+ void UpdateBounds();
+
+ // Exposed to subclasses for testing.
+ void SetTileSize(gfx::Size size);
+ void SetTextureFormat(unsigned texture_format) {
+ texture_format_ = texture_format;
+ }
+ void SetBorderTexelOption(LayerTilingData::BorderTexelOption option);
+ size_t NumPaintedTiles() { return tiler_->tiles().size(); }
+
+ virtual LayerUpdater* Updater() const = 0;
+ virtual void CreateUpdaterIfNeeded() = 0;
+
+ // Set invalidations to be potentially repainted during Update().
+ void InvalidateContentRect(gfx::Rect content_rect);
+
+ // Reset state on tiles that will be used for updating the layer.
+ void ResetUpdateState();
+
+ // After preparing an update, returns true if more painting is needed.
+ bool NeedsIdlePaint();
+ gfx::Rect IdlePaintRect();
+
+ bool SkipsDraw() const { return skips_draw_; }
+
+ // Virtual for testing
+ virtual PrioritizedResourceManager* ResourceManager() const;
+ const LayerTilingData* TilerForTesting() const { return tiler_.get(); }
+ const PrioritizedResource* ResourceAtForTesting(int i, int j) const;
+
+ private:
+ virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl)
+ OVERRIDE;
+
+ void CreateTilerIfNeeded();
+ void set_tiling_option(TilingOption tiling_option) {
+ tiling_option_ = tiling_option;
+ }
+
+ bool TileOnlyNeedsPartialUpdate(UpdatableTile* tile);
+ bool TileNeedsBufferedUpdate(UpdatableTile* tile);
+
+ void MarkOcclusionsAndRequestTextures(int left,
+ int top,
+ int right,
+ int bottom,
+ const OcclusionTracker* occlusion);
+
+ bool UpdateTiles(int left,
+ int top,
+ int right,
+ int bottom,
+ ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion,
+ bool* did_paint);
+ bool HaveTexturesForTiles(int left,
+ int top,
+ int right,
+ int bottom,
+ bool ignore_occlusions);
+ gfx::Rect MarkTilesForUpdate(int left,
+ int top,
+ int right,
+ int bottom,
+ bool ignore_occlusions);
+ void UpdateTileTextures(gfx::Rect paint_rect,
+ int left,
+ int top,
+ int right,
+ int bottom,
+ ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion);
+ void UpdateScrollPrediction();
+
+ UpdatableTile* TileAt(int i, int j) const;
+ UpdatableTile* CreateTile(int i, int j);
+
+ bool IsSmallAnimatedLayer() const;
+
+ unsigned texture_format_;
+ bool skips_draw_;
+ bool failed_update_;
+
+ // Used for predictive painting.
+ gfx::Vector2d predicted_scroll_;
+ gfx::Rect predicted_visible_rect_;
+ gfx::Rect previous_visible_rect_;
+ gfx::Size previous_content_bounds_;
+
+ TilingOption tiling_option_;
+ scoped_ptr<LayerTilingData> tiler_;
+
+ DISALLOW_COPY_AND_ASSIGN(TiledLayer);
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_TILED_LAYER_H_
diff --git a/chromium/cc/layers/tiled_layer_impl.cc b/chromium/cc/layers/tiled_layer_impl.cc
new file mode 100644
index 00000000000..e3852129da4
--- /dev/null
+++ b/chromium/cc/layers/tiled_layer_impl.cc
@@ -0,0 +1,321 @@
+// Copyright 2011 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 "cc/layers/tiled_layer_impl.h"
+
+#include "base/basictypes.h"
+#include "base/strings/stringprintf.h"
+#include "cc/base/math_util.h"
+#include "cc/debug/debug_colors.h"
+#include "cc/layers/append_quads_data.h"
+#include "cc/layers/quad_sink.h"
+#include "cc/quads/checkerboard_draw_quad.h"
+#include "cc/quads/debug_border_draw_quad.h"
+#include "cc/quads/solid_color_draw_quad.h"
+#include "cc/quads/tile_draw_quad.h"
+#include "cc/resources/layer_tiling_data.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/gfx/quad_f.h"
+
+namespace cc {
+
+// Temporary diagnostic.
+static bool s_safe_to_delete_drawable_tile = false;
+
+class DrawableTile : public LayerTilingData::Tile {
+ public:
+ static scoped_ptr<DrawableTile> Create() {
+ return make_scoped_ptr(new DrawableTile());
+ }
+
+ virtual ~DrawableTile() { CHECK(s_safe_to_delete_drawable_tile); }
+
+ ResourceProvider::ResourceId resource_id() const { return resource_id_; }
+ void set_resource_id(ResourceProvider::ResourceId resource_id) {
+ resource_id_ = resource_id;
+ }
+ bool contents_swizzled() { return contents_swizzled_; }
+ void set_contents_swizzled(bool contents_swizzled) {
+ contents_swizzled_ = contents_swizzled;
+ }
+
+ private:
+ DrawableTile() : resource_id_(0), contents_swizzled_(false) {}
+
+ ResourceProvider::ResourceId resource_id_;
+ bool contents_swizzled_;
+
+ DISALLOW_COPY_AND_ASSIGN(DrawableTile);
+};
+
+TiledLayerImpl::TiledLayerImpl(LayerTreeImpl* tree_impl, int id)
+ : LayerImpl(tree_impl, id), skips_draw_(true) {}
+
+TiledLayerImpl::~TiledLayerImpl() {
+ s_safe_to_delete_drawable_tile = true;
+ if (tiler_)
+ tiler_->reset();
+ s_safe_to_delete_drawable_tile = false;
+}
+
+ResourceProvider::ResourceId TiledLayerImpl::ContentsResourceId() const {
+ // This function is only valid for single texture layers, e.g. masks.
+ DCHECK(tiler_);
+ DCHECK_EQ(tiler_->num_tiles_x(), 1);
+ DCHECK_EQ(tiler_->num_tiles_y(), 1);
+
+ DrawableTile* tile = TileAt(0, 0);
+ ResourceProvider::ResourceId resource_id = tile ? tile->resource_id() : 0;
+ return resource_id;
+}
+
+bool TiledLayerImpl::HasTileAt(int i, int j) const {
+ return !!tiler_->TileAt(i, j);
+}
+
+bool TiledLayerImpl::HasResourceIdForTileAt(int i, int j) const {
+ return HasTileAt(i, j) && TileAt(i, j)->resource_id();
+}
+
+DrawableTile* TiledLayerImpl::TileAt(int i, int j) const {
+ return static_cast<DrawableTile*>(tiler_->TileAt(i, j));
+}
+
+DrawableTile* TiledLayerImpl::CreateTile(int i, int j) {
+ scoped_ptr<DrawableTile> tile(DrawableTile::Create());
+ DrawableTile* added_tile = tile.get();
+ tiler_->AddTile(tile.PassAs<LayerTilingData::Tile>(), i, j);
+
+ // Temporary diagnostic checks.
+ CHECK(added_tile);
+ CHECK(TileAt(i, j));
+
+ return added_tile;
+}
+
+void TiledLayerImpl::GetDebugBorderProperties(SkColor* color,
+ float* width) const {
+ *color = DebugColors::TiledContentLayerBorderColor();
+ *width = DebugColors::TiledContentLayerBorderWidth(layer_tree_impl());
+}
+
+scoped_ptr<LayerImpl> TiledLayerImpl::CreateLayerImpl(
+ LayerTreeImpl* tree_impl) {
+ return TiledLayerImpl::Create(tree_impl, id()).PassAs<LayerImpl>();
+}
+
+void TiledLayerImpl::AsValueInto(base::DictionaryValue* state) const {
+ LayerImpl::AsValueInto(state);
+ state->Set("invalidation", MathUtil::AsValue(update_rect()).release());
+}
+
+size_t TiledLayerImpl::GPUMemoryUsageInBytes() const {
+ size_t amount = 0;
+ const size_t kMemoryUsagePerTileInBytes =
+ 4 * tiler_->tile_size().width() * tiler_->tile_size().height();
+ for (LayerTilingData::TileMap::const_iterator iter = tiler_->tiles().begin();
+ iter != tiler_->tiles().end();
+ ++iter) {
+ const DrawableTile* tile = static_cast<DrawableTile*>(iter->second);
+ if (!tile || !tile->resource_id())
+ continue;
+ amount += kMemoryUsagePerTileInBytes;
+ }
+ return amount;
+}
+
+void TiledLayerImpl::PushPropertiesTo(LayerImpl* layer) {
+ LayerImpl::PushPropertiesTo(layer);
+
+ TiledLayerImpl* tiled_layer = static_cast<TiledLayerImpl*>(layer);
+
+ tiled_layer->set_skips_draw(skips_draw_);
+ tiled_layer->SetTilingData(*tiler_);
+
+ for (LayerTilingData::TileMap::const_iterator iter = tiler_->tiles().begin();
+ iter != tiler_->tiles().end();
+ ++iter) {
+ int i = iter->first.first;
+ int j = iter->first.second;
+ DrawableTile* tile = static_cast<DrawableTile*>(iter->second);
+
+ tiled_layer->PushTileProperties(i,
+ j,
+ tile->resource_id(),
+ tile->opaque_rect(),
+ tile->contents_swizzled());
+ }
+}
+
+bool TiledLayerImpl::WillDraw(DrawMode draw_mode,
+ ResourceProvider* resource_provider) {
+ if (!tiler_ || tiler_->has_empty_bounds() ||
+ visible_content_rect().IsEmpty() ||
+ draw_mode == DRAW_MODE_RESOURCELESS_SOFTWARE)
+ return false;
+ return LayerImpl::WillDraw(draw_mode, resource_provider);
+}
+
+void TiledLayerImpl::AppendQuads(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data) {
+ DCHECK(tiler_);
+ DCHECK(!tiler_->has_empty_bounds());
+ DCHECK(!visible_content_rect().IsEmpty());
+
+ gfx::Rect content_rect = visible_content_rect();
+ SharedQuadState* shared_quad_state =
+ quad_sink->UseSharedQuadState(CreateSharedQuadState());
+ AppendDebugBorderQuad(quad_sink, shared_quad_state, append_quads_data);
+
+ int left, top, right, bottom;
+ tiler_->ContentRectToTileIndices(content_rect, &left, &top, &right, &bottom);
+
+ if (ShowDebugBorders()) {
+ for (int j = top; j <= bottom; ++j) {
+ for (int i = left; i <= right; ++i) {
+ DrawableTile* tile = TileAt(i, j);
+ gfx::Rect tile_rect = tiler_->tile_bounds(i, j);
+ SkColor border_color;
+ float border_width;
+
+ if (skips_draw_ || !tile || !tile->resource_id()) {
+ border_color = DebugColors::MissingTileBorderColor();
+ border_width = DebugColors::MissingTileBorderWidth(layer_tree_impl());
+ } else {
+ border_color = DebugColors::HighResTileBorderColor();
+ border_width = DebugColors::HighResTileBorderWidth(layer_tree_impl());
+ }
+ scoped_ptr<DebugBorderDrawQuad> debug_border_quad =
+ DebugBorderDrawQuad::Create();
+ debug_border_quad->SetNew(
+ shared_quad_state, tile_rect, border_color, border_width);
+ quad_sink->Append(debug_border_quad.PassAs<DrawQuad>(),
+ append_quads_data);
+ }
+ }
+ }
+
+ if (skips_draw_)
+ return;
+
+ for (int j = top; j <= bottom; ++j) {
+ for (int i = left; i <= right; ++i) {
+ DrawableTile* tile = TileAt(i, j);
+ gfx::Rect tile_rect = tiler_->tile_bounds(i, j);
+ gfx::Rect display_rect = tile_rect;
+ tile_rect.Intersect(content_rect);
+
+ // Skip empty tiles.
+ if (tile_rect.IsEmpty())
+ continue;
+
+ if (!tile || !tile->resource_id()) {
+ SkColor checker_color;
+ if (ShowDebugBorders()) {
+ checker_color =
+ tile ? DebugColors::InvalidatedTileCheckerboardColor()
+ : DebugColors::EvictedTileCheckerboardColor();
+ } else {
+ checker_color = DebugColors::DefaultCheckerboardColor();
+ }
+
+ scoped_ptr<CheckerboardDrawQuad> checkerboard_quad =
+ CheckerboardDrawQuad::Create();
+ checkerboard_quad->SetNew(
+ shared_quad_state, tile_rect, checker_color);
+ if (quad_sink->Append(checkerboard_quad.PassAs<DrawQuad>(),
+ append_quads_data))
+ append_quads_data->num_missing_tiles++;
+
+ continue;
+ }
+
+ gfx::Rect tile_opaque_rect = contents_opaque() ? tile_rect :
+ gfx::IntersectRects(tile->opaque_rect(), content_rect);
+
+ // Keep track of how the top left has moved, so the texture can be
+ // offset the same amount.
+ gfx::Vector2d display_offset = tile_rect.origin() - display_rect.origin();
+ gfx::Vector2d texture_offset =
+ tiler_->texture_offset(i, j) + display_offset;
+ gfx::RectF tex_coord_rect = gfx::RectF(tile_rect.size()) + texture_offset;
+
+ float tile_width = static_cast<float>(tiler_->tile_size().width());
+ float tile_height = static_cast<float>(tiler_->tile_size().height());
+ gfx::Size texture_size(tile_width, tile_height);
+
+ scoped_ptr<TileDrawQuad> quad = TileDrawQuad::Create();
+ quad->SetNew(shared_quad_state,
+ tile_rect,
+ tile_opaque_rect,
+ tile->resource_id(),
+ tex_coord_rect,
+ texture_size,
+ tile->contents_swizzled());
+ quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data);
+ }
+ }
+}
+
+void TiledLayerImpl::SetTilingData(const LayerTilingData& tiler) {
+ s_safe_to_delete_drawable_tile = true;
+
+ if (tiler_) {
+ tiler_->reset();
+ } else {
+ tiler_ = LayerTilingData::Create(tiler.tile_size(),
+ tiler.has_border_texels()
+ ? LayerTilingData::HAS_BORDER_TEXELS
+ : LayerTilingData::NO_BORDER_TEXELS);
+ }
+ *tiler_ = tiler;
+
+ s_safe_to_delete_drawable_tile = false;
+}
+
+void TiledLayerImpl::PushTileProperties(
+ int i,
+ int j,
+ ResourceProvider::ResourceId resource_id,
+ gfx::Rect opaque_rect,
+ bool contents_swizzled) {
+ DrawableTile* tile = TileAt(i, j);
+ if (!tile)
+ tile = CreateTile(i, j);
+ tile->set_resource_id(resource_id);
+ tile->set_opaque_rect(opaque_rect);
+ tile->set_contents_swizzled(contents_swizzled);
+}
+
+void TiledLayerImpl::PushInvalidTile(int i, int j) {
+ DrawableTile* tile = TileAt(i, j);
+ if (!tile)
+ tile = CreateTile(i, j);
+ tile->set_resource_id(0);
+ tile->set_opaque_rect(gfx::Rect());
+ tile->set_contents_swizzled(false);
+}
+
+Region TiledLayerImpl::VisibleContentOpaqueRegion() const {
+ if (skips_draw_)
+ return Region();
+ if (contents_opaque())
+ return visible_content_rect();
+ return tiler_->OpaqueRegionInContentRect(visible_content_rect());
+}
+
+void TiledLayerImpl::DidLoseOutputSurface() {
+ s_safe_to_delete_drawable_tile = true;
+ // Temporary diagnostic check.
+ CHECK(tiler_);
+ tiler_->reset();
+ s_safe_to_delete_drawable_tile = false;
+}
+
+const char* TiledLayerImpl::LayerTypeAsString() const {
+ return "cc::TiledLayerImpl";
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/tiled_layer_impl.h b/chromium/cc/layers/tiled_layer_impl.h
new file mode 100644
index 00000000000..939842b9027
--- /dev/null
+++ b/chromium/cc/layers/tiled_layer_impl.h
@@ -0,0 +1,77 @@
+// Copyright 2011 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.
+
+#ifndef CC_LAYERS_TILED_LAYER_IMPL_H_
+#define CC_LAYERS_TILED_LAYER_IMPL_H_
+
+#include <string>
+
+#include "cc/base/cc_export.h"
+#include "cc/layers/layer_impl.h"
+
+namespace cc {
+
+class LayerTilingData;
+class DrawableTile;
+
+class CC_EXPORT TiledLayerImpl : public LayerImpl {
+ public:
+ static scoped_ptr<TiledLayerImpl> Create(LayerTreeImpl* tree_impl, int id) {
+ return make_scoped_ptr(new TiledLayerImpl(tree_impl, id));
+ }
+ virtual ~TiledLayerImpl();
+
+ virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl)
+ OVERRIDE;
+ virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE;
+
+ virtual bool WillDraw(DrawMode draw_mode,
+ ResourceProvider* resource_provider) OVERRIDE;
+ virtual void AppendQuads(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data) OVERRIDE;
+
+ virtual ResourceProvider::ResourceId ContentsResourceId() const OVERRIDE;
+
+ void set_skips_draw(bool skips_draw) { skips_draw_ = skips_draw; }
+ void SetTilingData(const LayerTilingData& tiler);
+ void PushTileProperties(int i,
+ int j,
+ ResourceProvider::ResourceId resource,
+ gfx::Rect opaque_rect,
+ bool contents_swizzled);
+ void PushInvalidTile(int i, int j);
+
+ virtual Region VisibleContentOpaqueRegion() const OVERRIDE;
+ virtual void DidLoseOutputSurface() OVERRIDE;
+
+ const LayerTilingData* TilingForTesting() const { return tiler_.get(); }
+
+ virtual size_t GPUMemoryUsageInBytes() const OVERRIDE;
+
+ protected:
+ TiledLayerImpl(LayerTreeImpl* tree_impl, int id);
+ // Exposed for testing.
+ bool HasTileAt(int i, int j) const;
+ bool HasResourceIdForTileAt(int i, int j) const;
+
+ virtual void GetDebugBorderProperties(SkColor* color, float* width) const
+ OVERRIDE;
+ virtual void AsValueInto(base::DictionaryValue* state) const OVERRIDE;
+
+ private:
+ virtual const char* LayerTypeAsString() const OVERRIDE;
+
+ DrawableTile* TileAt(int i, int j) const;
+ DrawableTile* CreateTile(int i, int j);
+
+ bool skips_draw_;
+
+ scoped_ptr<LayerTilingData> tiler_;
+
+ DISALLOW_COPY_AND_ASSIGN(TiledLayerImpl);
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_TILED_LAYER_IMPL_H_
diff --git a/chromium/cc/layers/tiled_layer_impl_unittest.cc b/chromium/cc/layers/tiled_layer_impl_unittest.cc
new file mode 100644
index 00000000000..42913ef70b7
--- /dev/null
+++ b/chromium/cc/layers/tiled_layer_impl_unittest.cc
@@ -0,0 +1,304 @@
+// 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 "cc/layers/tiled_layer_impl.h"
+
+#include "cc/layers/append_quads_data.h"
+#include "cc/quads/tile_draw_quad.h"
+#include "cc/resources/layer_tiling_data.h"
+#include "cc/test/fake_impl_proxy.h"
+#include "cc/test/fake_layer_tree_host_impl.h"
+#include "cc/test/layer_test_common.h"
+#include "cc/test/mock_quad_culler.h"
+#include "cc/trees/single_thread_proxy.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+class TiledLayerImplTest : public testing::Test {
+ public:
+ TiledLayerImplTest() : host_impl_(&proxy_) {}
+
+ scoped_ptr<TiledLayerImpl> CreateLayerNoTiles(
+ gfx::Size tile_size,
+ gfx::Size layer_size,
+ LayerTilingData::BorderTexelOption border_texels) {
+ scoped_ptr<TiledLayerImpl> layer =
+ TiledLayerImpl::Create(host_impl_.active_tree(), 1);
+ scoped_ptr<LayerTilingData> tiler =
+ LayerTilingData::Create(tile_size, border_texels);
+ tiler->SetBounds(layer_size);
+ layer->SetTilingData(*tiler);
+ layer->set_skips_draw(false);
+ layer->draw_properties().visible_content_rect =
+ gfx::Rect(layer_size);
+ layer->draw_properties().opacity = 1;
+ layer->SetBounds(layer_size);
+ layer->SetContentBounds(layer_size);
+ layer->CreateRenderSurface();
+ layer->draw_properties().render_target = layer.get();
+ return layer.Pass();
+ }
+
+ // Create a default tiled layer with textures for all tiles and a default
+ // visibility of the entire layer size.
+ scoped_ptr<TiledLayerImpl> CreateLayer(
+ gfx::Size tile_size,
+ gfx::Size layer_size,
+ LayerTilingData::BorderTexelOption border_texels) {
+ scoped_ptr<TiledLayerImpl> layer =
+ CreateLayerNoTiles(tile_size, layer_size, border_texels);
+
+ ResourceProvider::ResourceId resource_id = 1;
+ for (int i = 0; i < layer->TilingForTesting()->num_tiles_x(); ++i) {
+ for (int j = 0; j < layer->TilingForTesting()->num_tiles_y(); ++j) {
+ layer->PushTileProperties(
+ i, j, resource_id++, gfx::Rect(0, 0, 1, 1), false);
+ }
+ }
+
+ return layer.Pass();
+ }
+
+ void GetQuads(QuadList* quads,
+ SharedQuadStateList* shared_states,
+ gfx::Size tile_size,
+ gfx::Size layer_size,
+ LayerTilingData::BorderTexelOption border_texel_option,
+ gfx::Rect visible_content_rect) {
+ scoped_ptr<TiledLayerImpl> layer =
+ CreateLayer(tile_size, layer_size, border_texel_option);
+ layer->draw_properties().visible_content_rect = visible_content_rect;
+ layer->SetBounds(layer_size);
+
+ MockQuadCuller quad_culler(quads, shared_states);
+ AppendQuadsData data;
+ layer->AppendQuads(&quad_culler, &data);
+ }
+
+ protected:
+ FakeImplProxy proxy_;
+ FakeLayerTreeHostImpl host_impl_;
+};
+
+TEST_F(TiledLayerImplTest, EmptyQuadList) {
+ gfx::Size tile_size(90, 90);
+ int num_tiles_x = 8;
+ int num_tiles_y = 4;
+ gfx::Size layer_size(tile_size.width() * num_tiles_x,
+ tile_size.height() * num_tiles_y);
+
+ // Verify default layer does creates quads
+ {
+ scoped_ptr<TiledLayerImpl> layer =
+ CreateLayer(tile_size, layer_size, LayerTilingData::NO_BORDER_TEXELS);
+ MockQuadCuller quad_culler;
+ AppendQuadsData data;
+ EXPECT_TRUE(layer->WillDraw(DRAW_MODE_HARDWARE, NULL));
+ layer->AppendQuads(&quad_culler, &data);
+ layer->DidDraw(NULL);
+ unsigned num_tiles = num_tiles_x * num_tiles_y;
+ EXPECT_EQ(quad_culler.quad_list().size(), num_tiles);
+ }
+
+ // Layer with empty visible layer rect produces no quads
+ {
+ scoped_ptr<TiledLayerImpl> layer =
+ CreateLayer(tile_size, layer_size, LayerTilingData::NO_BORDER_TEXELS);
+ layer->draw_properties().visible_content_rect = gfx::Rect();
+
+ MockQuadCuller quad_culler;
+ EXPECT_FALSE(layer->WillDraw(DRAW_MODE_HARDWARE, NULL));
+ }
+
+ // Layer with non-intersecting visible layer rect produces no quads
+ {
+ scoped_ptr<TiledLayerImpl> layer =
+ CreateLayer(tile_size, layer_size, LayerTilingData::NO_BORDER_TEXELS);
+
+ gfx::Rect outside_bounds(-100, -100, 50, 50);
+ layer->draw_properties().visible_content_rect = outside_bounds;
+
+ MockQuadCuller quad_culler;
+ AppendQuadsData data;
+ EXPECT_TRUE(layer->WillDraw(DRAW_MODE_HARDWARE, NULL));
+ layer->AppendQuads(&quad_culler, &data);
+ layer->DidDraw(NULL);
+ EXPECT_EQ(quad_culler.quad_list().size(), 0u);
+ }
+
+ // Layer with skips draw produces no quads
+ {
+ scoped_ptr<TiledLayerImpl> layer =
+ CreateLayer(tile_size, layer_size, LayerTilingData::NO_BORDER_TEXELS);
+ layer->set_skips_draw(true);
+
+ MockQuadCuller quad_culler;
+ AppendQuadsData data;
+ layer->AppendQuads(&quad_culler, &data);
+ EXPECT_EQ(quad_culler.quad_list().size(), 0u);
+ }
+}
+
+TEST_F(TiledLayerImplTest, Checkerboarding) {
+ gfx::Size tile_size(10, 10);
+ int num_tiles_x = 2;
+ int num_tiles_y = 2;
+ gfx::Size layer_size(tile_size.width() * num_tiles_x,
+ tile_size.height() * num_tiles_y);
+
+ scoped_ptr<TiledLayerImpl> layer =
+ CreateLayer(tile_size, layer_size, LayerTilingData::NO_BORDER_TEXELS);
+
+ // No checkerboarding
+ {
+ MockQuadCuller quad_culler;
+ AppendQuadsData data;
+ layer->AppendQuads(&quad_culler, &data);
+ EXPECT_EQ(quad_culler.quad_list().size(), 4u);
+ EXPECT_EQ(0u, data.num_missing_tiles);
+
+ for (size_t i = 0; i < quad_culler.quad_list().size(); ++i)
+ EXPECT_EQ(quad_culler.quad_list()[i]->material, DrawQuad::TILED_CONTENT);
+ }
+
+ for (int i = 0; i < num_tiles_x; ++i)
+ for (int j = 0; j < num_tiles_y; ++j)
+ layer->PushTileProperties(i, j, 0, gfx::Rect(), false);
+
+ // All checkerboarding
+ {
+ MockQuadCuller quad_culler;
+ AppendQuadsData data;
+ layer->AppendQuads(&quad_culler, &data);
+ EXPECT_LT(0u, data.num_missing_tiles);
+ EXPECT_EQ(quad_culler.quad_list().size(), 4u);
+ for (size_t i = 0; i < quad_culler.quad_list().size(); ++i)
+ EXPECT_NE(quad_culler.quad_list()[i]->material, DrawQuad::TILED_CONTENT);
+ }
+}
+
+// Test with both border texels and without.
+#define WITH_AND_WITHOUT_BORDER_TEST(text_fixture_name) \
+ TEST_F(TiledLayerImplBorderTest, text_fixture_name##NoBorders) { \
+ text_fixture_name(LayerTilingData::NO_BORDER_TEXELS); \
+ } \
+ TEST_F(TiledLayerImplBorderTest, text_fixture_name##HasBorders) { \
+ text_fixture_name(LayerTilingData::HAS_BORDER_TEXELS); \
+ }
+
+class TiledLayerImplBorderTest : public TiledLayerImplTest {
+ public:
+ void CoverageVisibleRectOnTileBoundaries(
+ LayerTilingData::BorderTexelOption borders) {
+ gfx::Size layer_size(1000, 1000);
+ QuadList quads;
+ SharedQuadStateList shared_states;
+ GetQuads(&quads,
+ &shared_states,
+ gfx::Size(100, 100),
+ layer_size,
+ borders,
+ gfx::Rect(layer_size));
+ LayerTestCommon::VerifyQuadsExactlyCoverRect(quads, gfx::Rect(layer_size));
+ }
+
+ void CoverageVisibleRectIntersectsTiles(
+ LayerTilingData::BorderTexelOption borders) {
+ // This rect intersects the middle 3x3 of the 5x5 tiles.
+ gfx::Point top_left(65, 73);
+ gfx::Point bottom_right(182, 198);
+ gfx::Rect visible_content_rect = gfx::BoundingRect(top_left, bottom_right);
+
+ gfx::Size layer_size(250, 250);
+ QuadList quads;
+ SharedQuadStateList shared_states;
+ GetQuads(&quads,
+ &shared_states,
+ gfx::Size(50, 50),
+ gfx::Size(250, 250),
+ LayerTilingData::NO_BORDER_TEXELS,
+ visible_content_rect);
+ LayerTestCommon::VerifyQuadsExactlyCoverRect(quads, visible_content_rect);
+ }
+
+ void CoverageVisibleRectIntersectsBounds(
+ LayerTilingData::BorderTexelOption borders) {
+ gfx::Size layer_size(220, 210);
+ gfx::Rect visible_content_rect(layer_size);
+ QuadList quads;
+ SharedQuadStateList shared_states;
+ GetQuads(&quads,
+ &shared_states,
+ gfx::Size(100, 100),
+ layer_size,
+ LayerTilingData::NO_BORDER_TEXELS,
+ visible_content_rect);
+ LayerTestCommon::VerifyQuadsExactlyCoverRect(quads, visible_content_rect);
+ }
+};
+WITH_AND_WITHOUT_BORDER_TEST(CoverageVisibleRectOnTileBoundaries);
+
+WITH_AND_WITHOUT_BORDER_TEST(CoverageVisibleRectIntersectsTiles);
+
+WITH_AND_WITHOUT_BORDER_TEST(CoverageVisibleRectIntersectsBounds);
+
+TEST_F(TiledLayerImplTest, TextureInfoForLayerNoBorders) {
+ gfx::Size tile_size(50, 50);
+ gfx::Size layer_size(250, 250);
+ QuadList quads;
+ SharedQuadStateList shared_states;
+ GetQuads(&quads,
+ &shared_states,
+ tile_size,
+ layer_size,
+ LayerTilingData::NO_BORDER_TEXELS,
+ gfx::Rect(layer_size));
+
+ for (size_t i = 0; i < quads.size(); ++i) {
+ const TileDrawQuad* quad = TileDrawQuad::MaterialCast(quads[i]);
+
+ EXPECT_NE(0u, quad->resource_id) << LayerTestCommon::quad_string << i;
+ EXPECT_EQ(gfx::RectF(gfx::PointF(), tile_size), quad->tex_coord_rect)
+ << LayerTestCommon::quad_string << i;
+ EXPECT_EQ(tile_size, quad->texture_size) << LayerTestCommon::quad_string
+ << i;
+ EXPECT_EQ(gfx::Rect(0, 0, 1, 1), quad->opaque_rect)
+ << LayerTestCommon::quad_string << i;
+ }
+}
+
+TEST_F(TiledLayerImplTest, GPUMemoryUsage) {
+ gfx::Size tile_size(20, 30);
+ int num_tiles_x = 12;
+ int num_tiles_y = 32;
+ gfx::Size layer_size(tile_size.width() * num_tiles_x,
+ tile_size.height() * num_tiles_y);
+
+ scoped_ptr<TiledLayerImpl> layer = CreateLayerNoTiles(
+ tile_size, layer_size, LayerTilingData::NO_BORDER_TEXELS);
+
+ EXPECT_EQ(layer->GPUMemoryUsageInBytes(), 0u);
+
+ ResourceProvider::ResourceId resource_id = 1;
+ layer->PushTileProperties(0, 1, resource_id++, gfx::Rect(0, 0, 1, 1), false);
+ layer->PushTileProperties(2, 3, resource_id++, gfx::Rect(0, 0, 1, 1), false);
+ layer->PushTileProperties(2, 0, resource_id++, gfx::Rect(0, 0, 1, 1), false);
+
+ EXPECT_EQ(
+ layer->GPUMemoryUsageInBytes(),
+ static_cast<size_t>(3 * 4 * tile_size.width() * tile_size.height()));
+
+ ResourceProvider::ResourceId empty_resource(0);
+ layer->PushTileProperties(0, 1, empty_resource, gfx::Rect(0, 0, 1, 1), false);
+ layer->PushTileProperties(2, 3, empty_resource, gfx::Rect(0, 0, 1, 1), false);
+ layer->PushTileProperties(2, 0, empty_resource, gfx::Rect(0, 0, 1, 1), false);
+
+ EXPECT_EQ(layer->GPUMemoryUsageInBytes(), 0u);
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/layers/tiled_layer_unittest.cc b/chromium/cc/layers/tiled_layer_unittest.cc
new file mode 100644
index 00000000000..a2f0ca7a64d
--- /dev/null
+++ b/chromium/cc/layers/tiled_layer_unittest.cc
@@ -0,0 +1,1942 @@
+// Copyright 2011 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 "cc/layers/tiled_layer.h"
+
+#include <limits>
+#include <vector>
+
+#include "cc/debug/overdraw_metrics.h"
+#include "cc/resources/bitmap_content_layer_updater.h"
+#include "cc/resources/layer_painter.h"
+#include "cc/resources/prioritized_resource_manager.h"
+#include "cc/resources/resource_update_controller.h"
+#include "cc/test/animation_test_common.h"
+#include "cc/test/fake_layer_tree_host_client.h"
+#include "cc/test/fake_layer_tree_host_impl.h"
+#include "cc/test/fake_output_surface.h"
+#include "cc/test/fake_proxy.h"
+#include "cc/test/fake_rendering_stats_instrumentation.h"
+#include "cc/test/geometry_test_utils.h"
+#include "cc/test/tiled_layer_test_common.h"
+#include "cc/trees/single_thread_proxy.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+namespace {
+
+class TestOcclusionTracker : public OcclusionTracker {
+ public:
+ TestOcclusionTracker() : OcclusionTracker(gfx::Rect(0, 0, 1000, 1000), true) {
+ stack_.push_back(StackObject());
+ }
+
+ void SetRenderTarget(Layer* render_target) {
+ stack_.back().target = render_target;
+ }
+
+ void SetOcclusion(const Region& occlusion) {
+ stack_.back().occlusion_from_inside_target = occlusion;
+ }
+};
+
+class TiledLayerTest : public testing::Test {
+ public:
+ TiledLayerTest()
+ : proxy_(NULL),
+ output_surface_(CreateFakeOutputSurface()),
+ queue_(make_scoped_ptr(new ResourceUpdateQueue)),
+ fake_layer_impl_tree_host_client_(FakeLayerTreeHostClient::DIRECT_3D),
+ occlusion_(NULL) {
+ settings_.max_partial_texture_updates = std::numeric_limits<size_t>::max();
+ settings_.layer_transforms_should_scale_layer_contents = true;
+ }
+
+ virtual void SetUp() {
+ layer_tree_host_ = LayerTreeHost::Create(&fake_layer_impl_tree_host_client_,
+ settings_,
+ NULL);
+ proxy_ = layer_tree_host_->proxy();
+ resource_manager_ = PrioritizedResourceManager::Create(proxy_);
+ layer_tree_host_->InitializeOutputSurfaceIfNeeded();
+ layer_tree_host_->SetRootLayer(Layer::Create());
+
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(proxy_);
+ resource_provider_ = ResourceProvider::Create(output_surface_.get(), 0);
+ host_impl_ = make_scoped_ptr(new FakeLayerTreeHostImpl(proxy_));
+ }
+
+ virtual ~TiledLayerTest() {
+ ResourceManagerClearAllMemory(resource_manager_.get(),
+ resource_provider_.get());
+
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(proxy_);
+ resource_provider_.reset();
+ host_impl_.reset();
+ }
+
+ void ResourceManagerClearAllMemory(
+ PrioritizedResourceManager* resource_manager,
+ ResourceProvider* resource_provider) {
+ {
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(proxy_);
+ resource_manager->ClearAllMemory(resource_provider);
+ resource_manager->ReduceMemory(resource_provider);
+ }
+ resource_manager->UnlinkAndClearEvictedBackings();
+ }
+
+ void UpdateTextures() {
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(proxy_);
+ DCHECK(queue_);
+ scoped_ptr<ResourceUpdateController> update_controller =
+ ResourceUpdateController::Create(NULL,
+ proxy_->ImplThreadTaskRunner(),
+ queue_.Pass(),
+ resource_provider_.get());
+ update_controller->Finalize();
+ queue_ = make_scoped_ptr(new ResourceUpdateQueue);
+ }
+
+ void LayerPushPropertiesTo(FakeTiledLayer* layer,
+ FakeTiledLayerImpl* layer_impl) {
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(proxy_);
+ layer->PushPropertiesTo(layer_impl);
+ layer->ResetNumDependentsNeedPushProperties();
+ }
+
+ void LayerUpdate(FakeTiledLayer* layer, TestOcclusionTracker* occluded) {
+ DebugScopedSetMainThread main_thread(proxy_);
+ layer->Update(queue_.get(), occluded);
+ }
+
+ void CalcDrawProps(RenderSurfaceLayerList* render_surface_layer_list) {
+ if (occlusion_)
+ occlusion_->SetRenderTarget(layer_tree_host_->root_layer());
+
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ layer_tree_host_->root_layer(),
+ layer_tree_host_->device_viewport_size(),
+ render_surface_layer_list);
+ inputs.device_scale_factor = layer_tree_host_->device_scale_factor();
+ inputs.max_texture_size =
+ layer_tree_host_->GetRendererCapabilities().max_texture_size;
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+ }
+
+ bool UpdateAndPush(const scoped_refptr<FakeTiledLayer>& layer1,
+ const scoped_ptr<FakeTiledLayerImpl>& layer_impl1) {
+ scoped_refptr<FakeTiledLayer> layer2;
+ scoped_ptr<FakeTiledLayerImpl> layer_impl2;
+ return UpdateAndPush(layer1, layer_impl1, layer2, layer_impl2);
+ }
+
+ bool UpdateAndPush(const scoped_refptr<FakeTiledLayer>& layer1,
+ const scoped_ptr<FakeTiledLayerImpl>& layer_impl1,
+ const scoped_refptr<FakeTiledLayer>& layer2,
+ const scoped_ptr<FakeTiledLayerImpl>& layer_impl2) {
+ // Get textures
+ resource_manager_->ClearPriorities();
+ if (layer1.get())
+ layer1->SetTexturePriorities(priority_calculator_);
+ if (layer2.get())
+ layer2->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+
+ // Save paint properties
+ if (layer1.get())
+ layer1->SavePaintProperties();
+ if (layer2.get())
+ layer2->SavePaintProperties();
+
+ // Update content
+ if (layer1.get())
+ layer1->Update(queue_.get(), occlusion_);
+ if (layer2.get())
+ layer2->Update(queue_.get(), occlusion_);
+
+ bool needs_update = false;
+ if (layer1.get())
+ needs_update |= layer1->NeedsIdlePaint();
+ if (layer2.get())
+ needs_update |= layer2->NeedsIdlePaint();
+
+ // Update textures and push.
+ UpdateTextures();
+ if (layer1.get())
+ LayerPushPropertiesTo(layer1.get(), layer_impl1.get());
+ if (layer2.get())
+ LayerPushPropertiesTo(layer2.get(), layer_impl2.get());
+
+ return needs_update;
+ }
+
+ public:
+ Proxy* proxy_;
+ LayerTreeSettings settings_;
+ scoped_ptr<OutputSurface> output_surface_;
+ scoped_ptr<ResourceProvider> resource_provider_;
+ scoped_ptr<ResourceUpdateQueue> queue_;
+ PriorityCalculator priority_calculator_;
+ FakeLayerTreeHostClient fake_layer_impl_tree_host_client_;
+ scoped_ptr<LayerTreeHost> layer_tree_host_;
+ scoped_ptr<FakeLayerTreeHostImpl> host_impl_;
+ scoped_ptr<PrioritizedResourceManager> resource_manager_;
+ TestOcclusionTracker* occlusion_;
+};
+
+TEST_F(TiledLayerTest, PushDirtyTiles) {
+ scoped_refptr<FakeTiledLayer> layer =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+ scoped_ptr<FakeTiledLayerImpl> layer_impl =
+ make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1));
+ RenderSurfaceLayerList render_surface_layer_list;
+
+ layer_tree_host_->root_layer()->AddChild(layer);
+
+ // The tile size is 100x100, so this invalidates and then paints two tiles.
+ layer->SetBounds(gfx::Size(100, 200));
+ CalcDrawProps(&render_surface_layer_list);
+ UpdateAndPush(layer, layer_impl);
+
+ // We should have both tiles on the impl side.
+ EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 0));
+ EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 1));
+
+ // Invalidates both tiles, but then only update one of them.
+ layer->InvalidateContentRect(gfx::Rect(0, 0, 100, 200));
+ layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 100);
+ UpdateAndPush(layer, layer_impl);
+
+ // We should only have the first tile since the other tile was invalidated but
+ // not painted.
+ EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 0));
+ EXPECT_FALSE(layer_impl->HasResourceIdForTileAt(0, 1));
+}
+
+TEST_F(TiledLayerTest, PushOccludedDirtyTiles) {
+ scoped_refptr<FakeTiledLayer> layer =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+ scoped_ptr<FakeTiledLayerImpl> layer_impl =
+ make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1));
+ TestOcclusionTracker occluded;
+ occlusion_ = &occluded;
+ layer_tree_host_->SetViewportSize(gfx::Size(1000, 1000));
+
+ layer_tree_host_->root_layer()->AddChild(layer);
+
+ {
+ RenderSurfaceLayerList render_surface_layer_list;
+
+ // The tile size is 100x100, so this invalidates and then paints two tiles.
+ layer->SetBounds(gfx::Size(100, 200));
+ CalcDrawProps(&render_surface_layer_list);
+ UpdateAndPush(layer, layer_impl);
+
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1);
+ EXPECT_NEAR(
+ occluded.overdraw_metrics()->pixels_uploaded_translucent(), 20000, 1);
+ EXPECT_EQ(0, occluded.overdraw_metrics()->tiles_culled_for_upload());
+
+ // We should have both tiles on the impl side.
+ EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 0));
+ EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 1));
+ }
+
+ {
+ RenderSurfaceLayerList render_surface_layer_list;
+
+ // Invalidates part of the top tile...
+ layer->InvalidateContentRect(gfx::Rect(0, 0, 50, 50));
+ // ....but the area is occluded.
+ occluded.SetOcclusion(gfx::Rect(0, 0, 50, 50));
+ CalcDrawProps(&render_surface_layer_list);
+ UpdateAndPush(layer, layer_impl);
+
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1);
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(),
+ 20000 + 2500,
+ 1);
+ EXPECT_EQ(0, occluded.overdraw_metrics()->tiles_culled_for_upload());
+
+ // We should still have both tiles, as part of the top tile is still
+ // unoccluded.
+ EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 0));
+ EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 1));
+ }
+}
+
+TEST_F(TiledLayerTest, PushDeletedTiles) {
+ scoped_refptr<FakeTiledLayer> layer =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+ scoped_ptr<FakeTiledLayerImpl> layer_impl =
+ make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1));
+ RenderSurfaceLayerList render_surface_layer_list;
+
+ layer_tree_host_->root_layer()->AddChild(layer);
+
+ // The tile size is 100x100, so this invalidates and then paints two tiles.
+ layer->SetBounds(gfx::Size(100, 200));
+ CalcDrawProps(&render_surface_layer_list);
+ UpdateAndPush(layer, layer_impl);
+
+ // We should have both tiles on the impl side.
+ EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 0));
+ EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 1));
+
+ resource_manager_->ClearPriorities();
+ ResourceManagerClearAllMemory(resource_manager_.get(),
+ resource_provider_.get());
+ resource_manager_->SetMaxMemoryLimitBytes(4 * 1024 * 1024);
+
+ // This should drop the tiles on the impl thread.
+ LayerPushPropertiesTo(layer.get(), layer_impl.get());
+
+ // We should now have no textures on the impl thread.
+ EXPECT_FALSE(layer_impl->HasResourceIdForTileAt(0, 0));
+ EXPECT_FALSE(layer_impl->HasResourceIdForTileAt(0, 1));
+
+ // This should recreate and update one of the deleted textures.
+ layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 100);
+ UpdateAndPush(layer, layer_impl);
+
+ // We should have one tiles on the impl side.
+ EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 0));
+ EXPECT_FALSE(layer_impl->HasResourceIdForTileAt(0, 1));
+}
+
+TEST_F(TiledLayerTest, PushIdlePaintTiles) {
+ scoped_refptr<FakeTiledLayer> layer =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+ scoped_ptr<FakeTiledLayerImpl> layer_impl =
+ make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1));
+ RenderSurfaceLayerList render_surface_layer_list;
+
+ layer_tree_host_->root_layer()->AddChild(layer);
+
+ // The tile size is 100x100. Setup 5x5 tiles with one visible tile in the
+ // center. This paints 1 visible of the 25 invalid tiles.
+ layer->SetBounds(gfx::Size(500, 500));
+ CalcDrawProps(&render_surface_layer_list);
+ layer->draw_properties().visible_content_rect = gfx::Rect(200, 200, 100, 100);
+ bool needs_update = UpdateAndPush(layer, layer_impl);
+ // We should need idle-painting for surrounding tiles.
+ EXPECT_TRUE(needs_update);
+
+ // We should have one tile on the impl side.
+ EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(2, 2));
+
+ // For the next four updates, we should detect we still need idle painting.
+ for (int i = 0; i < 4; i++) {
+ needs_update = UpdateAndPush(layer, layer_impl);
+ EXPECT_TRUE(needs_update);
+ }
+
+ // We should always finish painting eventually.
+ for (int i = 0; i < 20; i++)
+ needs_update = UpdateAndPush(layer, layer_impl);
+
+ // We should have pre-painted all of the surrounding tiles.
+ for (int i = 0; i < 5; i++) {
+ for (int j = 0; j < 5; j++)
+ EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(i, j));
+ }
+
+ EXPECT_FALSE(needs_update);
+}
+
+TEST_F(TiledLayerTest, PredictivePainting) {
+ scoped_refptr<FakeTiledLayer> layer =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+ scoped_ptr<FakeTiledLayerImpl> layer_impl =
+ make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1));
+
+ layer_tree_host_->root_layer()->AddChild(layer);
+
+ // Prepainting should occur in the scroll direction first, and the
+ // visible rect should be extruded only along the dominant axis.
+ gfx::Vector2d directions[6] = { gfx::Vector2d(-10, 0), gfx::Vector2d(10, 0),
+ gfx::Vector2d(0, -10), gfx::Vector2d(0, 10),
+ gfx::Vector2d(10, 20),
+ gfx::Vector2d(-20, 10) };
+ // We should push all tiles that touch the extruded visible rect.
+ gfx::Rect pushed_visible_tiles[6] = {
+ gfx::Rect(2, 2, 2, 1), gfx::Rect(1, 2, 2, 1), gfx::Rect(2, 2, 1, 2),
+ gfx::Rect(2, 1, 1, 2), gfx::Rect(2, 1, 1, 2), gfx::Rect(2, 2, 2, 1)
+ };
+ // The first pre-paint should also paint first in the scroll
+ // direction so we should find one additional tile in the scroll direction.
+ gfx::Rect pushed_prepaint_tiles[6] = {
+ gfx::Rect(2, 2, 3, 1), gfx::Rect(0, 2, 3, 1), gfx::Rect(2, 2, 1, 3),
+ gfx::Rect(2, 0, 1, 3), gfx::Rect(2, 0, 1, 3), gfx::Rect(2, 2, 3, 1)
+ };
+ for (int k = 0; k < 6; k++) {
+ // The tile size is 100x100. Setup 5x5 tiles with one visible tile
+ // in the center.
+ gfx::Size bounds = gfx::Size(500, 500);
+ gfx::Rect visible_rect = gfx::Rect(200, 200, 100, 100);
+ gfx::Rect previous_visible_rect =
+ gfx::Rect(visible_rect.origin() + directions[k], visible_rect.size());
+ gfx::Rect next_visible_rect =
+ gfx::Rect(visible_rect.origin() - directions[k], visible_rect.size());
+
+ // Setup. Use the previous_visible_rect to setup the prediction for next
+ // frame.
+ layer->SetBounds(bounds);
+
+ RenderSurfaceLayerList render_surface_layer_list;
+ CalcDrawProps(&render_surface_layer_list);
+ layer->draw_properties().visible_content_rect = previous_visible_rect;
+ bool needs_update = UpdateAndPush(layer, layer_impl);
+
+ // Invalidate and move the visible_rect in the scroll direction.
+ // Check that the correct tiles have been painted in the visible pass.
+ layer->SetNeedsDisplay();
+ layer->draw_properties().visible_content_rect = visible_rect;
+ needs_update = UpdateAndPush(layer, layer_impl);
+ for (int i = 0; i < 5; i++) {
+ for (int j = 0; j < 5; j++)
+ EXPECT_EQ(layer_impl->HasResourceIdForTileAt(i, j),
+ pushed_visible_tiles[k].Contains(i, j));
+ }
+
+ // Move the transform in the same direction without invalidating.
+ // Check that non-visible pre-painting occured in the correct direction.
+ // Ignore diagonal scrolls here (k > 3) as these have new visible content
+ // now.
+ if (k <= 3) {
+ layer->draw_properties().visible_content_rect = next_visible_rect;
+ needs_update = UpdateAndPush(layer, layer_impl);
+ for (int i = 0; i < 5; i++) {
+ for (int j = 0; j < 5; j++)
+ EXPECT_EQ(layer_impl->HasResourceIdForTileAt(i, j),
+ pushed_prepaint_tiles[k].Contains(i, j));
+ }
+ }
+
+ // We should always finish painting eventually.
+ for (int i = 0; i < 20; i++)
+ needs_update = UpdateAndPush(layer, layer_impl);
+ EXPECT_FALSE(needs_update);
+ }
+}
+
+TEST_F(TiledLayerTest, PushTilesAfterIdlePaintFailed) {
+ // Start with 2mb of memory, but the test is going to try to use just more
+ // than 1mb, so we reduce to 1mb later.
+ resource_manager_->SetMaxMemoryLimitBytes(2 * 1024 * 1024);
+ scoped_refptr<FakeTiledLayer> layer1 =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+ scoped_ptr<FakeTiledLayerImpl> layer_impl1 =
+ make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1));
+ scoped_refptr<FakeTiledLayer> layer2 =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+ scoped_ptr<FakeTiledLayerImpl> layer_impl2 =
+ make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 2));
+ RenderSurfaceLayerList render_surface_layer_list;
+
+ layer_tree_host_->root_layer()->AddChild(layer1);
+ layer_tree_host_->root_layer()->AddChild(layer2);
+
+ // For this test we have two layers. layer1 exhausts most texture memory,
+ // leaving room for 2 more tiles from layer2, but not all three tiles. First
+ // we paint layer1, and one tile from layer2. Then when we idle paint layer2,
+ // we will fail on the third tile of layer2, and this should not leave the
+ // second tile in a bad state.
+
+ // This uses 960000 bytes, leaving 88576 bytes of memory left, which is enough
+ // for 2 tiles only in the other layer.
+ gfx::Rect layer1_rect(0, 0, 100, 2400);
+
+ // This requires 4*30000 bytes of memory.
+ gfx::Rect layer2_rect(0, 0, 100, 300);
+
+ // Paint a single tile in layer2 so that it will idle paint.
+ layer1->SetBounds(layer1_rect.size());
+ layer2->SetBounds(layer2_rect.size());
+ CalcDrawProps(&render_surface_layer_list);
+ layer1->draw_properties().visible_content_rect = layer1_rect;
+ layer2->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 100);
+ bool needs_update = UpdateAndPush(layer1, layer_impl1, layer2, layer_impl2);
+ // We should need idle-painting for both remaining tiles in layer2.
+ EXPECT_TRUE(needs_update);
+
+ // Reduce our memory limits to 1mb.
+ resource_manager_->SetMaxMemoryLimitBytes(1024 * 1024);
+
+ // Now idle paint layer2. We are going to run out of memory though!
+ // Oh well, commit the frame and push.
+ for (int i = 0; i < 4; i++) {
+ needs_update = UpdateAndPush(layer1, layer_impl1, layer2, layer_impl2);
+ }
+
+ // Sanity check, we should have textures for the big layer.
+ EXPECT_TRUE(layer_impl1->HasResourceIdForTileAt(0, 0));
+ EXPECT_TRUE(layer_impl1->HasResourceIdForTileAt(0, 23));
+
+ // We should only have the first two tiles from layer2 since
+ // it failed to idle update the last tile.
+ EXPECT_TRUE(layer_impl2->HasResourceIdForTileAt(0, 0));
+ EXPECT_TRUE(layer_impl2->HasResourceIdForTileAt(0, 0));
+ EXPECT_TRUE(layer_impl2->HasResourceIdForTileAt(0, 1));
+ EXPECT_TRUE(layer_impl2->HasResourceIdForTileAt(0, 1));
+
+ EXPECT_FALSE(needs_update);
+ EXPECT_FALSE(layer_impl2->HasResourceIdForTileAt(0, 2));
+}
+
+TEST_F(TiledLayerTest, PushIdlePaintedOccludedTiles) {
+ scoped_refptr<FakeTiledLayer> layer =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+ scoped_ptr<FakeTiledLayerImpl> layer_impl =
+ make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1));
+ RenderSurfaceLayerList render_surface_layer_list;
+ TestOcclusionTracker occluded;
+ occlusion_ = &occluded;
+
+ layer_tree_host_->root_layer()->AddChild(layer);
+
+ // The tile size is 100x100, so this invalidates one occluded tile, culls it
+ // during paint, but prepaints it.
+ occluded.SetOcclusion(gfx::Rect(0, 0, 100, 100));
+
+ layer->SetBounds(gfx::Size(100, 100));
+ CalcDrawProps(&render_surface_layer_list);
+ layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 100);
+ UpdateAndPush(layer, layer_impl);
+
+ // We should have the prepainted tile on the impl side, but culled it during
+ // paint.
+ EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 0));
+ EXPECT_EQ(1, occluded.overdraw_metrics()->tiles_culled_for_upload());
+}
+
+TEST_F(TiledLayerTest, PushTilesMarkedDirtyDuringPaint) {
+ scoped_refptr<FakeTiledLayer> layer =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+ scoped_ptr<FakeTiledLayerImpl> layer_impl =
+ make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1));
+ RenderSurfaceLayerList render_surface_layer_list;
+
+ layer_tree_host_->root_layer()->AddChild(layer);
+
+ // The tile size is 100x100, so this invalidates and then paints two tiles.
+ // However, during the paint, we invalidate one of the tiles. This should
+ // not prevent the tile from being pushed.
+ layer->fake_layer_updater()->SetRectToInvalidate(
+ gfx::Rect(0, 50, 100, 50), layer.get());
+ layer->SetBounds(gfx::Size(100, 200));
+ CalcDrawProps(&render_surface_layer_list);
+ layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 200);
+ UpdateAndPush(layer, layer_impl);
+
+ // We should have both tiles on the impl side.
+ EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 0));
+ EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 1));
+}
+
+TEST_F(TiledLayerTest, PushTilesLayerMarkedDirtyDuringPaintOnNextLayer) {
+ scoped_refptr<FakeTiledLayer> layer1 =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+ scoped_refptr<FakeTiledLayer> layer2 =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+ scoped_ptr<FakeTiledLayerImpl> layer1_impl =
+ make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1));
+ scoped_ptr<FakeTiledLayerImpl> layer2_impl =
+ make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 2));
+ RenderSurfaceLayerList render_surface_layer_list;
+
+ layer_tree_host_->root_layer()->AddChild(layer1);
+ layer_tree_host_->root_layer()->AddChild(layer2);
+
+ // Invalidate a tile on layer1, during update of layer 2.
+ layer2->fake_layer_updater()->SetRectToInvalidate(
+ gfx::Rect(0, 50, 100, 50), layer1.get());
+ layer1->SetBounds(gfx::Size(100, 200));
+ layer2->SetBounds(gfx::Size(100, 200));
+ CalcDrawProps(&render_surface_layer_list);
+ layer1->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 200);
+ layer2->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 200);
+ UpdateAndPush(layer1, layer1_impl, layer2, layer2_impl);
+
+ // We should have both tiles on the impl side for all layers.
+ EXPECT_TRUE(layer1_impl->HasResourceIdForTileAt(0, 0));
+ EXPECT_TRUE(layer1_impl->HasResourceIdForTileAt(0, 1));
+ EXPECT_TRUE(layer2_impl->HasResourceIdForTileAt(0, 0));
+ EXPECT_TRUE(layer2_impl->HasResourceIdForTileAt(0, 1));
+}
+
+TEST_F(TiledLayerTest, PushTilesLayerMarkedDirtyDuringPaintOnPreviousLayer) {
+ scoped_refptr<FakeTiledLayer> layer1 =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+ scoped_refptr<FakeTiledLayer> layer2 =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+ scoped_ptr<FakeTiledLayerImpl> layer1_impl =
+ make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1));
+ scoped_ptr<FakeTiledLayerImpl> layer2_impl =
+ make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 2));
+ RenderSurfaceLayerList render_surface_layer_list;
+
+ layer_tree_host_->root_layer()->AddChild(layer1);
+ layer_tree_host_->root_layer()->AddChild(layer2);
+
+ layer1->fake_layer_updater()->SetRectToInvalidate(
+ gfx::Rect(0, 50, 100, 50), layer2.get());
+ layer1->SetBounds(gfx::Size(100, 200));
+ layer2->SetBounds(gfx::Size(100, 200));
+ CalcDrawProps(&render_surface_layer_list);
+ layer1->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 200);
+ layer2->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 200);
+ UpdateAndPush(layer1, layer1_impl, layer2, layer2_impl);
+
+ // We should have both tiles on the impl side for all layers.
+ EXPECT_TRUE(layer1_impl->HasResourceIdForTileAt(0, 0));
+ EXPECT_TRUE(layer1_impl->HasResourceIdForTileAt(0, 1));
+ EXPECT_TRUE(layer2_impl->HasResourceIdForTileAt(0, 0));
+ EXPECT_TRUE(layer2_impl->HasResourceIdForTileAt(0, 1));
+}
+
+TEST_F(TiledLayerTest, PaintSmallAnimatedLayersImmediately) {
+ // Create a LayerTreeHost that has the right viewportsize,
+ // so the layer is considered small enough.
+ bool run_out_of_memory[2] = { false, true };
+ for (int i = 0; i < 2; i++) {
+ // Create a layer with 5x5 tiles, with 4x4 size viewport.
+ int viewport_width = 4 * FakeTiledLayer::tile_size().width();
+ int viewport_height = 4 * FakeTiledLayer::tile_size().width();
+ int layer_width = 5 * FakeTiledLayer::tile_size().width();
+ int layer_height = 5 * FakeTiledLayer::tile_size().height();
+ int memory_for_layer = layer_width * layer_height * 4;
+ layer_tree_host_->SetViewportSize(
+ gfx::Size(viewport_width, viewport_height));
+
+ // Use 10x5 tiles to run out of memory.
+ if (run_out_of_memory[i])
+ layer_width *= 2;
+
+ resource_manager_->SetMaxMemoryLimitBytes(memory_for_layer);
+
+ scoped_refptr<FakeTiledLayer> layer =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+ scoped_ptr<FakeTiledLayerImpl> layer_impl =
+ make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1));
+ RenderSurfaceLayerList render_surface_layer_list;
+
+ layer_tree_host_->root_layer()->AddChild(layer);
+
+ // Full size layer with half being visible.
+ layer->SetBounds(gfx::Size(layer_width, layer_height));
+ gfx::Rect visible_rect(0, 0, layer_width / 2, layer_height);
+ CalcDrawProps(&render_surface_layer_list);
+
+ // Pretend the layer is animating.
+ layer->draw_properties().target_space_transform_is_animating = true;
+ layer->draw_properties().visible_content_rect = visible_rect;
+ layer->SetLayerTreeHost(layer_tree_host_.get());
+
+ // The layer should paint its entire contents on the first paint
+ // if it is close to the viewport size and has the available memory.
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), NULL);
+ UpdateTextures();
+ LayerPushPropertiesTo(layer.get(), layer_impl.get());
+
+ // We should have all the tiles for the small animated layer.
+ // We should still have the visible tiles when we didn't
+ // have enough memory for all the tiles.
+ if (!run_out_of_memory[i]) {
+ for (int i = 0; i < 5; ++i) {
+ for (int j = 0; j < 5; ++j)
+ EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(i, j));
+ }
+ } else {
+ for (int i = 0; i < 10; ++i) {
+ for (int j = 0; j < 5; ++j)
+ EXPECT_EQ(layer_impl->HasResourceIdForTileAt(i, j), i < 5);
+ }
+ }
+
+ layer->RemoveFromParent();
+ }
+}
+
+TEST_F(TiledLayerTest, IdlePaintOutOfMemory) {
+ scoped_refptr<FakeTiledLayer> layer =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+ scoped_ptr<FakeTiledLayerImpl> layer_impl =
+ make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1));
+ RenderSurfaceLayerList render_surface_layer_list;
+
+ layer_tree_host_->root_layer()->AddChild(layer);
+
+ // We have enough memory for only the visible rect, so we will run out of
+ // memory in first idle paint.
+ int memory_limit = 4 * 100 * 100; // 1 tiles, 4 bytes per pixel.
+ resource_manager_->SetMaxMemoryLimitBytes(memory_limit);
+
+ // The tile size is 100x100, so this invalidates and then paints two tiles.
+ bool needs_update = false;
+ layer->SetBounds(gfx::Size(300, 300));
+ CalcDrawProps(&render_surface_layer_list);
+ layer->draw_properties().visible_content_rect = gfx::Rect(100, 100, 100, 100);
+ for (int i = 0; i < 2; i++)
+ needs_update = UpdateAndPush(layer, layer_impl);
+
+ // Idle-painting should see no more priority tiles for painting.
+ EXPECT_FALSE(needs_update);
+
+ // We should have one tile on the impl side.
+ EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(1, 1));
+}
+
+TEST_F(TiledLayerTest, IdlePaintZeroSizedLayer) {
+ scoped_refptr<FakeTiledLayer> layer =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+ scoped_ptr<FakeTiledLayerImpl> layer_impl =
+ make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1));
+
+ layer_tree_host_->root_layer()->AddChild(layer);
+
+ bool animating[2] = { false, true };
+ for (int i = 0; i < 2; i++) {
+ // Pretend the layer is animating.
+ layer->draw_properties().target_space_transform_is_animating = animating[i];
+
+ // The layer's bounds are empty.
+ // Empty layers don't paint or idle-paint.
+ layer->SetBounds(gfx::Size());
+
+ RenderSurfaceLayerList render_surface_layer_list;
+ CalcDrawProps(&render_surface_layer_list);
+ layer->draw_properties().visible_content_rect = gfx::Rect();
+ bool needs_update = UpdateAndPush(layer, layer_impl);
+
+ // Empty layers don't have tiles.
+ EXPECT_EQ(0u, layer->NumPaintedTiles());
+
+ // Empty layers don't need prepaint.
+ EXPECT_FALSE(needs_update);
+
+ // Empty layers don't have tiles.
+ EXPECT_FALSE(layer_impl->HasResourceIdForTileAt(0, 0));
+ }
+}
+
+TEST_F(TiledLayerTest, IdlePaintNonVisibleLayers) {
+ scoped_refptr<FakeTiledLayer> layer =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+ scoped_ptr<FakeTiledLayerImpl> layer_impl =
+ make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1));
+
+ // Alternate between not visible and visible.
+ gfx::Rect v(0, 0, 100, 100);
+ gfx::Rect nv(0, 0, 0, 0);
+ gfx::Rect visible_rect[10] = { nv, nv, v, v, nv, nv, v, v, nv, nv };
+ bool invalidate[10] = { true, true, true, true, true, true, true, true, false,
+ false };
+
+ // We should not have any tiles except for when the layer was visible
+ // or after the layer was visible and we didn't invalidate.
+ bool have_tile[10] = { false, false, true, true, false, false, true, true,
+ true, true };
+
+ layer_tree_host_->root_layer()->AddChild(layer);
+
+ for (int i = 0; i < 10; i++) {
+ layer->SetBounds(gfx::Size(100, 100));
+
+ RenderSurfaceLayerList render_surface_layer_list;
+ CalcDrawProps(&render_surface_layer_list);
+ layer->draw_properties().visible_content_rect = visible_rect[i];
+
+ if (invalidate[i])
+ layer->InvalidateContentRect(gfx::Rect(0, 0, 100, 100));
+ bool needs_update = UpdateAndPush(layer, layer_impl);
+
+ // We should never signal idle paint, as we painted the entire layer
+ // or the layer was not visible.
+ EXPECT_FALSE(needs_update);
+ EXPECT_EQ(layer_impl->HasResourceIdForTileAt(0, 0), have_tile[i]);
+ }
+}
+
+TEST_F(TiledLayerTest, InvalidateFromPrepare) {
+ scoped_refptr<FakeTiledLayer> layer =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+ scoped_ptr<FakeTiledLayerImpl> layer_impl =
+ make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1));
+ RenderSurfaceLayerList render_surface_layer_list;
+
+ layer_tree_host_->root_layer()->AddChild(layer);
+
+ // The tile size is 100x100, so this invalidates and then paints two tiles.
+ layer->SetBounds(gfx::Size(100, 200));
+ CalcDrawProps(&render_surface_layer_list);
+ layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 200);
+ UpdateAndPush(layer, layer_impl);
+
+ // We should have both tiles on the impl side.
+ EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 0));
+ EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 1));
+
+ layer->fake_layer_updater()->ClearPrepareCount();
+ // Invoke update again. As the layer is valid update shouldn't be invoked on
+ // the LayerUpdater.
+ UpdateAndPush(layer, layer_impl);
+ EXPECT_EQ(0, layer->fake_layer_updater()->prepare_count());
+
+ // SetRectToInvalidate triggers InvalidateContentRect() being invoked from
+ // update.
+ layer->fake_layer_updater()->SetRectToInvalidate(
+ gfx::Rect(25, 25, 50, 50), layer.get());
+ layer->fake_layer_updater()->ClearPrepareCount();
+ layer->InvalidateContentRect(gfx::Rect(0, 0, 50, 50));
+ UpdateAndPush(layer, layer_impl);
+ EXPECT_EQ(1, layer->fake_layer_updater()->prepare_count());
+ layer->fake_layer_updater()->ClearPrepareCount();
+
+ // The layer should still be invalid as update invoked invalidate.
+ UpdateAndPush(layer, layer_impl); // visible
+ EXPECT_EQ(1, layer->fake_layer_updater()->prepare_count());
+}
+
+TEST_F(TiledLayerTest, VerifyUpdateRectWhenContentBoundsAreScaled) {
+ // The update rect (that indicates what was actually painted) should be in
+ // layer space, not the content space.
+ scoped_refptr<FakeTiledLayerWithScaledBounds> layer = make_scoped_refptr(
+ new FakeTiledLayerWithScaledBounds(resource_manager_.get()));
+
+ layer_tree_host_->root_layer()->AddChild(layer);
+
+ gfx::Rect layer_bounds(0, 0, 300, 200);
+ gfx::Rect content_bounds(0, 0, 200, 250);
+
+ layer->SetBounds(layer_bounds.size());
+ layer->SetContentBounds(content_bounds.size());
+ layer->draw_properties().visible_content_rect = content_bounds;
+
+ // On first update, the update_rect includes all tiles, even beyond the
+ // boundaries of the layer.
+ // However, it should still be in layer space, not content space.
+ layer->InvalidateContentRect(content_bounds);
+
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), NULL);
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(0, 0, 300, 300 * 0.8), layer->update_rect());
+ UpdateTextures();
+
+ // After the tiles are updated once, another invalidate only needs to update
+ // the bounds of the layer.
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->InvalidateContentRect(content_bounds);
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), NULL);
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(layer_bounds), layer->update_rect());
+ UpdateTextures();
+
+ // Partial re-paint should also be represented by the update rect in layer
+ // space, not content space.
+ gfx::Rect partial_damage(30, 100, 10, 10);
+ layer->InvalidateContentRect(partial_damage);
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), NULL);
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(45, 80, 15, 8), layer->update_rect());
+}
+
+TEST_F(TiledLayerTest, VerifyInvalidationWhenContentsScaleChanges) {
+ scoped_refptr<FakeTiledLayer> layer =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+ scoped_ptr<FakeTiledLayerImpl> layer_impl =
+ make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1));
+ RenderSurfaceLayerList render_surface_layer_list;
+
+ layer_tree_host_->root_layer()->AddChild(layer);
+
+ // Create a layer with one tile.
+ layer->SetBounds(gfx::Size(100, 100));
+ CalcDrawProps(&render_surface_layer_list);
+ layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 100);
+ layer->Update(queue_.get(), NULL);
+ UpdateTextures();
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(0, 0, 100, 100),
+ layer->last_needs_display_rect());
+
+ // Push the tiles to the impl side and check that there is exactly one.
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), NULL);
+ UpdateTextures();
+ LayerPushPropertiesTo(layer.get(), layer_impl.get());
+ EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 0));
+ EXPECT_FALSE(layer_impl->HasResourceIdForTileAt(0, 1));
+ EXPECT_FALSE(layer_impl->HasResourceIdForTileAt(1, 0));
+ EXPECT_FALSE(layer_impl->HasResourceIdForTileAt(1, 1));
+
+ layer->SetNeedsDisplayRect(gfx::Rect());
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(), layer->last_needs_display_rect());
+
+ // Change the contents scale.
+ layer->UpdateContentsScale(2.f);
+ layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 200, 200);
+
+ // The impl side should get 2x2 tiles now.
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), NULL);
+ UpdateTextures();
+ LayerPushPropertiesTo(layer.get(), layer_impl.get());
+ EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 0));
+ EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 1));
+ EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(1, 0));
+ EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(1, 1));
+
+ // Verify that changing the contents scale caused invalidation, and
+ // that the layer-space rectangle requiring painting is not scaled.
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(0, 0, 100, 100),
+ layer->last_needs_display_rect());
+
+ // Invalidate the entire layer again, but do not paint. All tiles should be
+ // gone now from the impl side.
+ layer->SetNeedsDisplay();
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+
+ LayerPushPropertiesTo(layer.get(), layer_impl.get());
+ EXPECT_FALSE(layer_impl->HasResourceIdForTileAt(0, 0));
+ EXPECT_FALSE(layer_impl->HasResourceIdForTileAt(0, 1));
+ EXPECT_FALSE(layer_impl->HasResourceIdForTileAt(1, 0));
+ EXPECT_FALSE(layer_impl->HasResourceIdForTileAt(1, 1));
+}
+
+TEST_F(TiledLayerTest, SkipsDrawGetsReset) {
+ // Create two 300 x 300 tiled layers.
+ gfx::Size content_bounds(300, 300);
+ gfx::Rect content_rect(content_bounds);
+
+ // We have enough memory for only one of the two layers.
+ int memory_limit = 4 * 300 * 300; // 4 bytes per pixel.
+
+ scoped_refptr<FakeTiledLayer> root_layer = make_scoped_refptr(
+ new FakeTiledLayer(layer_tree_host_->contents_texture_manager()));
+ scoped_refptr<FakeTiledLayer> child_layer = make_scoped_refptr(
+ new FakeTiledLayer(layer_tree_host_->contents_texture_manager()));
+ root_layer->AddChild(child_layer);
+
+ root_layer->SetBounds(content_bounds);
+ root_layer->draw_properties().visible_content_rect = content_rect;
+ root_layer->SetPosition(gfx::PointF(0, 0));
+ child_layer->SetBounds(content_bounds);
+ child_layer->draw_properties().visible_content_rect = content_rect;
+ child_layer->SetPosition(gfx::PointF(0, 0));
+ root_layer->InvalidateContentRect(content_rect);
+ child_layer->InvalidateContentRect(content_rect);
+
+ layer_tree_host_->SetRootLayer(root_layer);
+ layer_tree_host_->SetViewportSize(gfx::Size(300, 300));
+
+ layer_tree_host_->UpdateLayers(queue_.get(), memory_limit);
+
+ // We'll skip the root layer.
+ EXPECT_TRUE(root_layer->SkipsDraw());
+ EXPECT_FALSE(child_layer->SkipsDraw());
+
+ layer_tree_host_->CommitComplete();
+
+ // Remove the child layer.
+ root_layer->RemoveAllChildren();
+
+ layer_tree_host_->UpdateLayers(queue_.get(), memory_limit);
+ EXPECT_FALSE(root_layer->SkipsDraw());
+
+ ResourceManagerClearAllMemory(layer_tree_host_->contents_texture_manager(),
+ resource_provider_.get());
+ layer_tree_host_->SetRootLayer(NULL);
+}
+
+TEST_F(TiledLayerTest, ResizeToSmaller) {
+ scoped_refptr<FakeTiledLayer> layer =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+
+ layer_tree_host_->root_layer()->AddChild(layer);
+
+ layer->SetBounds(gfx::Size(700, 700));
+ layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 700, 700);
+ layer->InvalidateContentRect(gfx::Rect(0, 0, 700, 700));
+
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), NULL);
+
+ layer->SetBounds(gfx::Size(200, 200));
+ layer->InvalidateContentRect(gfx::Rect(0, 0, 200, 200));
+}
+
+TEST_F(TiledLayerTest, HugeLayerUpdateCrash) {
+ scoped_refptr<FakeTiledLayer> layer =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+
+ layer_tree_host_->root_layer()->AddChild(layer);
+
+ int size = 1 << 30;
+ layer->SetBounds(gfx::Size(size, size));
+ layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 700, 700);
+ layer->InvalidateContentRect(gfx::Rect(0, 0, size, size));
+
+ // Ensure no crash for bounds where size * size would overflow an int.
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), NULL);
+}
+
+class TiledLayerPartialUpdateTest : public TiledLayerTest {
+ public:
+ TiledLayerPartialUpdateTest() { settings_.max_partial_texture_updates = 4; }
+};
+
+TEST_F(TiledLayerPartialUpdateTest, PartialUpdates) {
+ // Create one 300 x 200 tiled layer with 3 x 2 tiles.
+ gfx::Size content_bounds(300, 200);
+ gfx::Rect content_rect(content_bounds);
+
+ scoped_refptr<FakeTiledLayer> layer = make_scoped_refptr(
+ new FakeTiledLayer(layer_tree_host_->contents_texture_manager()));
+ layer->SetBounds(content_bounds);
+ layer->SetPosition(gfx::PointF(0, 0));
+ layer->draw_properties().visible_content_rect = content_rect;
+ layer->InvalidateContentRect(content_rect);
+
+ layer_tree_host_->SetRootLayer(layer);
+ layer_tree_host_->SetViewportSize(gfx::Size(300, 200));
+
+ // Full update of all 6 tiles.
+ layer_tree_host_->UpdateLayers(queue_.get(),
+ std::numeric_limits<size_t>::max());
+ {
+ scoped_ptr<FakeTiledLayerImpl> layer_impl =
+ make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1));
+ EXPECT_EQ(6u, queue_->FullUploadSize());
+ EXPECT_EQ(0u, queue_->PartialUploadSize());
+ UpdateTextures();
+ EXPECT_EQ(6, layer->fake_layer_updater()->update_count());
+ EXPECT_FALSE(queue_->HasMoreUpdates());
+ layer->fake_layer_updater()->ClearUpdateCount();
+ LayerPushPropertiesTo(layer.get(), layer_impl.get());
+ }
+ layer_tree_host_->CommitComplete();
+
+ // Full update of 3 tiles and partial update of 3 tiles.
+ layer->InvalidateContentRect(gfx::Rect(0, 0, 300, 150));
+ layer_tree_host_->UpdateLayers(queue_.get(),
+ std::numeric_limits<size_t>::max());
+ {
+ scoped_ptr<FakeTiledLayerImpl> layer_impl =
+ make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1));
+ EXPECT_EQ(3u, queue_->FullUploadSize());
+ EXPECT_EQ(3u, queue_->PartialUploadSize());
+ UpdateTextures();
+ EXPECT_EQ(6, layer->fake_layer_updater()->update_count());
+ EXPECT_FALSE(queue_->HasMoreUpdates());
+ layer->fake_layer_updater()->ClearUpdateCount();
+ LayerPushPropertiesTo(layer.get(), layer_impl.get());
+ }
+ layer_tree_host_->CommitComplete();
+
+ // Partial update of 6 tiles.
+ layer->InvalidateContentRect(gfx::Rect(50, 50, 200, 100));
+ {
+ scoped_ptr<FakeTiledLayerImpl> layer_impl =
+ make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1));
+ layer_tree_host_->UpdateLayers(queue_.get(),
+ std::numeric_limits<size_t>::max());
+ EXPECT_EQ(2u, queue_->FullUploadSize());
+ EXPECT_EQ(4u, queue_->PartialUploadSize());
+ UpdateTextures();
+ EXPECT_EQ(6, layer->fake_layer_updater()->update_count());
+ EXPECT_FALSE(queue_->HasMoreUpdates());
+ layer->fake_layer_updater()->ClearUpdateCount();
+ LayerPushPropertiesTo(layer.get(), layer_impl.get());
+ }
+ layer_tree_host_->CommitComplete();
+
+ // Checkerboard all tiles.
+ layer->InvalidateContentRect(gfx::Rect(0, 0, 300, 200));
+ {
+ scoped_ptr<FakeTiledLayerImpl> layer_impl =
+ make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1));
+ LayerPushPropertiesTo(layer.get(), layer_impl.get());
+ }
+ layer_tree_host_->CommitComplete();
+
+ // Partial update of 6 checkerboard tiles.
+ layer->InvalidateContentRect(gfx::Rect(50, 50, 200, 100));
+ {
+ scoped_ptr<FakeTiledLayerImpl> layer_impl =
+ make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1));
+ layer_tree_host_->UpdateLayers(queue_.get(),
+ std::numeric_limits<size_t>::max());
+ EXPECT_EQ(6u, queue_->FullUploadSize());
+ EXPECT_EQ(0u, queue_->PartialUploadSize());
+ UpdateTextures();
+ EXPECT_EQ(6, layer->fake_layer_updater()->update_count());
+ EXPECT_FALSE(queue_->HasMoreUpdates());
+ layer->fake_layer_updater()->ClearUpdateCount();
+ LayerPushPropertiesTo(layer.get(), layer_impl.get());
+ }
+ layer_tree_host_->CommitComplete();
+
+ // Partial update of 4 tiles.
+ layer->InvalidateContentRect(gfx::Rect(50, 50, 100, 100));
+ {
+ scoped_ptr<FakeTiledLayerImpl> layer_impl =
+ make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1));
+ layer_tree_host_->UpdateLayers(queue_.get(),
+ std::numeric_limits<size_t>::max());
+ EXPECT_EQ(0u, queue_->FullUploadSize());
+ EXPECT_EQ(4u, queue_->PartialUploadSize());
+ UpdateTextures();
+ EXPECT_EQ(4, layer->fake_layer_updater()->update_count());
+ EXPECT_FALSE(queue_->HasMoreUpdates());
+ layer->fake_layer_updater()->ClearUpdateCount();
+ LayerPushPropertiesTo(layer.get(), layer_impl.get());
+ }
+ layer_tree_host_->CommitComplete();
+
+ ResourceManagerClearAllMemory(layer_tree_host_->contents_texture_manager(),
+ resource_provider_.get());
+ layer_tree_host_->SetRootLayer(NULL);
+}
+
+TEST_F(TiledLayerTest, TilesPaintedWithoutOcclusion) {
+ scoped_refptr<FakeTiledLayer> layer =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+ RenderSurfaceLayerList render_surface_layer_list;
+
+ layer_tree_host_->root_layer()->AddChild(layer);
+
+ // The tile size is 100x100, so this invalidates and then paints two tiles.
+ layer->SetBounds(gfx::Size(100, 200));
+ CalcDrawProps(&render_surface_layer_list);
+
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), NULL);
+ EXPECT_EQ(2, layer->fake_layer_updater()->update_count());
+}
+
+TEST_F(TiledLayerTest, TilesPaintedWithOcclusion) {
+ scoped_refptr<FakeTiledLayer> layer =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+ RenderSurfaceLayerList render_surface_layer_list;
+ TestOcclusionTracker occluded;
+ occlusion_ = &occluded;
+
+ layer_tree_host_->root_layer()->AddChild(layer);
+
+ // The tile size is 100x100.
+
+ layer_tree_host_->SetViewportSize(gfx::Size(600, 600));
+ layer->SetBounds(gfx::Size(600, 600));
+ CalcDrawProps(&render_surface_layer_list);
+
+ occluded.SetOcclusion(gfx::Rect(200, 200, 300, 100));
+ layer->draw_properties().drawable_content_rect =
+ gfx::Rect(layer->content_bounds());
+ layer->draw_properties().visible_content_rect =
+ gfx::Rect(layer->content_bounds());
+ layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600));
+
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), &occluded);
+ EXPECT_EQ(36 - 3, layer->fake_layer_updater()->update_count());
+
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1);
+ EXPECT_NEAR(
+ occluded.overdraw_metrics()->pixels_uploaded_translucent(), 330000, 1);
+ EXPECT_EQ(3, occluded.overdraw_metrics()->tiles_culled_for_upload());
+
+ layer->fake_layer_updater()->ClearUpdateCount();
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+
+ occluded.SetOcclusion(gfx::Rect(250, 200, 300, 100));
+ layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600));
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), &occluded);
+ EXPECT_EQ(36 - 2, layer->fake_layer_updater()->update_count());
+
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1);
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(),
+ 330000 + 340000,
+ 1);
+ EXPECT_EQ(3 + 2, occluded.overdraw_metrics()->tiles_culled_for_upload());
+
+ layer->fake_layer_updater()->ClearUpdateCount();
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+
+ occluded.SetOcclusion(gfx::Rect(250, 250, 300, 100));
+ layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600));
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), &occluded);
+ EXPECT_EQ(36, layer->fake_layer_updater()->update_count());
+
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1);
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(),
+ 330000 + 340000 + 360000,
+ 1);
+ EXPECT_EQ(3 + 2, occluded.overdraw_metrics()->tiles_culled_for_upload());
+}
+
+TEST_F(TiledLayerTest, TilesPaintedWithOcclusionAndVisiblityConstraints) {
+ scoped_refptr<FakeTiledLayer> layer =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+ RenderSurfaceLayerList render_surface_layer_list;
+ TestOcclusionTracker occluded;
+ occlusion_ = &occluded;
+
+ layer_tree_host_->root_layer()->AddChild(layer);
+
+ // The tile size is 100x100.
+
+ layer_tree_host_->SetViewportSize(gfx::Size(600, 600));
+ layer->SetBounds(gfx::Size(600, 600));
+ CalcDrawProps(&render_surface_layer_list);
+
+ // The partially occluded tiles (by the 150 occlusion height) are visible
+ // beyond the occlusion, so not culled.
+ occluded.SetOcclusion(gfx::Rect(200, 200, 300, 150));
+ layer->draw_properties().drawable_content_rect = gfx::Rect(0, 0, 600, 360);
+ layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 600, 360);
+ layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600));
+
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), &occluded);
+ EXPECT_EQ(24 - 3, layer->fake_layer_updater()->update_count());
+
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1);
+ EXPECT_NEAR(
+ occluded.overdraw_metrics()->pixels_uploaded_translucent(), 210000, 1);
+ EXPECT_EQ(3, occluded.overdraw_metrics()->tiles_culled_for_upload());
+
+ layer->fake_layer_updater()->ClearUpdateCount();
+
+ // Now the visible region stops at the edge of the occlusion so the partly
+ // visible tiles become fully occluded.
+ occluded.SetOcclusion(gfx::Rect(200, 200, 300, 150));
+ layer->draw_properties().drawable_content_rect = gfx::Rect(0, 0, 600, 350);
+ layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 600, 350);
+ layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600));
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), &occluded);
+ EXPECT_EQ(24 - 6, layer->fake_layer_updater()->update_count());
+
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1);
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(),
+ 210000 + 180000,
+ 1);
+ EXPECT_EQ(3 + 6, occluded.overdraw_metrics()->tiles_culled_for_upload());
+
+ layer->fake_layer_updater()->ClearUpdateCount();
+
+ // Now the visible region is even smaller than the occlusion, it should have
+ // the same result.
+ occluded.SetOcclusion(gfx::Rect(200, 200, 300, 150));
+ layer->draw_properties().drawable_content_rect = gfx::Rect(0, 0, 600, 340);
+ layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 600, 340);
+ layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600));
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), &occluded);
+ EXPECT_EQ(24 - 6, layer->fake_layer_updater()->update_count());
+
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1);
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(),
+ 210000 + 180000 + 180000,
+ 1);
+ EXPECT_EQ(3 + 6 + 6, occluded.overdraw_metrics()->tiles_culled_for_upload());
+}
+
+TEST_F(TiledLayerTest, TilesNotPaintedWithoutInvalidation) {
+ scoped_refptr<FakeTiledLayer> layer =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+ RenderSurfaceLayerList render_surface_layer_list;
+ TestOcclusionTracker occluded;
+ occlusion_ = &occluded;
+
+ layer_tree_host_->root_layer()->AddChild(layer);
+
+ // The tile size is 100x100.
+
+ layer_tree_host_->SetViewportSize(gfx::Size(600, 600));
+ layer->SetBounds(gfx::Size(600, 600));
+ CalcDrawProps(&render_surface_layer_list);
+
+ occluded.SetOcclusion(gfx::Rect(200, 200, 300, 100));
+ layer->draw_properties().drawable_content_rect = gfx::Rect(0, 0, 600, 600);
+ layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 600, 600);
+ layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600));
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), &occluded);
+ EXPECT_EQ(36 - 3, layer->fake_layer_updater()->update_count());
+ { UpdateTextures(); }
+
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1);
+ EXPECT_NEAR(
+ occluded.overdraw_metrics()->pixels_uploaded_translucent(), 330000, 1);
+ EXPECT_EQ(3, occluded.overdraw_metrics()->tiles_culled_for_upload());
+
+ layer->fake_layer_updater()->ClearUpdateCount();
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+
+ // Repaint without marking it dirty. The 3 culled tiles will be pre-painted
+ // now.
+ layer->Update(queue_.get(), &occluded);
+ EXPECT_EQ(3, layer->fake_layer_updater()->update_count());
+
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1);
+ EXPECT_NEAR(
+ occluded.overdraw_metrics()->pixels_uploaded_translucent(), 330000, 1);
+ EXPECT_EQ(6, occluded.overdraw_metrics()->tiles_culled_for_upload());
+}
+
+TEST_F(TiledLayerTest, TilesPaintedWithOcclusionAndTransforms) {
+ scoped_refptr<FakeTiledLayer> layer =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+ RenderSurfaceLayerList render_surface_layer_list;
+ TestOcclusionTracker occluded;
+ occlusion_ = &occluded;
+
+ layer_tree_host_->root_layer()->AddChild(layer);
+
+ // The tile size is 100x100.
+
+ // This makes sure the painting works when the occluded region (in screen
+ // space) is transformed differently than the layer.
+ layer_tree_host_->SetViewportSize(gfx::Size(600, 600));
+ layer->SetBounds(gfx::Size(600, 600));
+ CalcDrawProps(&render_surface_layer_list);
+ gfx::Transform screen_transform;
+ screen_transform.Scale(0.5, 0.5);
+ layer->draw_properties().screen_space_transform = screen_transform;
+ layer->draw_properties().target_space_transform = screen_transform;
+
+ occluded.SetOcclusion(gfx::Rect(100, 100, 150, 50));
+ layer->draw_properties().drawable_content_rect =
+ gfx::Rect(layer->content_bounds());
+ layer->draw_properties().visible_content_rect =
+ gfx::Rect(layer->content_bounds());
+ layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600));
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), &occluded);
+ EXPECT_EQ(36 - 3, layer->fake_layer_updater()->update_count());
+
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1);
+ EXPECT_NEAR(
+ occluded.overdraw_metrics()->pixels_uploaded_translucent(), 330000, 1);
+ EXPECT_EQ(3, occluded.overdraw_metrics()->tiles_culled_for_upload());
+}
+
+TEST_F(TiledLayerTest, TilesPaintedWithOcclusionAndScaling) {
+ scoped_refptr<FakeTiledLayer> layer =
+ new FakeTiledLayer(resource_manager_.get());
+ RenderSurfaceLayerList render_surface_layer_list;
+ TestOcclusionTracker occluded;
+ occlusion_ = &occluded;
+
+ scoped_refptr<FakeTiledLayer> scale_layer =
+ new FakeTiledLayer(resource_manager_.get());
+ gfx::Transform scale_transform;
+ scale_transform.Scale(2.0, 2.0);
+ scale_layer->SetTransform(scale_transform);
+
+ layer_tree_host_->root_layer()->AddChild(scale_layer);
+
+ // The tile size is 100x100.
+
+ // This makes sure the painting works when the content space is scaled to
+ // a different layer space.
+ layer_tree_host_->SetViewportSize(gfx::Size(600, 600));
+ layer->SetAnchorPoint(gfx::PointF());
+ layer->SetBounds(gfx::Size(300, 300));
+ scale_layer->AddChild(layer);
+ CalcDrawProps(&render_surface_layer_list);
+ EXPECT_FLOAT_EQ(2.f, layer->contents_scale_x());
+ EXPECT_FLOAT_EQ(2.f, layer->contents_scale_y());
+ EXPECT_EQ(gfx::Size(600, 600).ToString(),
+ layer->content_bounds().ToString());
+
+ // No tiles are covered by the 300x50 occlusion.
+ occluded.SetOcclusion(gfx::Rect(200, 200, 300, 50));
+ layer->draw_properties().drawable_content_rect =
+ gfx::Rect(layer->bounds());
+ layer->draw_properties().visible_content_rect =
+ gfx::Rect(layer->content_bounds());
+ layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600));
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), &occluded);
+ int visible_tiles1 = 6 * 6;
+ EXPECT_EQ(visible_tiles1, layer->fake_layer_updater()->update_count());
+
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1);
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(),
+ visible_tiles1 * 100 * 100,
+ 1);
+ EXPECT_EQ(0, occluded.overdraw_metrics()->tiles_culled_for_upload());
+
+ layer->fake_layer_updater()->ClearUpdateCount();
+
+ // The occlusion of 300x100 will be cover 3 tiles as tiles are 100x100 still.
+ occluded.SetOcclusion(gfx::Rect(200, 200, 300, 100));
+ layer->draw_properties().drawable_content_rect =
+ gfx::Rect(layer->bounds());
+ layer->draw_properties().visible_content_rect =
+ gfx::Rect(layer->content_bounds());
+ layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600));
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), &occluded);
+ int visible_tiles2 = 6 * 6 - 3;
+ EXPECT_EQ(visible_tiles2, layer->fake_layer_updater()->update_count());
+
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1);
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(),
+ visible_tiles2 * 100 * 100 +
+ visible_tiles1 * 100 * 100,
+ 1);
+ EXPECT_EQ(3, occluded.overdraw_metrics()->tiles_culled_for_upload());
+
+ layer->fake_layer_updater()->ClearUpdateCount();
+
+ // This makes sure content scaling and transforms work together.
+ // When the tiles are scaled down by half, they are 50x50 each in the
+ // screen.
+ gfx::Transform screen_transform;
+ screen_transform.Scale(0.5, 0.5);
+ layer->draw_properties().screen_space_transform = screen_transform;
+ layer->draw_properties().target_space_transform = screen_transform;
+
+ // An occlusion of 150x100 will cover 3*2 = 6 tiles.
+ occluded.SetOcclusion(gfx::Rect(100, 100, 150, 100));
+
+ gfx::Rect layer_bounds_rect(layer->bounds());
+ layer->draw_properties().drawable_content_rect =
+ gfx::ScaleToEnclosingRect(layer_bounds_rect, 0.5f);
+ layer->draw_properties().visible_content_rect =
+ gfx::Rect(layer->content_bounds());
+ layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600));
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), &occluded);
+ int visible_tiles3 = 6 * 6 - 6;
+ EXPECT_EQ(visible_tiles3, layer->fake_layer_updater()->update_count());
+
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1);
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(),
+ visible_tiles3 * 100 * 100 +
+ visible_tiles2 * 100 * 100 +
+ visible_tiles1 * 100 * 100,
+ 1);
+ EXPECT_EQ(6 + 3, occluded.overdraw_metrics()->tiles_culled_for_upload());
+}
+
+TEST_F(TiledLayerTest, VisibleContentOpaqueRegion) {
+ scoped_refptr<FakeTiledLayer> layer =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+ RenderSurfaceLayerList render_surface_layer_list;
+ TestOcclusionTracker occluded;
+ occlusion_ = &occluded;
+ layer_tree_host_->SetViewportSize(gfx::Size(1000, 1000));
+
+ layer_tree_host_->root_layer()->AddChild(layer);
+
+ // The tile size is 100x100, so this invalidates and then paints two tiles in
+ // various ways.
+
+ gfx::Rect opaque_paint_rect;
+ Region opaque_contents;
+
+ gfx::Rect content_bounds = gfx::Rect(0, 0, 100, 200);
+ gfx::Rect visible_bounds = gfx::Rect(0, 0, 100, 150);
+
+ layer->SetBounds(content_bounds.size());
+ CalcDrawProps(&render_surface_layer_list);
+ layer->draw_properties().drawable_content_rect = visible_bounds;
+ layer->draw_properties().visible_content_rect = visible_bounds;
+
+ // If the layer doesn't paint opaque content, then the
+ // VisibleContentOpaqueRegion should be empty.
+ layer->fake_layer_updater()->SetOpaquePaintRect(gfx::Rect());
+ layer->InvalidateContentRect(content_bounds);
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), &occluded);
+ opaque_contents = layer->VisibleContentOpaqueRegion();
+ EXPECT_TRUE(opaque_contents.IsEmpty());
+
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_painted(), 20000, 1);
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1);
+ EXPECT_NEAR(
+ occluded.overdraw_metrics()->pixels_uploaded_translucent(), 20000, 1);
+ EXPECT_EQ(0, occluded.overdraw_metrics()->tiles_culled_for_upload());
+
+ // VisibleContentOpaqueRegion should match the visible part of what is painted
+ // opaque.
+ opaque_paint_rect = gfx::Rect(10, 10, 90, 190);
+ layer->fake_layer_updater()->SetOpaquePaintRect(opaque_paint_rect);
+ layer->InvalidateContentRect(content_bounds);
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), &occluded);
+ UpdateTextures();
+ opaque_contents = layer->VisibleContentOpaqueRegion();
+ EXPECT_EQ(gfx::IntersectRects(opaque_paint_rect, visible_bounds).ToString(),
+ opaque_contents.ToString());
+
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_painted(), 20000 * 2, 1);
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 17100, 1);
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(),
+ 20000 + 20000 - 17100,
+ 1);
+ EXPECT_EQ(0, occluded.overdraw_metrics()->tiles_culled_for_upload());
+
+ // If we paint again without invalidating, the same stuff should be opaque.
+ layer->fake_layer_updater()->SetOpaquePaintRect(gfx::Rect());
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), &occluded);
+ UpdateTextures();
+ opaque_contents = layer->VisibleContentOpaqueRegion();
+ EXPECT_EQ(gfx::IntersectRects(opaque_paint_rect, visible_bounds).ToString(),
+ opaque_contents.ToString());
+
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_painted(), 20000 * 2, 1);
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 17100, 1);
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(),
+ 20000 + 20000 - 17100,
+ 1);
+ EXPECT_EQ(0, occluded.overdraw_metrics()->tiles_culled_for_upload());
+
+ // If we repaint a non-opaque part of the tile, then it shouldn't lose its
+ // opaque-ness. And other tiles should not be affected.
+ layer->fake_layer_updater()->SetOpaquePaintRect(gfx::Rect());
+ layer->InvalidateContentRect(gfx::Rect(0, 0, 1, 1));
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), &occluded);
+ UpdateTextures();
+ opaque_contents = layer->VisibleContentOpaqueRegion();
+ EXPECT_EQ(gfx::IntersectRects(opaque_paint_rect, visible_bounds).ToString(),
+ opaque_contents.ToString());
+
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_painted(), 20000 * 2 + 1, 1);
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 17100, 1);
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(),
+ 20000 + 20000 - 17100 + 1,
+ 1);
+ EXPECT_EQ(0, occluded.overdraw_metrics()->tiles_culled_for_upload());
+
+ // If we repaint an opaque part of the tile, then it should lose its
+ // opaque-ness. But other tiles should still not be affected.
+ layer->fake_layer_updater()->SetOpaquePaintRect(gfx::Rect());
+ layer->InvalidateContentRect(gfx::Rect(10, 10, 1, 1));
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), &occluded);
+ UpdateTextures();
+ opaque_contents = layer->VisibleContentOpaqueRegion();
+ EXPECT_EQ(gfx::IntersectRects(gfx::Rect(10, 100, 90, 100),
+ visible_bounds).ToString(),
+ opaque_contents.ToString());
+
+ EXPECT_NEAR(
+ occluded.overdraw_metrics()->pixels_painted(), 20000 * 2 + 1 + 1, 1);
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 17100, 1);
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(),
+ 20000 + 20000 - 17100 + 1 + 1,
+ 1);
+ EXPECT_EQ(0, occluded.overdraw_metrics()->tiles_culled_for_upload());
+}
+
+TEST_F(TiledLayerTest, PixelsPaintedMetrics) {
+ scoped_refptr<FakeTiledLayer> layer =
+ make_scoped_refptr(new FakeTiledLayer(resource_manager_.get()));
+ RenderSurfaceLayerList render_surface_layer_list;
+ TestOcclusionTracker occluded;
+ occlusion_ = &occluded;
+ layer_tree_host_->SetViewportSize(gfx::Size(1000, 1000));
+
+ layer_tree_host_->root_layer()->AddChild(layer);
+
+ // The tile size is 100x100, so this invalidates and then paints two tiles in
+ // various ways.
+
+ gfx::Rect opaque_paint_rect;
+ Region opaque_contents;
+
+ gfx::Rect content_bounds = gfx::Rect(0, 0, 100, 300);
+ layer->SetBounds(content_bounds.size());
+ CalcDrawProps(&render_surface_layer_list);
+
+ // Invalidates and paints the whole layer.
+ layer->fake_layer_updater()->SetOpaquePaintRect(gfx::Rect());
+ layer->InvalidateContentRect(content_bounds);
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), &occluded);
+ UpdateTextures();
+ opaque_contents = layer->VisibleContentOpaqueRegion();
+ EXPECT_TRUE(opaque_contents.IsEmpty());
+
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_painted(), 30000, 1);
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1);
+ EXPECT_NEAR(
+ occluded.overdraw_metrics()->pixels_uploaded_translucent(), 30000, 1);
+ EXPECT_EQ(0, occluded.overdraw_metrics()->tiles_culled_for_upload());
+
+ // Invalidates an area on the top and bottom tile, which will cause us to
+ // paint the tile in the middle, even though it is not dirty and will not be
+ // uploaded.
+ layer->fake_layer_updater()->SetOpaquePaintRect(gfx::Rect());
+ layer->InvalidateContentRect(gfx::Rect(0, 0, 1, 1));
+ layer->InvalidateContentRect(gfx::Rect(50, 200, 10, 10));
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+ layer->Update(queue_.get(), &occluded);
+ UpdateTextures();
+ opaque_contents = layer->VisibleContentOpaqueRegion();
+ EXPECT_TRUE(opaque_contents.IsEmpty());
+
+ // The middle tile was painted even though not invalidated.
+ EXPECT_NEAR(
+ occluded.overdraw_metrics()->pixels_painted(), 30000 + 60 * 210, 1);
+ // The pixels uploaded will not include the non-invalidated tile in the
+ // middle.
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1);
+ EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(),
+ 30000 + 1 + 100,
+ 1);
+ EXPECT_EQ(0, occluded.overdraw_metrics()->tiles_culled_for_upload());
+}
+
+TEST_F(TiledLayerTest, DontAllocateContentsWhenTargetSurfaceCantBeAllocated) {
+ // Tile size is 100x100.
+ gfx::Rect root_rect(0, 0, 300, 200);
+ gfx::Rect child_rect(0, 0, 300, 100);
+ gfx::Rect child2_rect(0, 100, 300, 100);
+
+ scoped_refptr<FakeTiledLayer> root = make_scoped_refptr(
+ new FakeTiledLayer(layer_tree_host_->contents_texture_manager()));
+ scoped_refptr<Layer> surface = Layer::Create();
+ scoped_refptr<FakeTiledLayer> child = make_scoped_refptr(
+ new FakeTiledLayer(layer_tree_host_->contents_texture_manager()));
+ scoped_refptr<FakeTiledLayer> child2 = make_scoped_refptr(
+ new FakeTiledLayer(layer_tree_host_->contents_texture_manager()));
+
+ root->SetBounds(root_rect.size());
+ root->SetAnchorPoint(gfx::PointF());
+ root->draw_properties().drawable_content_rect = root_rect;
+ root->draw_properties().visible_content_rect = root_rect;
+ root->AddChild(surface);
+
+ surface->SetForceRenderSurface(true);
+ surface->SetAnchorPoint(gfx::PointF());
+ surface->SetOpacity(0.5);
+ surface->AddChild(child);
+ surface->AddChild(child2);
+
+ child->SetBounds(child_rect.size());
+ child->SetAnchorPoint(gfx::PointF());
+ child->SetPosition(child_rect.origin());
+ child->draw_properties().visible_content_rect = child_rect;
+ child->draw_properties().drawable_content_rect = root_rect;
+
+ child2->SetBounds(child2_rect.size());
+ child2->SetAnchorPoint(gfx::PointF());
+ child2->SetPosition(child2_rect.origin());
+ child2->draw_properties().visible_content_rect = child2_rect;
+ child2->draw_properties().drawable_content_rect = root_rect;
+
+ layer_tree_host_->SetRootLayer(root);
+ layer_tree_host_->SetViewportSize(root_rect.size());
+
+ // With a huge memory limit, all layers should update and push their textures.
+ root->InvalidateContentRect(root_rect);
+ child->InvalidateContentRect(child_rect);
+ child2->InvalidateContentRect(child2_rect);
+ layer_tree_host_->UpdateLayers(queue_.get(),
+ std::numeric_limits<size_t>::max());
+ {
+ UpdateTextures();
+ EXPECT_EQ(6, root->fake_layer_updater()->update_count());
+ EXPECT_EQ(3, child->fake_layer_updater()->update_count());
+ EXPECT_EQ(3, child2->fake_layer_updater()->update_count());
+ EXPECT_FALSE(queue_->HasMoreUpdates());
+
+ root->fake_layer_updater()->ClearUpdateCount();
+ child->fake_layer_updater()->ClearUpdateCount();
+ child2->fake_layer_updater()->ClearUpdateCount();
+
+ scoped_ptr<FakeTiledLayerImpl> root_impl = make_scoped_ptr(
+ new FakeTiledLayerImpl(host_impl_->active_tree(), root->id()));
+ scoped_ptr<FakeTiledLayerImpl> child_impl = make_scoped_ptr(
+ new FakeTiledLayerImpl(host_impl_->active_tree(), child->id()));
+ scoped_ptr<FakeTiledLayerImpl> child2_impl = make_scoped_ptr(
+ new FakeTiledLayerImpl(host_impl_->active_tree(), child2->id()));
+ LayerPushPropertiesTo(child2.get(), child2_impl.get());
+ LayerPushPropertiesTo(child.get(), child_impl.get());
+ LayerPushPropertiesTo(root.get(), root_impl.get());
+
+ for (unsigned i = 0; i < 3; ++i) {
+ for (unsigned j = 0; j < 2; ++j)
+ EXPECT_TRUE(root_impl->HasResourceIdForTileAt(i, j));
+ EXPECT_TRUE(child_impl->HasResourceIdForTileAt(i, 0));
+ EXPECT_TRUE(child2_impl->HasResourceIdForTileAt(i, 0));
+ }
+ }
+ layer_tree_host_->CommitComplete();
+
+ // With a memory limit that includes only the root layer (3x2 tiles) and half
+ // the surface that the child layers draw into, the child layers will not be
+ // allocated. If the surface isn't accounted for, then one of the children
+ // would fit within the memory limit.
+ root->InvalidateContentRect(root_rect);
+ child->InvalidateContentRect(child_rect);
+ child2->InvalidateContentRect(child2_rect);
+ layer_tree_host_->UpdateLayers(queue_.get(),
+ (3 * 2 + 3 * 1) * (100 * 100) * 4);
+ {
+ UpdateTextures();
+ EXPECT_EQ(6, root->fake_layer_updater()->update_count());
+ EXPECT_EQ(0, child->fake_layer_updater()->update_count());
+ EXPECT_EQ(0, child2->fake_layer_updater()->update_count());
+ EXPECT_FALSE(queue_->HasMoreUpdates());
+
+ root->fake_layer_updater()->ClearUpdateCount();
+ child->fake_layer_updater()->ClearUpdateCount();
+ child2->fake_layer_updater()->ClearUpdateCount();
+
+ scoped_ptr<FakeTiledLayerImpl> root_impl = make_scoped_ptr(
+ new FakeTiledLayerImpl(host_impl_->active_tree(), root->id()));
+ scoped_ptr<FakeTiledLayerImpl> child_impl = make_scoped_ptr(
+ new FakeTiledLayerImpl(host_impl_->active_tree(), child->id()));
+ scoped_ptr<FakeTiledLayerImpl> child2_impl = make_scoped_ptr(
+ new FakeTiledLayerImpl(host_impl_->active_tree(), child2->id()));
+ LayerPushPropertiesTo(child2.get(), child2_impl.get());
+ LayerPushPropertiesTo(child.get(), child_impl.get());
+ LayerPushPropertiesTo(root.get(), root_impl.get());
+
+ for (unsigned i = 0; i < 3; ++i) {
+ for (unsigned j = 0; j < 2; ++j)
+ EXPECT_TRUE(root_impl->HasResourceIdForTileAt(i, j));
+ EXPECT_FALSE(child_impl->HasResourceIdForTileAt(i, 0));
+ EXPECT_FALSE(child2_impl->HasResourceIdForTileAt(i, 0));
+ }
+ }
+ layer_tree_host_->CommitComplete();
+
+ // With a memory limit that includes only half the root layer, no contents
+ // will be allocated. If render surface memory wasn't accounted for, there is
+ // enough space for one of the children layers, but they draw into a surface
+ // that can't be allocated.
+ root->InvalidateContentRect(root_rect);
+ child->InvalidateContentRect(child_rect);
+ child2->InvalidateContentRect(child2_rect);
+ layer_tree_host_->UpdateLayers(queue_.get(), (3 * 1) * (100 * 100) * 4);
+ {
+ UpdateTextures();
+ EXPECT_EQ(0, root->fake_layer_updater()->update_count());
+ EXPECT_EQ(0, child->fake_layer_updater()->update_count());
+ EXPECT_EQ(0, child2->fake_layer_updater()->update_count());
+ EXPECT_FALSE(queue_->HasMoreUpdates());
+
+ root->fake_layer_updater()->ClearUpdateCount();
+ child->fake_layer_updater()->ClearUpdateCount();
+ child2->fake_layer_updater()->ClearUpdateCount();
+
+ scoped_ptr<FakeTiledLayerImpl> root_impl = make_scoped_ptr(
+ new FakeTiledLayerImpl(host_impl_->active_tree(), root->id()));
+ scoped_ptr<FakeTiledLayerImpl> child_impl = make_scoped_ptr(
+ new FakeTiledLayerImpl(host_impl_->active_tree(), child->id()));
+ scoped_ptr<FakeTiledLayerImpl> child2_impl = make_scoped_ptr(
+ new FakeTiledLayerImpl(host_impl_->active_tree(), child2->id()));
+ LayerPushPropertiesTo(child2.get(), child2_impl.get());
+ LayerPushPropertiesTo(child.get(), child_impl.get());
+ LayerPushPropertiesTo(root.get(), root_impl.get());
+
+ for (unsigned i = 0; i < 3; ++i) {
+ for (unsigned j = 0; j < 2; ++j)
+ EXPECT_FALSE(root_impl->HasResourceIdForTileAt(i, j));
+ EXPECT_FALSE(child_impl->HasResourceIdForTileAt(i, 0));
+ EXPECT_FALSE(child2_impl->HasResourceIdForTileAt(i, 0));
+ }
+ }
+ layer_tree_host_->CommitComplete();
+
+ ResourceManagerClearAllMemory(layer_tree_host_->contents_texture_manager(),
+ resource_provider_.get());
+ layer_tree_host_->SetRootLayer(NULL);
+}
+
+class TrackingLayerPainter : public LayerPainter {
+ public:
+ static scoped_ptr<TrackingLayerPainter> Create() {
+ return make_scoped_ptr(new TrackingLayerPainter());
+ }
+
+ virtual void Paint(SkCanvas* canvas,
+ gfx::Rect content_rect,
+ gfx::RectF* opaque) OVERRIDE {
+ painted_rect_ = content_rect;
+ }
+
+ gfx::Rect PaintedRect() const { return painted_rect_; }
+ void ResetPaintedRect() { painted_rect_ = gfx::Rect(); }
+
+ private:
+ gfx::Rect painted_rect_;
+};
+
+class UpdateTrackingTiledLayer : public FakeTiledLayer {
+ public:
+ explicit UpdateTrackingTiledLayer(PrioritizedResourceManager* manager)
+ : FakeTiledLayer(manager) {
+ scoped_ptr<TrackingLayerPainter> painter(TrackingLayerPainter::Create());
+ tracking_layer_painter_ = painter.get();
+ layer_updater_ =
+ BitmapContentLayerUpdater::Create(painter.PassAs<LayerPainter>(),
+ &stats_instrumentation_,
+ 0);
+ }
+
+ TrackingLayerPainter* tracking_layer_painter() const {
+ return tracking_layer_painter_;
+ }
+
+ private:
+ virtual LayerUpdater* Updater() const OVERRIDE {
+ return layer_updater_.get();
+ }
+ virtual ~UpdateTrackingTiledLayer() {}
+
+ TrackingLayerPainter* tracking_layer_painter_;
+ scoped_refptr<BitmapContentLayerUpdater> layer_updater_;
+ FakeRenderingStatsInstrumentation stats_instrumentation_;
+};
+
+TEST_F(TiledLayerTest, NonIntegerContentsScaleIsNotDistortedDuringPaint) {
+ scoped_refptr<UpdateTrackingTiledLayer> layer =
+ make_scoped_refptr(new UpdateTrackingTiledLayer(resource_manager_.get()));
+
+ layer_tree_host_->root_layer()->AddChild(layer);
+
+ gfx::Rect layer_rect(0, 0, 30, 31);
+ layer->SetPosition(layer_rect.origin());
+ layer->SetBounds(layer_rect.size());
+ layer->UpdateContentsScale(1.5f);
+
+ gfx::Rect content_rect(0, 0, 45, 47);
+ EXPECT_EQ(content_rect.size(), layer->content_bounds());
+ layer->draw_properties().visible_content_rect = content_rect;
+ layer->draw_properties().drawable_content_rect = content_rect;
+
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+
+ // Update the whole tile.
+ layer->Update(queue_.get(), NULL);
+ layer->tracking_layer_painter()->ResetPaintedRect();
+
+ EXPECT_RECT_EQ(gfx::Rect(), layer->tracking_layer_painter()->PaintedRect());
+ UpdateTextures();
+
+ // Invalidate the entire layer in content space. When painting, the rect given
+ // to webkit should match the layer's bounds.
+ layer->InvalidateContentRect(content_rect);
+ layer->Update(queue_.get(), NULL);
+
+ EXPECT_RECT_EQ(layer_rect, layer->tracking_layer_painter()->PaintedRect());
+}
+
+TEST_F(TiledLayerTest,
+ NonIntegerContentsScaleIsNotDistortedDuringInvalidation) {
+ scoped_refptr<UpdateTrackingTiledLayer> layer =
+ make_scoped_refptr(new UpdateTrackingTiledLayer(resource_manager_.get()));
+
+ layer_tree_host_->root_layer()->AddChild(layer);
+
+ gfx::Rect layer_rect(0, 0, 30, 31);
+ layer->SetPosition(layer_rect.origin());
+ layer->SetBounds(layer_rect.size());
+ layer->UpdateContentsScale(1.3f);
+
+ gfx::Rect content_rect(layer->content_bounds());
+ layer->draw_properties().visible_content_rect = content_rect;
+ layer->draw_properties().drawable_content_rect = content_rect;
+
+ layer->SetTexturePriorities(priority_calculator_);
+ resource_manager_->PrioritizeTextures();
+ layer->SavePaintProperties();
+
+ // Update the whole tile.
+ layer->Update(queue_.get(), NULL);
+ layer->tracking_layer_painter()->ResetPaintedRect();
+
+ EXPECT_RECT_EQ(gfx::Rect(), layer->tracking_layer_painter()->PaintedRect());
+ UpdateTextures();
+
+ // Invalidate the entire layer in layer space. When painting, the rect given
+ // to webkit should match the layer's bounds.
+ layer->SetNeedsDisplayRect(layer_rect);
+ layer->Update(queue_.get(), NULL);
+
+ EXPECT_RECT_EQ(layer_rect, layer->tracking_layer_painter()->PaintedRect());
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/layers/video_frame_provider.h b/chromium/cc/layers/video_frame_provider.h
new file mode 100644
index 00000000000..784d951fe52
--- /dev/null
+++ b/chromium/cc/layers/video_frame_provider.h
@@ -0,0 +1,63 @@
+// Copyright (c) 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.
+
+#ifndef CC_LAYERS_VIDEO_FRAME_PROVIDER_H_
+#define CC_LAYERS_VIDEO_FRAME_PROVIDER_H_
+
+#include "base/memory/ref_counted.h"
+
+namespace media {
+class VideoFrame;
+}
+
+namespace cc {
+
+// Threading notes: This class may be used in a multi threaded manner.
+// Specifically, the implementation may call GetCurrentFrame() or
+// PutCurrentFrame() from the compositor thread. If so, the caller is
+// responsible for making sure Client::DidReceiveFrame() and
+// Client::DidUpdateMatrix() are only called from this same thread.
+class VideoFrameProvider {
+ public:
+ virtual ~VideoFrameProvider() {}
+
+ class Client {
+ public:
+ // Provider will call this method to tell the client to stop using it.
+ // StopUsingProvider() may be called from any thread. The client should
+ // block until it has PutCurrentFrame() any outstanding frames.
+ virtual void StopUsingProvider() = 0;
+
+ // Notifies the provider's client that a call to GetCurrentFrame() will
+ // return new data.
+ virtual void DidReceiveFrame() = 0;
+
+ // Notifies the provider's client of a new UV transform matrix to be used.
+ virtual void DidUpdateMatrix(const float* matrix) = 0;
+
+ protected:
+ virtual ~Client() {}
+ };
+
+ // May be called from any thread, but there must be some external guarantee
+ // that the provider is not destroyed before this call returns.
+ virtual void SetVideoFrameProviderClient(Client* client) = 0;
+
+ // This function places a lock on the current frame and returns a pointer to
+ // it. Calls to this method should always be followed with a call to
+ // PutCurrentFrame().
+ // Only the current provider client should call this function.
+ virtual scoped_refptr<media::VideoFrame> GetCurrentFrame() = 0;
+
+ // This function releases the lock on the video frame. It should always be
+ // called after GetCurrentFrame(). Frames passed into this method
+ // should no longer be referenced after the call is made. Only the current
+ // provider client should call this function.
+ virtual void PutCurrentFrame(
+ const scoped_refptr<media::VideoFrame>& frame) = 0;
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_VIDEO_FRAME_PROVIDER_H_
diff --git a/chromium/cc/layers/video_frame_provider_client_impl.cc b/chromium/cc/layers/video_frame_provider_client_impl.cc
new file mode 100644
index 00000000000..732bbbb1d0a
--- /dev/null
+++ b/chromium/cc/layers/video_frame_provider_client_impl.cc
@@ -0,0 +1,90 @@
+// Copyright 2013 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 "cc/layers/video_frame_provider_client_impl.h"
+
+#include "cc/base/math_util.h"
+#include "cc/layers/video_layer_impl.h"
+#include "media/base/video_frame.h"
+
+namespace cc {
+
+// static
+scoped_refptr<VideoFrameProviderClientImpl>
+ VideoFrameProviderClientImpl::Create(
+ VideoFrameProvider* provider) {
+ return make_scoped_refptr(
+ new VideoFrameProviderClientImpl(provider));
+}
+
+VideoFrameProviderClientImpl::~VideoFrameProviderClientImpl() {}
+
+VideoFrameProviderClientImpl::VideoFrameProviderClientImpl(
+ VideoFrameProvider* provider)
+ : provider_(provider) {
+ // This only happens during a commit on the compositor thread while the main
+ // thread is blocked. That makes this a thread-safe call to set the video
+ // frame provider client that does not require a lock. The same is true of
+ // the call to Stop().
+ provider_->SetVideoFrameProviderClient(this);
+
+ // This matrix is the default transformation for stream textures, and flips
+ // on the Y axis.
+ stream_texture_matrix_ = gfx::Transform(
+ 1.0, 0.0, 0.0, 0.0,
+ 0.0, -1.0, 0.0, 1.0,
+ 0.0, 0.0, 1.0, 0.0,
+ 0.0, 0.0, 0.0, 1.0);
+}
+
+void VideoFrameProviderClientImpl::Stop() {
+ if (!provider_)
+ return;
+ provider_->SetVideoFrameProviderClient(NULL);
+ provider_ = NULL;
+}
+
+scoped_refptr<media::VideoFrame>
+VideoFrameProviderClientImpl::AcquireLockAndCurrentFrame() {
+ provider_lock_.Acquire(); // Balanced by call to ReleaseLock().
+ if (!provider_)
+ return NULL;
+
+ return provider_->GetCurrentFrame();
+}
+
+void VideoFrameProviderClientImpl::PutCurrentFrame(
+ const scoped_refptr<media::VideoFrame>& frame) {
+ provider_lock_.AssertAcquired();
+ provider_->PutCurrentFrame(frame);
+}
+
+void VideoFrameProviderClientImpl::ReleaseLock() {
+ provider_lock_.AssertAcquired();
+ provider_lock_.Release();
+}
+
+void VideoFrameProviderClientImpl::StopUsingProvider() {
+ // Block the provider from shutting down until this client is done
+ // using the frame.
+ base::AutoLock locker(provider_lock_);
+ provider_ = NULL;
+}
+
+void VideoFrameProviderClientImpl::DidReceiveFrame() {
+ if (active_video_layer_)
+ active_video_layer_->SetNeedsRedraw();
+}
+
+void VideoFrameProviderClientImpl::DidUpdateMatrix(const float* matrix) {
+ stream_texture_matrix_ = gfx::Transform(
+ matrix[0], matrix[4], matrix[8], matrix[12],
+ matrix[1], matrix[5], matrix[9], matrix[13],
+ matrix[2], matrix[6], matrix[10], matrix[14],
+ matrix[3], matrix[7], matrix[11], matrix[15]);
+ if (active_video_layer_)
+ active_video_layer_->SetNeedsRedraw();
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/video_frame_provider_client_impl.h b/chromium/cc/layers/video_frame_provider_client_impl.h
new file mode 100644
index 00000000000..2bcb4462b73
--- /dev/null
+++ b/chromium/cc/layers/video_frame_provider_client_impl.h
@@ -0,0 +1,63 @@
+// Copyright 2013 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.
+
+#ifndef CC_LAYERS_VIDEO_FRAME_PROVIDER_CLIENT_IMPL_H_
+#define CC_LAYERS_VIDEO_FRAME_PROVIDER_CLIENT_IMPL_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "cc/layers/video_frame_provider.h"
+#include "ui/gfx/transform.h"
+
+namespace media { class VideoFrame; }
+
+namespace cc {
+class VideoLayerImpl;
+
+class VideoFrameProviderClientImpl
+ : public VideoFrameProvider::Client,
+ public base::RefCounted<VideoFrameProviderClientImpl> {
+ public:
+ static scoped_refptr<VideoFrameProviderClientImpl> Create(
+ VideoFrameProvider* provider);
+
+ void set_active_video_layer(VideoLayerImpl* video_layer) {
+ active_video_layer_ = video_layer;
+ }
+
+ void Stop();
+ bool Stopped() const { return !provider_; }
+
+ scoped_refptr<media::VideoFrame> AcquireLockAndCurrentFrame();
+ void PutCurrentFrame(const scoped_refptr<media::VideoFrame>& frame);
+ void ReleaseLock();
+ const gfx::Transform& stream_texture_matrix() const {
+ return stream_texture_matrix_;
+ }
+
+ // VideoFrameProvider::Client implementation. These methods are all callable
+ // on any thread.
+ virtual void StopUsingProvider() OVERRIDE;
+ virtual void DidReceiveFrame() OVERRIDE;
+ virtual void DidUpdateMatrix(const float* matrix) OVERRIDE;
+
+ private:
+ explicit VideoFrameProviderClientImpl(VideoFrameProvider* provider);
+ friend class base::RefCounted<VideoFrameProviderClientImpl>;
+ virtual ~VideoFrameProviderClientImpl();
+
+ VideoLayerImpl* active_video_layer_;
+
+ // Guards the destruction of provider_ and the frame that it provides
+ base::Lock provider_lock_;
+ VideoFrameProvider* provider_;
+
+ gfx::Transform stream_texture_matrix_;
+
+ DISALLOW_COPY_AND_ASSIGN(VideoFrameProviderClientImpl);
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_VIDEO_FRAME_PROVIDER_CLIENT_IMPL_H_
diff --git a/chromium/cc/layers/video_layer.cc b/chromium/cc/layers/video_layer.cc
new file mode 100644
index 00000000000..8d728e0a8e0
--- /dev/null
+++ b/chromium/cc/layers/video_layer.cc
@@ -0,0 +1,39 @@
+// Copyright 2010 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 "cc/layers/video_layer.h"
+
+#include "cc/layers/video_layer_impl.h"
+
+namespace cc {
+
+scoped_refptr<VideoLayer> VideoLayer::Create(VideoFrameProvider* provider) {
+ return make_scoped_refptr(new VideoLayer(provider));
+}
+
+VideoLayer::VideoLayer(VideoFrameProvider* provider) : provider_(provider) {
+ DCHECK(provider_);
+}
+
+VideoLayer::~VideoLayer() {}
+
+scoped_ptr<LayerImpl> VideoLayer::CreateLayerImpl(LayerTreeImpl* tree_impl) {
+ return VideoLayerImpl::Create(tree_impl, id(), provider_).PassAs<LayerImpl>();
+}
+
+bool VideoLayer::Update(ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion) {
+ bool updated = Layer::Update(queue, occlusion);
+
+ // Video layer doesn't update any resources from the main thread side,
+ // but repaint rects need to be sent to the VideoLayerImpl via commit.
+ //
+ // This is the inefficient legacy redraw path for videos. It's better to
+ // communicate this directly to the VideoLayerImpl.
+ updated |= !update_rect_.IsEmpty();
+
+ return updated;
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/video_layer.h b/chromium/cc/layers/video_layer.h
new file mode 100644
index 00000000000..4d14f80dd4c
--- /dev/null
+++ b/chromium/cc/layers/video_layer.h
@@ -0,0 +1,42 @@
+// Copyright 2010 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.
+
+#ifndef CC_LAYERS_VIDEO_LAYER_H_
+#define CC_LAYERS_VIDEO_LAYER_H_
+
+#include "base/callback.h"
+#include "cc/base/cc_export.h"
+#include "cc/layers/layer.h"
+
+namespace media { class VideoFrame; }
+
+namespace cc {
+
+class VideoFrameProvider;
+class VideoLayerImpl;
+
+// A Layer that contains a Video element.
+class CC_EXPORT VideoLayer : public Layer {
+ public:
+ static scoped_refptr<VideoLayer> Create(VideoFrameProvider* provider);
+
+ virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl)
+ OVERRIDE;
+
+ virtual bool Update(ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion) OVERRIDE;
+ private:
+ explicit VideoLayer(VideoFrameProvider* provider);
+ virtual ~VideoLayer();
+
+ // This pointer is only for passing to VideoLayerImpl's constructor. It should
+ // never be dereferenced by this class.
+ VideoFrameProvider* provider_;
+
+ DISALLOW_COPY_AND_ASSIGN(VideoLayer);
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_VIDEO_LAYER_H_
diff --git a/chromium/cc/layers/video_layer_impl.cc b/chromium/cc/layers/video_layer_impl.cc
new file mode 100644
index 00000000000..4b5671ff90e
--- /dev/null
+++ b/chromium/cc/layers/video_layer_impl.cc
@@ -0,0 +1,311 @@
+// Copyright 2011 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 "cc/layers/video_layer_impl.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "cc/layers/quad_sink.h"
+#include "cc/layers/video_frame_provider_client_impl.h"
+#include "cc/quads/io_surface_draw_quad.h"
+#include "cc/quads/stream_video_draw_quad.h"
+#include "cc/quads/texture_draw_quad.h"
+#include "cc/quads/yuv_video_draw_quad.h"
+#include "cc/resources/resource_provider.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "cc/trees/proxy.h"
+#include "media/base/video_frame.h"
+
+#if defined(GOOGLE_TV)
+#include "cc/quads/solid_color_draw_quad.h"
+#endif
+
+namespace cc {
+
+// static
+scoped_ptr<VideoLayerImpl> VideoLayerImpl::Create(
+ LayerTreeImpl* tree_impl,
+ int id,
+ VideoFrameProvider* provider) {
+ scoped_ptr<VideoLayerImpl> layer(new VideoLayerImpl(tree_impl, id));
+ layer->SetProviderClientImpl(VideoFrameProviderClientImpl::Create(provider));
+ DCHECK(tree_impl->proxy()->IsImplThread());
+ DCHECK(tree_impl->proxy()->IsMainThreadBlocked());
+ return layer.Pass();
+}
+
+VideoLayerImpl::VideoLayerImpl(LayerTreeImpl* tree_impl, int id)
+ : LayerImpl(tree_impl, id),
+ frame_(NULL) {}
+
+VideoLayerImpl::~VideoLayerImpl() {
+ if (!provider_client_impl_->Stopped()) {
+ // In impl side painting, we may have a pending and active layer
+ // associated with the video provider at the same time. Both have a ref
+ // on the VideoFrameProviderClientImpl, but we stop when the first
+ // LayerImpl (the one on the pending tree) is destroyed since we know
+ // the main thread is blocked for this commit.
+ DCHECK(layer_tree_impl()->proxy()->IsImplThread());
+ DCHECK(layer_tree_impl()->proxy()->IsMainThreadBlocked());
+ provider_client_impl_->Stop();
+ }
+}
+
+scoped_ptr<LayerImpl> VideoLayerImpl::CreateLayerImpl(
+ LayerTreeImpl* tree_impl) {
+ return scoped_ptr<LayerImpl>(new VideoLayerImpl(tree_impl, id()));
+}
+
+void VideoLayerImpl::PushPropertiesTo(LayerImpl* layer) {
+ LayerImpl::PushPropertiesTo(layer);
+
+ VideoLayerImpl* other = static_cast<VideoLayerImpl*>(layer);
+ other->SetProviderClientImpl(provider_client_impl_);
+}
+
+void VideoLayerImpl::DidBecomeActive() {
+ provider_client_impl_->set_active_video_layer(this);
+}
+
+bool VideoLayerImpl::WillDraw(DrawMode draw_mode,
+ ResourceProvider* resource_provider) {
+ if (draw_mode == DRAW_MODE_RESOURCELESS_SOFTWARE)
+ return false;
+
+ // Explicitly acquire and release the provider mutex so it can be held from
+ // WillDraw to DidDraw. Since the compositor thread is in the middle of
+ // drawing, the layer will not be destroyed before DidDraw is called.
+ // Therefore, the only thing that will prevent this lock from being released
+ // is the GPU process locking it. As the GPU process can't cause the
+ // destruction of the provider (calling StopUsingProvider), holding this
+ // lock should not cause a deadlock.
+ frame_ = provider_client_impl_->AcquireLockAndCurrentFrame();
+
+ if (!frame_.get()) {
+ // Drop any resources used by the updater if there is no frame to display.
+ updater_.reset();
+
+ provider_client_impl_->ReleaseLock();
+ return false;
+ }
+
+ if (!LayerImpl::WillDraw(draw_mode, resource_provider))
+ return false;
+
+ if (!updater_)
+ updater_.reset(new VideoResourceUpdater(resource_provider));
+
+ VideoFrameExternalResources external_resources =
+ updater_->CreateExternalResourcesFromVideoFrame(frame_);
+ frame_resource_type_ = external_resources.type;
+
+ if (external_resources.type ==
+ VideoFrameExternalResources::SOFTWARE_RESOURCE) {
+ software_resources_ = external_resources.software_resources;
+ software_release_callback_ =
+ external_resources.software_release_callback;
+ return true;
+ }
+
+ for (size_t i = 0; i < external_resources.mailboxes.size(); ++i) {
+ frame_resources_.push_back(
+ resource_provider->CreateResourceFromTextureMailbox(
+ external_resources.mailboxes[i]));
+ }
+
+ return true;
+}
+
+void VideoLayerImpl::AppendQuads(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data) {
+ DCHECK(frame_.get());
+
+ SharedQuadState* shared_quad_state =
+ quad_sink->UseSharedQuadState(CreateSharedQuadState());
+ AppendDebugBorderQuad(quad_sink, shared_quad_state, append_quads_data);
+
+ gfx::Rect quad_rect(content_bounds());
+ gfx::Rect opaque_rect(contents_opaque() ? quad_rect : gfx::Rect());
+ gfx::Rect visible_rect = frame_->visible_rect();
+ gfx::Size coded_size = frame_->coded_size();
+
+ // Pixels for macroblocked formats.
+ float tex_width_scale =
+ static_cast<float>(visible_rect.width()) / coded_size.width();
+ float tex_height_scale =
+ static_cast<float>(visible_rect.height()) / coded_size.height();
+
+ switch (frame_resource_type_) {
+ // TODO(danakj): Remove this, hide it in the hardware path.
+ case VideoFrameExternalResources::SOFTWARE_RESOURCE: {
+ DCHECK_EQ(frame_resources_.size(), 0u);
+ DCHECK_EQ(software_resources_.size(), 1u);
+ if (software_resources_.size() < 1u)
+ break;
+ bool premultiplied_alpha = true;
+ gfx::PointF uv_top_left(0.f, 0.f);
+ gfx::PointF uv_bottom_right(tex_width_scale, tex_height_scale);
+ float opacity[] = {1.0f, 1.0f, 1.0f, 1.0f};
+ bool flipped = false;
+ scoped_ptr<TextureDrawQuad> texture_quad = TextureDrawQuad::Create();
+ texture_quad->SetNew(shared_quad_state,
+ quad_rect,
+ opaque_rect,
+ software_resources_[0],
+ premultiplied_alpha,
+ uv_top_left,
+ uv_bottom_right,
+ SK_ColorTRANSPARENT,
+ opacity,
+ flipped);
+ quad_sink->Append(texture_quad.PassAs<DrawQuad>(), append_quads_data);
+ break;
+ }
+ case VideoFrameExternalResources::YUV_RESOURCE: {
+ DCHECK_GE(frame_resources_.size(), 3u);
+ if (frame_resources_.size() < 3u)
+ break;
+ gfx::SizeF tex_scale(tex_width_scale, tex_height_scale);
+ scoped_ptr<YUVVideoDrawQuad> yuv_video_quad = YUVVideoDrawQuad::Create();
+ yuv_video_quad->SetNew(shared_quad_state,
+ quad_rect,
+ opaque_rect,
+ tex_scale,
+ frame_resources_[0],
+ frame_resources_[1],
+ frame_resources_[2],
+ frame_resources_.size() > 3 ?
+ frame_resources_[3] : 0);
+ quad_sink->Append(yuv_video_quad.PassAs<DrawQuad>(), append_quads_data);
+ break;
+ }
+ case VideoFrameExternalResources::RGB_RESOURCE: {
+ DCHECK_EQ(frame_resources_.size(), 1u);
+ if (frame_resources_.size() < 1u)
+ break;
+ bool premultiplied_alpha = true;
+ gfx::PointF uv_top_left(0.f, 0.f);
+ gfx::PointF uv_bottom_right(tex_width_scale, tex_height_scale);
+ float opacity[] = {1.0f, 1.0f, 1.0f, 1.0f};
+ bool flipped = false;
+ scoped_ptr<TextureDrawQuad> texture_quad = TextureDrawQuad::Create();
+ texture_quad->SetNew(shared_quad_state,
+ quad_rect,
+ opaque_rect,
+ frame_resources_[0],
+ premultiplied_alpha,
+ uv_top_left,
+ uv_bottom_right,
+ SK_ColorTRANSPARENT,
+ opacity,
+ flipped);
+ quad_sink->Append(texture_quad.PassAs<DrawQuad>(), append_quads_data);
+ break;
+ }
+ case VideoFrameExternalResources::STREAM_TEXTURE_RESOURCE: {
+ DCHECK_EQ(frame_resources_.size(), 1u);
+ if (frame_resources_.size() < 1u)
+ break;
+ gfx::Transform transform(
+ provider_client_impl_->stream_texture_matrix());
+ transform.Scale(tex_width_scale, tex_height_scale);
+ scoped_ptr<StreamVideoDrawQuad> stream_video_quad =
+ StreamVideoDrawQuad::Create();
+ stream_video_quad->SetNew(shared_quad_state,
+ quad_rect,
+ opaque_rect,
+ frame_resources_[0],
+ transform);
+ quad_sink->Append(stream_video_quad.PassAs<DrawQuad>(),
+ append_quads_data);
+ break;
+ }
+ case VideoFrameExternalResources::IO_SURFACE: {
+ DCHECK_EQ(frame_resources_.size(), 1u);
+ if (frame_resources_.size() < 1u)
+ break;
+ gfx::Size visible_size(visible_rect.width(), visible_rect.height());
+ scoped_ptr<IOSurfaceDrawQuad> io_surface_quad =
+ IOSurfaceDrawQuad::Create();
+ io_surface_quad->SetNew(shared_quad_state,
+ quad_rect,
+ opaque_rect,
+ visible_size,
+ frame_resources_[0],
+ IOSurfaceDrawQuad::UNFLIPPED);
+ quad_sink->Append(io_surface_quad.PassAs<DrawQuad>(),
+ append_quads_data);
+ break;
+ }
+#if defined(GOOGLE_TV)
+ // This block and other blocks wrapped around #if defined(GOOGLE_TV) is not
+ // maintained by the general compositor team. Please contact the following
+ // people instead:
+ //
+ // wonsik@chromium.org
+ // ycheo@chromium.org
+ case VideoFrameExternalResources::HOLE: {
+ DCHECK_EQ(frame_resources_.size(), 0u);
+ scoped_ptr<SolidColorDrawQuad> solid_color_draw_quad =
+ SolidColorDrawQuad::Create();
+
+ // Create a solid color quad with transparent black and force no
+ // blending / no anti-aliasing.
+ solid_color_draw_quad->SetAll(
+ shared_quad_state, quad_rect, quad_rect, quad_rect, false,
+ SK_ColorTRANSPARENT, true);
+ quad_sink->Append(solid_color_draw_quad.PassAs<DrawQuad>(),
+ append_quads_data);
+ break;
+ }
+#endif
+ case VideoFrameExternalResources::NONE:
+ NOTIMPLEMENTED();
+ break;
+ }
+}
+
+void VideoLayerImpl::DidDraw(ResourceProvider* resource_provider) {
+ LayerImpl::DidDraw(resource_provider);
+
+ DCHECK(frame_.get());
+
+ if (frame_resource_type_ ==
+ VideoFrameExternalResources::SOFTWARE_RESOURCE) {
+ for (size_t i = 0; i < software_resources_.size(); ++i)
+ software_release_callback_.Run(0, false);
+
+ software_resources_.clear();
+ software_release_callback_.Reset();
+ } else {
+ for (size_t i = 0; i < frame_resources_.size(); ++i)
+ resource_provider->DeleteResource(frame_resources_[i]);
+ frame_resources_.clear();
+ }
+
+ provider_client_impl_->PutCurrentFrame(frame_);
+ frame_ = NULL;
+
+ provider_client_impl_->ReleaseLock();
+}
+
+void VideoLayerImpl::DidLoseOutputSurface() {
+ updater_.reset();
+}
+
+void VideoLayerImpl::SetNeedsRedraw() {
+ set_update_rect(gfx::UnionRects(update_rect(), gfx::RectF(bounds())));
+ layer_tree_impl()->SetNeedsRedraw();
+}
+
+void VideoLayerImpl::SetProviderClientImpl(
+ scoped_refptr<VideoFrameProviderClientImpl> provider_client_impl) {
+ provider_client_impl_ = provider_client_impl;
+}
+
+const char* VideoLayerImpl::LayerTypeAsString() const {
+ return "cc::VideoLayerImpl";
+}
+
+} // namespace cc
diff --git a/chromium/cc/layers/video_layer_impl.h b/chromium/cc/layers/video_layer_impl.h
new file mode 100644
index 00000000000..b07187115ed
--- /dev/null
+++ b/chromium/cc/layers/video_layer_impl.h
@@ -0,0 +1,69 @@
+// 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.
+
+#ifndef CC_LAYERS_VIDEO_LAYER_IMPL_H_
+#define CC_LAYERS_VIDEO_LAYER_IMPL_H_
+
+#include <vector>
+
+#include "cc/base/cc_export.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/resources/video_resource_updater.h"
+
+namespace media {
+class VideoFrame;
+}
+
+namespace cc {
+class VideoFrameProvider;
+class VideoFrameProviderClientImpl;
+
+class CC_EXPORT VideoLayerImpl : public LayerImpl {
+ public:
+ static scoped_ptr<VideoLayerImpl> Create(LayerTreeImpl* tree_impl,
+ int id,
+ VideoFrameProvider* provider);
+ virtual ~VideoLayerImpl();
+
+ // LayerImpl implementation.
+ virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl)
+ OVERRIDE;
+ virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE;
+ virtual bool WillDraw(DrawMode draw_mode,
+ ResourceProvider* resource_provider) OVERRIDE;
+ virtual void AppendQuads(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data) OVERRIDE;
+ virtual void DidDraw(ResourceProvider* resource_provider) OVERRIDE;
+ virtual void DidBecomeActive() OVERRIDE;
+ virtual void DidLoseOutputSurface() OVERRIDE;
+
+ void SetNeedsRedraw();
+
+ void SetProviderClientImpl(
+ scoped_refptr<VideoFrameProviderClientImpl> provider_client_impl);
+
+ private:
+ VideoLayerImpl(LayerTreeImpl* tree_impl, int id);
+
+ virtual const char* LayerTypeAsString() const OVERRIDE;
+
+ scoped_refptr<VideoFrameProviderClientImpl> provider_client_impl_;
+
+ scoped_refptr<media::VideoFrame> frame_;
+
+ scoped_ptr<VideoResourceUpdater> updater_;
+ VideoFrameExternalResources::ResourceType frame_resource_type_;
+ std::vector<ResourceProvider::ResourceId> frame_resources_;
+
+ // TODO(danakj): Remove these, hide software path inside ResourceProvider and
+ // ExternalResource (aka TextureMailbox) classes.
+ std::vector<unsigned> software_resources_;
+ TextureMailbox::ReleaseCallback software_release_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(VideoLayerImpl);
+};
+
+} // namespace cc
+
+#endif // CC_LAYERS_VIDEO_LAYER_IMPL_H_
diff --git a/chromium/cc/output/begin_frame_args.cc b/chromium/cc/output/begin_frame_args.cc
new file mode 100644
index 00000000000..cefb22bdc13
--- /dev/null
+++ b/chromium/cc/output/begin_frame_args.cc
@@ -0,0 +1,66 @@
+// Copyright 2013 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 "cc/output/begin_frame_args.h"
+
+namespace cc {
+
+BeginFrameArgs::BeginFrameArgs()
+ : frame_time(base::TimeTicks()),
+ deadline(base::TimeTicks()),
+ interval(base::TimeDelta::FromMicroseconds(-1)) {
+}
+
+BeginFrameArgs::BeginFrameArgs(base::TimeTicks frame_time,
+ base::TimeTicks deadline,
+ base::TimeDelta interval)
+ : frame_time(frame_time),
+ deadline(deadline),
+ interval(interval)
+{}
+
+BeginFrameArgs BeginFrameArgs::Create(base::TimeTicks frame_time,
+ base::TimeTicks deadline,
+ base::TimeDelta interval) {
+ return BeginFrameArgs(frame_time, deadline, interval);
+}
+
+BeginFrameArgs BeginFrameArgs::CreateForSynchronousCompositor() {
+ // For WebView/SynchronousCompositor, we always want to draw immediately,
+ // so we set the deadline to 0 and guess that the interval is 16 milliseconds.
+ return BeginFrameArgs(base::TimeTicks::Now(),
+ base::TimeTicks(),
+ DefaultInterval());
+}
+
+BeginFrameArgs BeginFrameArgs::CreateForTesting() {
+ base::TimeTicks now = base::TimeTicks::Now();
+ return BeginFrameArgs(now,
+ now + (DefaultInterval() / 2),
+ DefaultInterval());
+}
+
+BeginFrameArgs BeginFrameArgs::CreateExpiredForTesting() {
+ base::TimeTicks now = base::TimeTicks::Now();
+ return BeginFrameArgs(now,
+ now - DefaultInterval(),
+ DefaultInterval());
+}
+
+base::TimeDelta BeginFrameArgs::DefaultDeadlineAdjustment() {
+ // Using a large deadline adjustment will effectively revert BeginFrame
+ // scheduling to the hard vsync scheduling we used to have.
+ return base::TimeDelta::FromSeconds(-1);
+}
+
+base::TimeDelta BeginFrameArgs::DefaultInterval() {
+ return base::TimeDelta::FromMicroseconds(16666);
+}
+
+base::TimeDelta BeginFrameArgs::DefaultRetroactiveBeginFramePeriod() {
+ return base::TimeDelta::FromMicroseconds(4444);
+}
+
+
+} // namespace cc
diff --git a/chromium/cc/output/begin_frame_args.h b/chromium/cc/output/begin_frame_args.h
new file mode 100644
index 00000000000..22693dbef94
--- /dev/null
+++ b/chromium/cc/output/begin_frame_args.h
@@ -0,0 +1,56 @@
+// Copyright 2013 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.
+
+#ifndef CC_OUTPUT_BEGIN_FRAME_ARGS_H_
+#define CC_OUTPUT_BEGIN_FRAME_ARGS_H_
+
+#include "base/time/time.h"
+#include "cc/base/cc_export.h"
+
+namespace cc {
+
+struct CC_EXPORT BeginFrameArgs {
+ // Creates an invalid set of values.
+ BeginFrameArgs();
+
+ // You should be able to find all instances where a BeginFrame has been
+ // created by searching for "BeginFrameArgs::Create".
+ static BeginFrameArgs Create(base::TimeTicks frame_time,
+ base::TimeTicks deadline,
+ base::TimeDelta interval);
+ static BeginFrameArgs CreateForSynchronousCompositor();
+ static BeginFrameArgs CreateForTesting();
+ static BeginFrameArgs CreateExpiredForTesting();
+
+ // This is the default delta that will be used to adjust the deadline when
+ // proper draw-time estimations are not yet available.
+ static base::TimeDelta DefaultDeadlineAdjustment();
+
+ // This is the default interval to use to avoid sprinkling the code with
+ // magic numbers.
+ static base::TimeDelta DefaultInterval();
+
+ // This is the default amount of time after the frame_time to retroactively
+ // send a BeginFrame that had been skipped. This only has an effect if the
+ // deadline has passed, since the deadline is also used to trigger BeginFrame
+ // retroactively.
+ static base::TimeDelta DefaultRetroactiveBeginFramePeriod();
+
+ bool IsValid() const {
+ return interval >= base::TimeDelta();
+ }
+
+ base::TimeTicks frame_time;
+ base::TimeTicks deadline;
+ base::TimeDelta interval;
+
+ private:
+ BeginFrameArgs(base::TimeTicks frame_time,
+ base::TimeTicks deadline,
+ base::TimeDelta interval);
+};
+
+} // namespace cc
+
+#endif // CC_OUTPUT_BEGIN_FRAME_ARGS_H_
diff --git a/chromium/cc/output/compositor_frame.cc b/chromium/cc/output/compositor_frame.cc
new file mode 100644
index 00000000000..e6d713d50e5
--- /dev/null
+++ b/chromium/cc/output/compositor_frame.cc
@@ -0,0 +1,20 @@
+// 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 "cc/output/compositor_frame.h"
+
+namespace cc {
+
+CompositorFrame::CompositorFrame() {}
+
+CompositorFrame::~CompositorFrame() {}
+
+void CompositorFrame::AssignTo(CompositorFrame* target) {
+ target->delegated_frame_data = delegated_frame_data.Pass();
+ target->gl_frame_data = gl_frame_data.Pass();
+ target->software_frame_data = software_frame_data.Pass();
+ target->metadata = metadata;
+}
+
+} // namespace cc
diff --git a/chromium/cc/output/compositor_frame.h b/chromium/cc/output/compositor_frame.h
new file mode 100644
index 00000000000..e5c8a334161
--- /dev/null
+++ b/chromium/cc/output/compositor_frame.h
@@ -0,0 +1,35 @@
+// 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.
+
+#ifndef CC_OUTPUT_COMPOSITOR_FRAME_H_
+#define CC_OUTPUT_COMPOSITOR_FRAME_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/output/compositor_frame_metadata.h"
+#include "cc/output/delegated_frame_data.h"
+#include "cc/output/gl_frame_data.h"
+#include "cc/output/software_frame_data.h"
+
+namespace cc {
+
+class CC_EXPORT CompositorFrame {
+ public:
+ CompositorFrame();
+ ~CompositorFrame();
+
+ CompositorFrameMetadata metadata;
+ scoped_ptr<DelegatedFrameData> delegated_frame_data;
+ scoped_ptr<GLFrameData> gl_frame_data;
+ scoped_ptr<SoftwareFrameData> software_frame_data;
+
+ void AssignTo(CompositorFrame* target);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CompositorFrame);
+};
+
+} // namespace cc
+
+#endif // CC_OUTPUT_COMPOSITOR_FRAME_H_
diff --git a/chromium/cc/output/compositor_frame_ack.cc b/chromium/cc/output/compositor_frame_ack.cc
new file mode 100644
index 00000000000..feb0bac3674
--- /dev/null
+++ b/chromium/cc/output/compositor_frame_ack.cc
@@ -0,0 +1,14 @@
+// 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 "cc/output/compositor_frame_ack.h"
+
+namespace cc {
+
+CompositorFrameAck::CompositorFrameAck()
+ : last_software_frame_id(0) {}
+
+CompositorFrameAck::~CompositorFrameAck() {}
+
+} // namespace cc
diff --git a/chromium/cc/output/compositor_frame_ack.h b/chromium/cc/output/compositor_frame_ack.h
new file mode 100644
index 00000000000..276ecf0d96e
--- /dev/null
+++ b/chromium/cc/output/compositor_frame_ack.h
@@ -0,0 +1,30 @@
+// 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.
+
+#ifndef CC_OUTPUT_COMPOSITOR_FRAME_ACK_H_
+#define CC_OUTPUT_COMPOSITOR_FRAME_ACK_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/output/gl_frame_data.h"
+#include "cc/resources/transferable_resource.h"
+
+namespace cc {
+
+class CC_EXPORT CompositorFrameAck {
+ public:
+ CompositorFrameAck();
+ ~CompositorFrameAck();
+
+ TransferableResourceArray resources;
+ scoped_ptr<GLFrameData> gl_frame_data;
+ unsigned last_software_frame_id;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CompositorFrameAck);
+};
+
+} // namespace cc
+
+#endif // CC_OUTPUT_COMPOSITOR_FRAME_ACK_H_
diff --git a/chromium/cc/output/compositor_frame_metadata.cc b/chromium/cc/output/compositor_frame_metadata.cc
new file mode 100644
index 00000000000..bec3ab41119
--- /dev/null
+++ b/chromium/cc/output/compositor_frame_metadata.cc
@@ -0,0 +1,20 @@
+// Copyright (c) 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 "cc/output/compositor_frame_metadata.h"
+
+namespace cc {
+
+CompositorFrameMetadata::CompositorFrameMetadata()
+ : device_scale_factor(0.f),
+ page_scale_factor(0.f),
+ min_page_scale_factor(0.f),
+ max_page_scale_factor(0.f),
+ overdraw_bottom_height(0.f) {
+}
+
+CompositorFrameMetadata::~CompositorFrameMetadata() {
+}
+
+} // namespace cc
diff --git a/chromium/cc/output/compositor_frame_metadata.h b/chromium/cc/output/compositor_frame_metadata.h
new file mode 100644
index 00000000000..4e569bbb2c7
--- /dev/null
+++ b/chromium/cc/output/compositor_frame_metadata.h
@@ -0,0 +1,46 @@
+// Copyright (c) 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.
+
+#ifndef CC_OUTPUT_COMPOSITOR_FRAME_METADATA_H_
+#define CC_OUTPUT_COMPOSITOR_FRAME_METADATA_H_
+
+#include "cc/base/cc_export.h"
+#include "ui/base/latency_info.h"
+#include "ui/gfx/size_f.h"
+#include "ui/gfx/vector2d_f.h"
+
+namespace cc {
+
+class CC_EXPORT CompositorFrameMetadata {
+ public:
+ CompositorFrameMetadata();
+ ~CompositorFrameMetadata();
+
+ // The device scale factor used to generate this compositor frame.
+ float device_scale_factor;
+
+ // Scroll offset and scale of the root layer. This can be used for tasks
+ // like positioning windowed plugins.
+ gfx::Vector2dF root_scroll_offset;
+ float page_scale_factor;
+
+ // These limits can be used together with the scroll/scale fields above to
+ // determine if scrolling/scaling in a particular direction is possible.
+ gfx::SizeF viewport_size;
+ gfx::SizeF root_layer_size;
+ float min_page_scale_factor;
+ float max_page_scale_factor;
+
+ // Used to position the Android location top bar and page content, whose
+ // precise position is computed by the renderer compositor.
+ gfx::Vector2dF location_bar_offset;
+ gfx::Vector2dF location_bar_content_translation;
+ float overdraw_bottom_height;
+
+ ui::LatencyInfo latency_info;
+};
+
+} // namespace cc
+
+#endif // CC_OUTPUT_COMPOSITOR_FRAME_METADATA_H_
diff --git a/chromium/cc/output/context_provider.h b/chromium/cc/output/context_provider.h
new file mode 100644
index 00000000000..13b7df5948b
--- /dev/null
+++ b/chromium/cc/output/context_provider.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2013 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.
+
+#ifndef CC_OUTPUT_CONTEXT_PROVIDER_H_
+#define CC_OUTPUT_CONTEXT_PROVIDER_H_
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+
+class GrContext;
+namespace WebKit { class WebGraphicsContext3D; }
+
+namespace cc {
+
+class ContextProvider : public base::RefCountedThreadSafe<ContextProvider> {
+ public:
+ // Bind the 3d context to the current thread. This should be called before
+ // accessing the contexts. Calling it more than once should have no effect.
+ // Once this function has been called, the class should only be accessed
+ // from the same thread.
+ virtual bool BindToCurrentThread() = 0;
+
+ virtual WebKit::WebGraphicsContext3D* Context3d() = 0;
+ virtual class GrContext* GrContext() = 0;
+
+ // Ask the provider to check if the contexts are valid or lost. If they are,
+ // this should invalidate the provider so that it can be replaced with a new
+ // one.
+ virtual void VerifyContexts() = 0;
+
+ // A method to be called from the main thread that should return true if
+ // the context inside the provider is no longer valid.
+ virtual bool DestroyedOnMainThread() = 0;
+
+ // Sets a callback to be called when the context is lost. This should be
+ // called from the same thread that the context is bound to. To avoid races,
+ // it should be called before BindToCurrentThread(), or VerifyContexts()
+ // should be called after setting the callback.
+ typedef base::Closure LostContextCallback;
+ virtual void SetLostContextCallback(
+ const LostContextCallback& lost_context_callback) = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<ContextProvider>;
+ virtual ~ContextProvider() {}
+};
+
+} // namespace cc
+
+#endif // CC_OUTPUT_CONTEXT_PROVIDER_H_
diff --git a/chromium/cc/output/copy_output_request.cc b/chromium/cc/output/copy_output_request.cc
new file mode 100644
index 00000000000..995c3f4cfcc
--- /dev/null
+++ b/chromium/cc/output/copy_output_request.cc
@@ -0,0 +1,50 @@
+// Copyright 2013 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 "cc/output/copy_output_request.h"
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/logging.h"
+#include "cc/output/copy_output_result.h"
+#include "cc/resources/texture_mailbox.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+namespace cc {
+
+CopyOutputRequest::CopyOutputRequest() {}
+
+CopyOutputRequest::CopyOutputRequest(
+ bool force_bitmap_result,
+ const CopyOutputRequestCallback& result_callback)
+ : force_bitmap_result_(force_bitmap_result),
+ has_area_(false),
+ result_callback_(result_callback) {
+}
+
+CopyOutputRequest::~CopyOutputRequest() {
+ if (!result_callback_.is_null())
+ SendResult(CopyOutputResult::CreateEmptyResult().Pass());
+}
+
+void CopyOutputRequest::SendResult(scoped_ptr<CopyOutputResult> result) {
+ base::ResetAndReturn(&result_callback_).Run(result.Pass());
+}
+
+void CopyOutputRequest::SendEmptyResult() {
+ SendResult(CopyOutputResult::CreateEmptyResult().Pass());
+}
+
+void CopyOutputRequest::SendBitmapResult(scoped_ptr<SkBitmap> bitmap) {
+ SendResult(CopyOutputResult::CreateBitmapResult(bitmap.Pass()).Pass());
+}
+
+void CopyOutputRequest::SendTextureResult(gfx::Size size,
+ scoped_ptr<TextureMailbox> texture) {
+ DCHECK(texture->IsTexture());
+ SendResult(CopyOutputResult::CreateTextureResult(size,
+ texture.Pass()).Pass());
+}
+
+} // namespace cc
diff --git a/chromium/cc/output/copy_output_request.h b/chromium/cc/output/copy_output_request.h
new file mode 100644
index 00000000000..3f4d925ee16
--- /dev/null
+++ b/chromium/cc/output/copy_output_request.h
@@ -0,0 +1,86 @@
+// Copyright 2013 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.
+
+#ifndef CC_OUTPUT_COPY_OUTPUT_REQUEST_H_
+#define CC_OUTPUT_COPY_OUTPUT_REQUEST_H_
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "ui/gfx/rect.h"
+
+class SkBitmap;
+
+namespace cc {
+class CopyOutputResult;
+class TextureMailbox;
+
+class CC_EXPORT CopyOutputRequest {
+ public:
+ typedef base::Callback<void(scoped_ptr<CopyOutputResult> result)>
+ CopyOutputRequestCallback;
+
+ static scoped_ptr<CopyOutputRequest> CreateEmptyRequest() {
+ return make_scoped_ptr(new CopyOutputRequest);
+ }
+ static scoped_ptr<CopyOutputRequest> CreateRequest(
+ const CopyOutputRequestCallback& result_callback) {
+ return make_scoped_ptr(new CopyOutputRequest(false, result_callback));
+ }
+ static scoped_ptr<CopyOutputRequest> CreateBitmapRequest(
+ const CopyOutputRequestCallback& result_callback) {
+ return make_scoped_ptr(new CopyOutputRequest(true, result_callback));
+ }
+ static scoped_ptr<CopyOutputRequest> CreateRelayRequest(
+ const CopyOutputRequest& original_request,
+ const CopyOutputRequestCallback& result_callback) {
+ scoped_ptr<CopyOutputRequest> relay = CreateRequest(result_callback);
+ relay->force_bitmap_result_ = original_request.force_bitmap_result_;
+ relay->has_area_ = original_request.has_area_;
+ relay->area_ = original_request.area_;
+ return relay.Pass();
+ }
+
+ ~CopyOutputRequest();
+
+ bool IsEmpty() const { return result_callback_.is_null(); }
+
+ bool force_bitmap_result() const { return force_bitmap_result_; }
+
+ // By default copy requests copy the entire layer's subtree output. If an
+ // area is given, then the intersection of this rect (in layer space) with
+ // the layer's subtree output will be returned.
+ void set_area(gfx::Rect area) {
+ has_area_ = true;
+ area_ = area;
+ }
+ bool has_area() const { return has_area_; }
+ gfx::Rect area() const { return area_; }
+
+ void SendEmptyResult();
+ void SendBitmapResult(scoped_ptr<SkBitmap> bitmap);
+ void SendTextureResult(gfx::Size size,
+ scoped_ptr<TextureMailbox> texture_mailbox);
+
+ void SendResult(scoped_ptr<CopyOutputResult> result);
+
+ bool Equals(const CopyOutputRequest& other) const {
+ return result_callback_.Equals(other.result_callback_) &&
+ force_bitmap_result_ == other.force_bitmap_result_;
+ }
+
+ private:
+ CopyOutputRequest();
+ explicit CopyOutputRequest(bool force_bitmap_result,
+ const CopyOutputRequestCallback& result_callback);
+
+ bool force_bitmap_result_;
+ bool has_area_;
+ gfx::Rect area_;
+ CopyOutputRequestCallback result_callback_;
+};
+
+} // namespace cc
+
+#endif // CC_OUTPUT_COPY_OUTPUT_REQUEST_H_
diff --git a/chromium/cc/output/copy_output_result.cc b/chromium/cc/output/copy_output_result.cc
new file mode 100644
index 00000000000..55213cde6b8
--- /dev/null
+++ b/chromium/cc/output/copy_output_result.cc
@@ -0,0 +1,42 @@
+// Copyright 2013 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 "cc/output/copy_output_result.h"
+
+#include "base/logging.h"
+#include "cc/resources/texture_mailbox.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+namespace cc {
+
+CopyOutputResult::CopyOutputResult() {}
+
+CopyOutputResult::CopyOutputResult(scoped_ptr<SkBitmap> bitmap)
+ : size_(bitmap->width(), bitmap->height()),
+ bitmap_(bitmap.Pass()) {
+ DCHECK(bitmap_);
+}
+
+CopyOutputResult::CopyOutputResult(gfx::Size size,
+ scoped_ptr<TextureMailbox> texture_mailbox)
+ : size_(size),
+ texture_mailbox_(texture_mailbox.Pass()) {
+ DCHECK(texture_mailbox_);
+ DCHECK(texture_mailbox_->IsTexture());
+}
+
+CopyOutputResult::~CopyOutputResult() {
+ if (texture_mailbox_)
+ texture_mailbox_->RunReleaseCallback(0, false);
+}
+
+scoped_ptr<SkBitmap> CopyOutputResult::TakeBitmap() {
+ return bitmap_.Pass();
+}
+
+scoped_ptr<TextureMailbox> CopyOutputResult::TakeTexture() {
+ return texture_mailbox_.Pass();
+}
+
+} // namespace cc
diff --git a/chromium/cc/output/copy_output_result.h b/chromium/cc/output/copy_output_result.h
new file mode 100644
index 00000000000..04cf2c6d489
--- /dev/null
+++ b/chromium/cc/output/copy_output_result.h
@@ -0,0 +1,55 @@
+// Copyright 2013 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.
+
+#ifndef CC_OUTPUT_COPY_OUTPUT_RESULT_H_
+#define CC_OUTPUT_COPY_OUTPUT_RESULT_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "ui/gfx/size.h"
+
+class SkBitmap;
+
+namespace cc {
+class TextureMailbox;
+
+class CC_EXPORT CopyOutputResult {
+ public:
+ static scoped_ptr<CopyOutputResult> CreateEmptyResult() {
+ return make_scoped_ptr(new CopyOutputResult);
+ }
+ static scoped_ptr<CopyOutputResult> CreateBitmapResult(
+ scoped_ptr<SkBitmap> bitmap) {
+ return make_scoped_ptr(new CopyOutputResult(bitmap.Pass()));
+ }
+ static scoped_ptr<CopyOutputResult> CreateTextureResult(
+ gfx::Size size,
+ scoped_ptr<TextureMailbox> texture_mailbox) {
+ return make_scoped_ptr(new CopyOutputResult(size, texture_mailbox.Pass()));
+ }
+
+ ~CopyOutputResult();
+
+ bool IsEmpty() const { return !HasBitmap() && !HasTexture(); }
+ bool HasBitmap() const { return !!bitmap_; }
+ bool HasTexture() const { return !!texture_mailbox_; }
+
+ gfx::Size size() const { return size_; }
+ scoped_ptr<SkBitmap> TakeBitmap();
+ scoped_ptr<TextureMailbox> TakeTexture();
+
+ private:
+ CopyOutputResult();
+ explicit CopyOutputResult(scoped_ptr<SkBitmap> bitmap);
+ explicit CopyOutputResult(gfx::Size size,
+ scoped_ptr<TextureMailbox> texture_mailbox);
+
+ gfx::Size size_;
+ scoped_ptr<SkBitmap> bitmap_;
+ scoped_ptr<TextureMailbox> texture_mailbox_;
+};
+
+} // namespace cc
+
+#endif // CC_OUTPUT_COPY_OUTPUT_RESULT_H_
diff --git a/chromium/cc/output/delegated_frame_data.cc b/chromium/cc/output/delegated_frame_data.cc
new file mode 100644
index 00000000000..516a3ec5a5f
--- /dev/null
+++ b/chromium/cc/output/delegated_frame_data.cc
@@ -0,0 +1,13 @@
+// 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 "cc/output/delegated_frame_data.h"
+
+namespace cc {
+
+DelegatedFrameData::DelegatedFrameData() {}
+
+DelegatedFrameData::~DelegatedFrameData() {}
+
+} // namespace cc
diff --git a/chromium/cc/output/delegated_frame_data.h b/chromium/cc/output/delegated_frame_data.h
new file mode 100644
index 00000000000..f7b89bce843
--- /dev/null
+++ b/chromium/cc/output/delegated_frame_data.h
@@ -0,0 +1,30 @@
+// 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.
+
+#ifndef CC_OUTPUT_DELEGATED_FRAME_DATA_H_
+#define CC_OUTPUT_DELEGATED_FRAME_DATA_H_
+
+#include "cc/base/cc_export.h"
+#include "cc/base/scoped_ptr_vector.h"
+#include "cc/quads/render_pass.h"
+#include "cc/resources/transferable_resource.h"
+#include "ui/gfx/size.h"
+
+namespace cc {
+
+class CC_EXPORT DelegatedFrameData {
+ public:
+ DelegatedFrameData();
+ ~DelegatedFrameData();
+
+ TransferableResourceArray resource_list;
+ ScopedPtrVector<RenderPass> render_pass_list;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DelegatedFrameData);
+};
+
+} // namespace cc
+
+#endif // CC_OUTPUT_DELEGATED_FRAME_DATA_H_
diff --git a/chromium/cc/output/delegating_renderer.cc b/chromium/cc/output/delegating_renderer.cc
new file mode 100644
index 00000000000..bd1c5d1174d
--- /dev/null
+++ b/chromium/cc/output/delegating_renderer.cc
@@ -0,0 +1,222 @@
+// 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 "cc/output/delegating_renderer.h"
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/debug/trace_event.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "cc/output/compositor_frame_ack.h"
+#include "cc/quads/checkerboard_draw_quad.h"
+#include "cc/quads/debug_border_draw_quad.h"
+#include "cc/quads/render_pass.h"
+#include "cc/quads/render_pass_draw_quad.h"
+#include "cc/quads/solid_color_draw_quad.h"
+#include "cc/quads/texture_draw_quad.h"
+#include "cc/quads/tile_draw_quad.h"
+#include "cc/quads/yuv_video_draw_quad.h"
+#include "cc/resources/resource_provider.h"
+#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h"
+#include "third_party/khronos/GLES2/gl2ext.h"
+
+using WebKit::WebGraphicsContext3D;
+
+namespace cc {
+
+scoped_ptr<DelegatingRenderer> DelegatingRenderer::Create(
+ RendererClient* client,
+ OutputSurface* output_surface,
+ ResourceProvider* resource_provider) {
+ scoped_ptr<DelegatingRenderer> renderer(
+ new DelegatingRenderer(client, output_surface, resource_provider));
+ if (!renderer->Initialize())
+ return scoped_ptr<DelegatingRenderer>();
+ return renderer.Pass();
+}
+
+DelegatingRenderer::DelegatingRenderer(
+ RendererClient* client,
+ OutputSurface* output_surface,
+ ResourceProvider* resource_provider)
+ : Renderer(client),
+ output_surface_(output_surface),
+ resource_provider_(resource_provider),
+ visible_(true) {
+ DCHECK(resource_provider_);
+}
+
+bool DelegatingRenderer::Initialize() {
+ capabilities_.using_partial_swap = false;
+ capabilities_.max_texture_size = resource_provider_->max_texture_size();
+ capabilities_.best_texture_format = resource_provider_->best_texture_format();
+ capabilities_.allow_partial_texture_updates = false;
+ capabilities_.using_offscreen_context3d = false;
+
+ WebGraphicsContext3D* context3d = resource_provider_->GraphicsContext3D();
+
+ if (!context3d) {
+ // Software compositing.
+ return true;
+ }
+
+ if (!context3d->makeContextCurrent())
+ return false;
+
+ std::string unique_context_name = base::StringPrintf(
+ "%s-%p",
+ Settings().compositor_name.c_str(),
+ context3d);
+ context3d->pushGroupMarkerEXT(unique_context_name.c_str());
+
+ std::string extensions_string =
+ UTF16ToASCII(context3d->getString(GL_EXTENSIONS));
+
+ std::vector<std::string> extensions;
+ base::SplitString(extensions_string, ' ', &extensions);
+
+ // TODO(danakj): We need non-GPU-specific paths for these things. This
+ // renderer shouldn't need to use context3d extensions directly.
+ bool has_set_visibility = false;
+ bool has_io_surface = false;
+ bool has_arb_texture_rect = false;
+ bool has_egl_image = false;
+ bool has_map_image = false;
+ for (size_t i = 0; i < extensions.size(); ++i) {
+ if (extensions[i] == "GL_CHROMIUM_set_visibility") {
+ has_set_visibility = true;
+ } else if (extensions[i] == "GL_CHROMIUM_iosurface") {
+ has_io_surface = true;
+ } else if (extensions[i] == "GL_ARB_texture_rectangle") {
+ has_arb_texture_rect = true;
+ } else if (extensions[i] == "GL_OES_EGL_image_external") {
+ has_egl_image = true;
+ } else if (extensions[i] == "GL_CHROMIUM_map_image") {
+ has_map_image = true;
+ }
+ }
+
+ if (has_io_surface)
+ DCHECK(has_arb_texture_rect);
+
+ capabilities_.using_set_visibility = has_set_visibility;
+
+ capabilities_.using_egl_image = has_egl_image;
+
+ capabilities_.using_map_image = has_map_image;
+
+ return true;
+}
+
+DelegatingRenderer::~DelegatingRenderer() {}
+
+const RendererCapabilities& DelegatingRenderer::Capabilities() const {
+ return capabilities_;
+}
+
+bool DelegatingRenderer::CanReadPixels() const { return false; }
+
+static ResourceProvider::ResourceId AppendToArray(
+ ResourceProvider::ResourceIdArray* array,
+ ResourceProvider::ResourceId id) {
+ array->push_back(id);
+ return id;
+}
+
+void DelegatingRenderer::DrawFrame(
+ RenderPassList* render_passes_in_draw_order) {
+ TRACE_EVENT0("cc", "DelegatingRenderer::DrawFrame");
+
+ DCHECK(!frame_for_swap_buffers_.delegated_frame_data);
+
+ frame_for_swap_buffers_.metadata = client_->MakeCompositorFrameMetadata();
+
+ frame_for_swap_buffers_.delegated_frame_data =
+ make_scoped_ptr(new DelegatedFrameData);
+ DelegatedFrameData& out_data = *frame_for_swap_buffers_.delegated_frame_data;
+ // Move the render passes and resources into the |out_frame|.
+ out_data.render_pass_list.swap(*render_passes_in_draw_order);
+
+ // Collect all resource ids in the render passes into a ResourceIdArray.
+ ResourceProvider::ResourceIdArray resources;
+ DrawQuad::ResourceIteratorCallback append_to_array =
+ base::Bind(&AppendToArray, &resources);
+ for (size_t i = 0; i < out_data.render_pass_list.size(); ++i) {
+ RenderPass* render_pass = out_data.render_pass_list.at(i);
+ for (size_t j = 0; j < render_pass->quad_list.size(); ++j)
+ render_pass->quad_list[j]->IterateResources(append_to_array);
+ }
+ resource_provider_->PrepareSendToParent(resources, &out_data.resource_list);
+}
+
+void DelegatingRenderer::SwapBuffers() {
+ TRACE_EVENT0("cc", "DelegatingRenderer::SwapBuffers");
+
+ output_surface_->SwapBuffers(&frame_for_swap_buffers_);
+ frame_for_swap_buffers_.delegated_frame_data.reset();
+}
+
+void DelegatingRenderer::GetFramebufferPixels(void* pixels, gfx::Rect rect) {
+ NOTREACHED();
+}
+
+void DelegatingRenderer::ReceiveSwapBuffersAck(
+ const CompositorFrameAck& ack) {
+ resource_provider_->ReceiveFromParent(ack.resources);
+}
+
+bool DelegatingRenderer::IsContextLost() {
+ WebGraphicsContext3D* context3d = resource_provider_->GraphicsContext3D();
+ if (!context3d)
+ return false;
+ return context3d->getGraphicsResetStatusARB() != GL_NO_ERROR;
+}
+
+void DelegatingRenderer::SetVisible(bool visible) {
+ if (visible == visible_)
+ return;
+
+ visible_ = visible;
+ WebGraphicsContext3D* context = resource_provider_->GraphicsContext3D();
+ if (!visible_) {
+ TRACE_EVENT0("cc", "DelegatingRenderer::SetVisible dropping resources");
+ resource_provider_->ReleaseCachedData();
+ if (context)
+ context->flush();
+ }
+ if (capabilities_.using_set_visibility) {
+ // We loop visibility to the GPU process, since that's what manages memory.
+ // That will allow it to feed us with memory allocations that we can act
+ // upon.
+ DCHECK(context);
+ context->setVisibilityCHROMIUM(visible);
+ }
+}
+
+void DelegatingRenderer::SendManagedMemoryStats(size_t bytes_visible,
+ size_t bytes_visible_and_nearby,
+ size_t bytes_allocated) {
+ WebGraphicsContext3D* context = resource_provider_->GraphicsContext3D();
+ if (!context) {
+ // TODO(piman): software path.
+ NOTIMPLEMENTED();
+ return;
+ }
+ WebKit::WebGraphicsManagedMemoryStats stats;
+ stats.bytesVisible = bytes_visible;
+ stats.bytesVisibleAndNearby = bytes_visible_and_nearby;
+ stats.bytesAllocated = bytes_allocated;
+ stats.backbufferRequested = false;
+ context->sendManagedMemoryStatsCHROMIUM(&stats);
+}
+
+void DelegatingRenderer::SetDiscardBackBufferWhenNotVisible(bool discard) {
+ // Nothing to do, we don't have a back buffer.
+}
+
+} // namespace cc
diff --git a/chromium/cc/output/delegating_renderer.h b/chromium/cc/output/delegating_renderer.h
new file mode 100644
index 00000000000..11c8c6bd41d
--- /dev/null
+++ b/chromium/cc/output/delegating_renderer.h
@@ -0,0 +1,66 @@
+// 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.
+
+#ifndef CC_OUTPUT_DELEGATING_RENDERER_H_
+#define CC_OUTPUT_DELEGATING_RENDERER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/output/renderer.h"
+
+namespace cc {
+
+class OutputSurface;
+class ResourceProvider;
+
+class CC_EXPORT DelegatingRenderer : public Renderer {
+ public:
+ static scoped_ptr<DelegatingRenderer> Create(
+ RendererClient* client,
+ OutputSurface* output_surface,
+ ResourceProvider* resource_provider);
+ virtual ~DelegatingRenderer();
+
+ virtual const RendererCapabilities& Capabilities() const OVERRIDE;
+
+ virtual bool CanReadPixels() const OVERRIDE;
+
+ virtual void DrawFrame(RenderPassList* render_passes_in_draw_order) OVERRIDE;
+
+ virtual void Finish() OVERRIDE {}
+
+ virtual void SwapBuffers() OVERRIDE;
+ virtual void ReceiveSwapBuffersAck(const CompositorFrameAck&) OVERRIDE;
+
+ virtual void GetFramebufferPixels(void* pixels, gfx::Rect rect) OVERRIDE;
+
+ virtual bool IsContextLost() OVERRIDE;
+
+ virtual void SetVisible(bool visible) OVERRIDE;
+
+ virtual void SendManagedMemoryStats(size_t bytes_visible,
+ size_t bytes_visible_and_nearby,
+ size_t bytes_allocated) OVERRIDE;
+
+ virtual void SetDiscardBackBufferWhenNotVisible(bool discard) OVERRIDE;
+
+ private:
+ DelegatingRenderer(RendererClient* client,
+ OutputSurface* output_surface,
+ ResourceProvider* resource_provider);
+ bool Initialize();
+
+ OutputSurface* output_surface_;
+ ResourceProvider* resource_provider_;
+ RendererCapabilities capabilities_;
+ CompositorFrame frame_for_swap_buffers_;
+ bool visible_;
+
+ DISALLOW_COPY_AND_ASSIGN(DelegatingRenderer);
+};
+
+} // namespace cc
+
+#endif // CC_OUTPUT_DELEGATING_RENDERER_H_
diff --git a/chromium/cc/output/delegating_renderer_unittest.cc b/chromium/cc/output/delegating_renderer_unittest.cc
new file mode 100644
index 00000000000..6cb532fe86b
--- /dev/null
+++ b/chromium/cc/output/delegating_renderer_unittest.cc
@@ -0,0 +1,145 @@
+// Copyright 2013 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 "cc/output/delegating_renderer.h"
+
+#include "cc/test/fake_output_surface.h"
+#include "cc/test/layer_tree_test.h"
+#include "cc/test/render_pass_test_common.h"
+#include "cc/test/render_pass_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+
+class DelegatingRendererTest : public LayerTreeTest {
+ public:
+ DelegatingRendererTest() : LayerTreeTest(), output_surface_(NULL) {}
+ virtual ~DelegatingRendererTest() {}
+
+ virtual scoped_ptr<OutputSurface> CreateOutputSurface(bool fallback)
+ OVERRIDE {
+ scoped_ptr<FakeOutputSurface> output_surface =
+ FakeOutputSurface::CreateDelegating3d();
+ output_surface_ = output_surface.get();
+ return output_surface.PassAs<OutputSurface>();
+ }
+
+ protected:
+ TestWebGraphicsContext3D* context3d_;
+ FakeOutputSurface* output_surface_;
+};
+
+class DelegatingRendererTestDraw : public DelegatingRendererTest {
+ public:
+ virtual void BeginTest() OVERRIDE {
+ layer_tree_host()->SetPageScaleFactorAndLimits(1.f, 0.5f, 4.f);
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ virtual bool PrepareToDrawOnThread(LayerTreeHostImpl* host_impl,
+ LayerTreeHostImpl::FrameData* frame,
+ bool result)
+ OVERRIDE {
+ EXPECT_EQ(0u, output_surface_->num_sent_frames());
+
+ const CompositorFrame& last_frame = output_surface_->last_sent_frame();
+ EXPECT_FALSE(last_frame.delegated_frame_data);
+ EXPECT_FALSE(last_frame.gl_frame_data);
+ EXPECT_EQ(0.f, last_frame.metadata.min_page_scale_factor);
+ EXPECT_EQ(0.f, last_frame.metadata.max_page_scale_factor);
+ return true;
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ EXPECT_EQ(0u, output_surface_->num_sent_frames());
+ }
+
+ virtual void SwapBuffersOnThread(LayerTreeHostImpl* host_impl,
+ bool result) OVERRIDE {
+ EXPECT_TRUE(result);
+ EXPECT_EQ(1u, output_surface_->num_sent_frames());
+
+ const CompositorFrame& last_frame = output_surface_->last_sent_frame();
+ DelegatedFrameData* last_frame_data = last_frame.delegated_frame_data.get();
+ ASSERT_TRUE(last_frame.delegated_frame_data);
+ EXPECT_FALSE(last_frame.gl_frame_data);
+ EXPECT_EQ(
+ gfx::Rect(host_impl->device_viewport_size()).ToString(),
+ last_frame_data->render_pass_list.back()->output_rect.ToString());
+ EXPECT_EQ(0.5f, last_frame.metadata.min_page_scale_factor);
+ EXPECT_EQ(4.f, last_frame.metadata.max_page_scale_factor);
+
+ EXPECT_EQ(
+ 0u, last_frame.delegated_frame_data->resource_list.size());
+ EXPECT_EQ(1u, last_frame.delegated_frame_data->render_pass_list.size());
+
+ EndTest();
+ }
+};
+
+SINGLE_AND_MULTI_THREAD_DELEGATING_RENDERER_TEST_F(DelegatingRendererTestDraw);
+
+class DelegatingRendererTestResources : public DelegatingRendererTest {
+ public:
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ virtual bool PrepareToDrawOnThread(
+ LayerTreeHostImpl* host_impl,
+ LayerTreeHostImpl::FrameData* frame,
+ bool result) OVERRIDE {
+ frame->render_passes.clear();
+ frame->render_passes_by_id.clear();
+
+ TestRenderPass* child_pass = AddRenderPass(
+ &frame->render_passes,
+ RenderPass::Id(2, 1),
+ gfx::Rect(3, 3, 10, 10),
+ gfx::Transform());
+ child_pass->AppendOneOfEveryQuadType(
+ host_impl->resource_provider(), RenderPass::Id(0, 0));
+
+ TestRenderPass* pass = AddRenderPass(
+ &frame->render_passes,
+ RenderPass::Id(1, 1),
+ gfx::Rect(3, 3, 10, 10),
+ gfx::Transform());
+ pass->AppendOneOfEveryQuadType(
+ host_impl->resource_provider(), child_pass->id);
+ return true;
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ EXPECT_EQ(0u, output_surface_->num_sent_frames());
+ }
+
+ virtual void SwapBuffersOnThread(LayerTreeHostImpl* host_impl,
+ bool result) OVERRIDE {
+ EXPECT_TRUE(result);
+ EXPECT_EQ(1u, output_surface_->num_sent_frames());
+
+ const CompositorFrame& last_frame = output_surface_->last_sent_frame();
+ ASSERT_TRUE(last_frame.delegated_frame_data);
+
+ EXPECT_EQ(2u, last_frame.delegated_frame_data->render_pass_list.size());
+ // Each render pass has 10 resources in it. And the root render pass has a
+ // mask resource used when drawing the child render pass. The number 10 may
+ // change if AppendOneOfEveryQuadType() is updated, and the value here
+ // should be updated accordingly.
+ EXPECT_EQ(
+ 21u, last_frame.delegated_frame_data->resource_list.size());
+
+ EndTest();
+ }
+};
+
+SINGLE_AND_MULTI_THREAD_DELEGATING_RENDERER_TEST_F(
+ DelegatingRendererTestResources);
+
+} // namespace cc
diff --git a/chromium/cc/output/direct_renderer.cc b/chromium/cc/output/direct_renderer.cc
new file mode 100644
index 00000000000..a664d7df822
--- /dev/null
+++ b/chromium/cc/output/direct_renderer.cc
@@ -0,0 +1,388 @@
+// 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 "cc/output/direct_renderer.h"
+
+#include <utility>
+#include <vector>
+
+#include "base/containers/hash_tables.h"
+#include "base/debug/trace_event.h"
+#include "base/metrics/histogram.h"
+#include "cc/base/math_util.h"
+#include "cc/output/copy_output_request.h"
+#include "cc/quads/draw_quad.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/transform.h"
+
+static gfx::Transform OrthoProjectionMatrix(float left,
+ float right,
+ float bottom,
+ float top) {
+ // Use the standard formula to map the clipping frustum to the cube from
+ // [-1, -1, -1] to [1, 1, 1].
+ float delta_x = right - left;
+ float delta_y = top - bottom;
+ gfx::Transform proj;
+ if (!delta_x || !delta_y)
+ return proj;
+ proj.matrix().setDouble(0, 0, 2.0f / delta_x);
+ proj.matrix().setDouble(0, 3, -(right + left) / delta_x);
+ proj.matrix().setDouble(1, 1, 2.0f / delta_y);
+ proj.matrix().setDouble(1, 3, -(top + bottom) / delta_y);
+
+ // Z component of vertices is always set to zero as we don't use the depth
+ // buffer while drawing.
+ proj.matrix().setDouble(2, 2, 0);
+
+ return proj;
+}
+
+static gfx::Transform window_matrix(int x, int y, int width, int height) {
+ gfx::Transform canvas;
+
+ // Map to window position and scale up to pixel coordinates.
+ canvas.Translate3d(x, y, 0);
+ canvas.Scale3d(width, height, 0);
+
+ // Map from ([-1, -1] to [1, 1]) -> ([0, 0] to [1, 1])
+ canvas.Translate3d(0.5, 0.5, 0.5);
+ canvas.Scale3d(0.5, 0.5, 0.5);
+
+ return canvas;
+}
+
+namespace cc {
+
+DirectRenderer::DrawingFrame::DrawingFrame()
+ : root_render_pass(NULL),
+ current_render_pass(NULL),
+ current_texture(NULL) {}
+
+DirectRenderer::DrawingFrame::~DrawingFrame() {}
+
+//
+// static
+gfx::RectF DirectRenderer::QuadVertexRect() {
+ return gfx::RectF(-0.5f, -0.5f, 1.f, 1.f);
+}
+
+// static
+void DirectRenderer::QuadRectTransform(gfx::Transform* quad_rect_transform,
+ const gfx::Transform& quad_transform,
+ const gfx::RectF& quad_rect) {
+ *quad_rect_transform = quad_transform;
+ quad_rect_transform->Translate(0.5 * quad_rect.width() + quad_rect.x(),
+ 0.5 * quad_rect.height() + quad_rect.y());
+ quad_rect_transform->Scale(quad_rect.width(), quad_rect.height());
+}
+
+void DirectRenderer::InitializeViewport(DrawingFrame* frame,
+ gfx::Rect draw_rect,
+ gfx::Rect viewport_rect,
+ gfx::Size surface_size) {
+ bool flip_y = FlippedFramebuffer();
+
+ DCHECK_GE(viewport_rect.x(), 0);
+ DCHECK_GE(viewport_rect.y(), 0);
+ DCHECK_LE(viewport_rect.right(), surface_size.width());
+ DCHECK_LE(viewport_rect.bottom(), surface_size.height());
+ if (flip_y) {
+ frame->projection_matrix = OrthoProjectionMatrix(draw_rect.x(),
+ draw_rect.right(),
+ draw_rect.bottom(),
+ draw_rect.y());
+ } else {
+ frame->projection_matrix = OrthoProjectionMatrix(draw_rect.x(),
+ draw_rect.right(),
+ draw_rect.y(),
+ draw_rect.bottom());
+ }
+
+ gfx::Rect window_rect = viewport_rect;
+ if (flip_y)
+ window_rect.set_y(surface_size.height() - viewport_rect.bottom());
+ frame->window_matrix = window_matrix(window_rect.x(),
+ window_rect.y(),
+ window_rect.width(),
+ window_rect.height());
+ SetDrawViewport(window_rect);
+
+ current_draw_rect_ = draw_rect;
+ current_viewport_rect_ = viewport_rect;
+ current_surface_size_ = surface_size;
+}
+
+gfx::Rect DirectRenderer::MoveFromDrawToWindowSpace(
+ const gfx::RectF& draw_rect) const {
+ gfx::Rect window_rect = gfx::ToEnclosingRect(draw_rect);
+ window_rect -= current_draw_rect_.OffsetFromOrigin();
+ window_rect += current_viewport_rect_.OffsetFromOrigin();
+ if (FlippedFramebuffer())
+ window_rect.set_y(current_surface_size_.height() - window_rect.bottom());
+ return window_rect;
+}
+
+DirectRenderer::DirectRenderer(RendererClient* client,
+ OutputSurface* output_surface,
+ ResourceProvider* resource_provider)
+ : Renderer(client),
+ output_surface_(output_surface),
+ resource_provider_(resource_provider) {}
+
+DirectRenderer::~DirectRenderer() {}
+
+bool DirectRenderer::CanReadPixels() const { return true; }
+
+void DirectRenderer::SetEnlargePassTextureAmountForTesting(
+ gfx::Vector2d amount) {
+ enlarge_pass_texture_amount_ = amount;
+}
+
+void DirectRenderer::DecideRenderPassAllocationsForFrame(
+ const RenderPassList& render_passes_in_draw_order) {
+ if (!resource_provider_)
+ return;
+
+ base::hash_map<RenderPass::Id, const RenderPass*> render_passes_in_frame;
+ for (size_t i = 0; i < render_passes_in_draw_order.size(); ++i)
+ render_passes_in_frame.insert(std::pair<RenderPass::Id, const RenderPass*>(
+ render_passes_in_draw_order[i]->id, render_passes_in_draw_order[i]));
+
+ std::vector<RenderPass::Id> passes_to_delete;
+ ScopedPtrHashMap<RenderPass::Id, CachedResource>::const_iterator pass_iter;
+ for (pass_iter = render_pass_textures_.begin();
+ pass_iter != render_pass_textures_.end();
+ ++pass_iter) {
+ base::hash_map<RenderPass::Id, const RenderPass*>::const_iterator it =
+ render_passes_in_frame.find(pass_iter->first);
+ if (it == render_passes_in_frame.end()) {
+ passes_to_delete.push_back(pass_iter->first);
+ continue;
+ }
+
+ const RenderPass* render_pass_in_frame = it->second;
+ gfx::Size required_size = RenderPassTextureSize(render_pass_in_frame);
+ GLenum required_format = RenderPassTextureFormat(render_pass_in_frame);
+ CachedResource* texture = pass_iter->second;
+ DCHECK(texture);
+
+ bool size_appropriate = texture->size().width() >= required_size.width() &&
+ texture->size().height() >= required_size.height();
+ if (texture->id() &&
+ (!size_appropriate || texture->format() != required_format))
+ texture->Free();
+ }
+
+ // Delete RenderPass textures from the previous frame that will not be used
+ // again.
+ for (size_t i = 0; i < passes_to_delete.size(); ++i)
+ render_pass_textures_.erase(passes_to_delete[i]);
+
+ for (size_t i = 0; i < render_passes_in_draw_order.size(); ++i) {
+ if (!render_pass_textures_.contains(render_passes_in_draw_order[i]->id)) {
+ scoped_ptr<CachedResource> texture =
+ CachedResource::Create(resource_provider_);
+ render_pass_textures_.set(render_passes_in_draw_order[i]->id,
+ texture.Pass());
+ }
+ }
+}
+
+void DirectRenderer::DrawFrame(RenderPassList* render_passes_in_draw_order) {
+ TRACE_EVENT0("cc", "DirectRenderer::DrawFrame");
+ UMA_HISTOGRAM_COUNTS("Renderer4.renderPassCount",
+ render_passes_in_draw_order->size());
+
+ const RenderPass* root_render_pass = render_passes_in_draw_order->back();
+ DCHECK(root_render_pass);
+
+ DrawingFrame frame;
+ frame.root_render_pass = root_render_pass;
+ frame.root_damage_rect =
+ Capabilities().using_partial_swap && client_->AllowPartialSwap() ?
+ root_render_pass->damage_rect : root_render_pass->output_rect;
+ frame.root_damage_rect.Intersect(gfx::Rect(client_->DeviceViewport().size()));
+
+ EnsureBackbuffer();
+
+ // Only reshape when we know we are going to draw. Otherwise, the reshape
+ // can leave the window at the wrong size if we never draw and the proper
+ // viewport size is never set.
+ output_surface_->Reshape(client_->DeviceViewport().size(),
+ client_->DeviceScaleFactor());
+
+ BeginDrawingFrame(&frame);
+ for (size_t i = 0; i < render_passes_in_draw_order->size(); ++i) {
+ RenderPass* pass = render_passes_in_draw_order->at(i);
+ DrawRenderPass(&frame, pass);
+
+ for (ScopedPtrVector<CopyOutputRequest>::iterator it =
+ pass->copy_requests.begin();
+ it != pass->copy_requests.end();
+ ++it) {
+ if (i > 0) {
+ // Doing a readback is destructive of our state on Mac, so make sure
+ // we restore the state between readbacks. http://crbug.com/99393.
+ UseRenderPass(&frame, pass);
+ }
+ CopyCurrentRenderPassToBitmap(&frame, pass->copy_requests.take(it));
+ }
+ }
+ FinishDrawingFrame(&frame);
+
+ render_passes_in_draw_order->clear();
+}
+
+gfx::RectF DirectRenderer::ComputeScissorRectForRenderPass(
+ const DrawingFrame* frame) {
+ gfx::RectF render_pass_scissor = frame->current_render_pass->output_rect;
+
+ if (frame->root_damage_rect == frame->root_render_pass->output_rect)
+ return render_pass_scissor;
+
+ gfx::Transform inverse_transform(gfx::Transform::kSkipInitialization);
+ if (frame->current_render_pass->transform_to_root_target.GetInverse(
+ &inverse_transform)) {
+ // Only intersect inverse-projected damage if the transform is invertible.
+ gfx::RectF damage_rect_in_render_pass_space =
+ MathUtil::ProjectClippedRect(inverse_transform,
+ frame->root_damage_rect);
+ render_pass_scissor.Intersect(damage_rect_in_render_pass_space);
+ }
+
+ return render_pass_scissor;
+}
+
+void DirectRenderer::SetScissorStateForQuad(const DrawingFrame* frame,
+ const DrawQuad& quad) {
+ if (quad.isClipped()) {
+ gfx::RectF quad_scissor_rect = quad.clipRect();
+ SetScissorTestRect(MoveFromDrawToWindowSpace(quad_scissor_rect));
+ } else {
+ EnsureScissorTestDisabled();
+ }
+}
+
+void DirectRenderer::SetScissorStateForQuadWithRenderPassScissor(
+ const DrawingFrame* frame,
+ const DrawQuad& quad,
+ const gfx::RectF& render_pass_scissor,
+ bool* should_skip_quad) {
+ gfx::RectF quad_scissor_rect = render_pass_scissor;
+
+ if (quad.isClipped())
+ quad_scissor_rect.Intersect(quad.clipRect());
+
+ if (quad_scissor_rect.IsEmpty()) {
+ *should_skip_quad = true;
+ return;
+ }
+
+ *should_skip_quad = false;
+ SetScissorTestRect(MoveFromDrawToWindowSpace(quad_scissor_rect));
+}
+
+void DirectRenderer::FinishDrawingQuadList() {}
+
+void DirectRenderer::DrawRenderPass(DrawingFrame* frame,
+ const RenderPass* render_pass) {
+ TRACE_EVENT0("cc", "DirectRenderer::DrawRenderPass");
+ if (!UseRenderPass(frame, render_pass))
+ return;
+
+ bool using_scissor_as_optimization =
+ Capabilities().using_partial_swap && client_->AllowPartialSwap();
+ gfx::RectF render_pass_scissor;
+
+ if (using_scissor_as_optimization) {
+ render_pass_scissor = ComputeScissorRectForRenderPass(frame);
+ SetScissorTestRect(MoveFromDrawToWindowSpace(render_pass_scissor));
+ }
+
+ if (frame->current_render_pass != frame->root_render_pass ||
+ client_->ShouldClearRootRenderPass()) {
+ if (!using_scissor_as_optimization)
+ EnsureScissorTestDisabled();
+ ClearFramebuffer(frame);
+ }
+
+ const QuadList& quad_list = render_pass->quad_list;
+ for (QuadList::ConstBackToFrontIterator it = quad_list.BackToFrontBegin();
+ it != quad_list.BackToFrontEnd();
+ ++it) {
+ const DrawQuad& quad = *(*it);
+ bool should_skip_quad = false;
+
+ if (using_scissor_as_optimization) {
+ SetScissorStateForQuadWithRenderPassScissor(
+ frame, quad, render_pass_scissor, &should_skip_quad);
+ } else {
+ SetScissorStateForQuad(frame, quad);
+ }
+
+ if (!should_skip_quad)
+ DoDrawQuad(frame, *it);
+ }
+ FinishDrawingQuadList();
+
+ CachedResource* texture = render_pass_textures_.get(render_pass->id);
+ if (texture) {
+ texture->set_is_complete(
+ !render_pass->has_occlusion_from_outside_target_surface);
+ }
+}
+
+bool DirectRenderer::UseRenderPass(DrawingFrame* frame,
+ const RenderPass* render_pass) {
+ frame->current_render_pass = render_pass;
+ frame->current_texture = NULL;
+
+ if (render_pass == frame->root_render_pass) {
+ BindFramebufferToOutputSurface(frame);
+ InitializeViewport(frame,
+ render_pass->output_rect,
+ client_->DeviceViewport(),
+ output_surface_->SurfaceSize());
+ return true;
+ }
+
+ if (!resource_provider_)
+ return false;
+
+ CachedResource* texture = render_pass_textures_.get(render_pass->id);
+ DCHECK(texture);
+
+ gfx::Size size = RenderPassTextureSize(render_pass);
+ size.Enlarge(enlarge_pass_texture_amount_.x(),
+ enlarge_pass_texture_amount_.y());
+ if (!texture->id() &&
+ !texture->Allocate(size,
+ RenderPassTextureFormat(render_pass),
+ ResourceProvider::TextureUsageFramebuffer))
+ return false;
+
+ return BindFramebufferToTexture(frame, texture, render_pass->output_rect);
+}
+
+bool DirectRenderer::HaveCachedResourcesForRenderPassId(RenderPass::Id id)
+ const {
+ if (!Settings().cache_render_pass_contents)
+ return false;
+
+ CachedResource* texture = render_pass_textures_.get(id);
+ return texture && texture->id() && texture->is_complete();
+}
+
+// static
+gfx::Size DirectRenderer::RenderPassTextureSize(const RenderPass* render_pass) {
+ return render_pass->output_rect.size();
+}
+
+// static
+GLenum DirectRenderer::RenderPassTextureFormat(const RenderPass* render_pass) {
+ return GL_RGBA;
+}
+
+} // namespace cc
diff --git a/chromium/cc/output/direct_renderer.h b/chromium/cc/output/direct_renderer.h
new file mode 100644
index 00000000000..ffc2b67b8a7
--- /dev/null
+++ b/chromium/cc/output/direct_renderer.h
@@ -0,0 +1,144 @@
+// 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.
+
+#ifndef CC_OUTPUT_DIRECT_RENDERER_H_
+#define CC_OUTPUT_DIRECT_RENDERER_H_
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "cc/base/cc_export.h"
+#include "cc/output/renderer.h"
+#include "cc/resources/resource_provider.h"
+#include "cc/resources/scoped_resource.h"
+
+namespace cc {
+
+class ResourceProvider;
+
+// This is the base class for code shared between the GL and software
+// renderer implementations. "Direct" refers to the fact that it does not
+// delegate rendering to another compositor.
+class CC_EXPORT DirectRenderer : public Renderer {
+ public:
+ virtual ~DirectRenderer();
+
+ ResourceProvider* resource_provider() const { return resource_provider_; }
+
+ virtual bool CanReadPixels() const OVERRIDE;
+ virtual void DecideRenderPassAllocationsForFrame(
+ const RenderPassList& render_passes_in_draw_order) OVERRIDE;
+ virtual bool HaveCachedResourcesForRenderPassId(RenderPass::Id id) const
+ OVERRIDE;
+ virtual void DrawFrame(RenderPassList* render_passes_in_draw_order) OVERRIDE;
+
+ struct CC_EXPORT DrawingFrame {
+ DrawingFrame();
+ ~DrawingFrame();
+
+ const RenderPass* root_render_pass;
+ const RenderPass* current_render_pass;
+ const ScopedResource* current_texture;
+
+ gfx::RectF root_damage_rect;
+
+ gfx::Transform projection_matrix;
+ gfx::Transform window_matrix;
+ };
+
+ void SetEnlargePassTextureAmountForTesting(gfx::Vector2d amount);
+
+ protected:
+ DirectRenderer(RendererClient* client,
+ OutputSurface* output_surface,
+ ResourceProvider* resource_provider);
+
+ class CachedResource : public ScopedResource {
+ public:
+ static scoped_ptr<CachedResource> Create(
+ ResourceProvider* resource_provider) {
+ return make_scoped_ptr(new CachedResource(resource_provider));
+ }
+ virtual ~CachedResource() {}
+
+ bool is_complete() const { return is_complete_; }
+ void set_is_complete(bool is_complete) { is_complete_ = is_complete; }
+
+ protected:
+ explicit CachedResource(ResourceProvider* resource_provider)
+ : ScopedResource(resource_provider),
+ is_complete_(false) {}
+
+ private:
+ bool is_complete_;
+
+ DISALLOW_COPY_AND_ASSIGN(CachedResource);
+ };
+
+ static gfx::RectF QuadVertexRect();
+ static void QuadRectTransform(gfx::Transform* quad_rect_transform,
+ const gfx::Transform& quad_transform,
+ const gfx::RectF& quad_rect);
+ void InitializeViewport(DrawingFrame* frame,
+ gfx::Rect draw_rect,
+ gfx::Rect viewport_rect,
+ gfx::Size surface_size);
+ gfx::Rect MoveFromDrawToWindowSpace(const gfx::RectF& draw_rect) const;
+
+ static gfx::RectF ComputeScissorRectForRenderPass(const DrawingFrame* frame);
+ void SetScissorStateForQuad(const DrawingFrame* frame, const DrawQuad& quad);
+ void SetScissorStateForQuadWithRenderPassScissor(
+ const DrawingFrame* frame,
+ const DrawQuad& quad,
+ const gfx::RectF& render_pass_scissor,
+ bool* should_skip_quad);
+
+ static gfx::Size RenderPassTextureSize(const RenderPass* render_pass);
+ static GLenum RenderPassTextureFormat(const RenderPass* render_pass);
+
+ void DrawRenderPass(DrawingFrame* frame, const RenderPass* render_pass);
+ bool UseRenderPass(DrawingFrame* frame, const RenderPass* render_pass);
+
+ virtual void BindFramebufferToOutputSurface(DrawingFrame* frame) = 0;
+ virtual bool BindFramebufferToTexture(DrawingFrame* frame,
+ const ScopedResource* resource,
+ gfx::Rect target_rect) = 0;
+ virtual void SetDrawViewport(gfx::Rect window_space_viewport) = 0;
+ virtual void SetScissorTestRect(gfx::Rect scissor_rect) = 0;
+ virtual void ClearFramebuffer(DrawingFrame* frame) = 0;
+ virtual void DoDrawQuad(DrawingFrame* frame, const DrawQuad* quad) = 0;
+ virtual void BeginDrawingFrame(DrawingFrame* frame) = 0;
+ virtual void FinishDrawingFrame(DrawingFrame* frame) = 0;
+ virtual void FinishDrawingQuadList();
+ virtual bool FlippedFramebuffer() const = 0;
+ virtual void EnsureScissorTestEnabled() = 0;
+ virtual void EnsureScissorTestDisabled() = 0;
+ virtual void DiscardBackbuffer() {}
+ virtual void EnsureBackbuffer() {}
+
+ virtual void CopyCurrentRenderPassToBitmap(
+ DrawingFrame* frame,
+ scoped_ptr<CopyOutputRequest> request) = 0;
+
+ ScopedPtrHashMap<RenderPass::Id, CachedResource> render_pass_textures_;
+ OutputSurface* output_surface_;
+ ResourceProvider* resource_provider_;
+
+ // For use in coordinate conversion, this stores the output rect, viewport
+ // rect (= unflipped version of glViewport rect), and the size of target
+ // framebuffer. During a draw, this stores the values for the current render
+ // pass; in between draws, they retain the values for the root render pass of
+ // the last draw.
+ gfx::Rect current_draw_rect_;
+ gfx::Rect current_viewport_rect_;
+ gfx::Size current_surface_size_;
+
+ private:
+ gfx::Vector2d enlarge_pass_texture_amount_;
+
+ DISALLOW_COPY_AND_ASSIGN(DirectRenderer);
+};
+
+} // namespace cc
+
+#endif // CC_OUTPUT_DIRECT_RENDERER_H_
diff --git a/chromium/cc/output/filter_operation.cc b/chromium/cc/output/filter_operation.cc
new file mode 100644
index 00000000000..6a778388ae5
--- /dev/null
+++ b/chromium/cc/output/filter_operation.cc
@@ -0,0 +1,257 @@
+// Copyright 2013 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 <algorithm>
+
+#include "base/values.h"
+#include "cc/base/math_util.h"
+#include "cc/output/filter_operation.h"
+#include "third_party/skia/include/core/SkMath.h"
+
+namespace cc {
+
+bool FilterOperation::operator==(const FilterOperation& other) const {
+ if (type_ != other.type_)
+ return false;
+ if (type_ == COLOR_MATRIX)
+ return !memcmp(matrix_, other.matrix_, sizeof(matrix_));
+ if (type_ == DROP_SHADOW) {
+ return amount_ == other.amount_ &&
+ drop_shadow_offset_ == other.drop_shadow_offset_ &&
+ drop_shadow_color_ == other.drop_shadow_color_;
+ }
+ return amount_ == other.amount_;
+}
+
+FilterOperation::FilterOperation(FilterType type, float amount)
+ : type_(type),
+ amount_(amount),
+ drop_shadow_offset_(0, 0),
+ drop_shadow_color_(0),
+ zoom_inset_(0) {
+ DCHECK_NE(type_, DROP_SHADOW);
+ DCHECK_NE(type_, COLOR_MATRIX);
+ memset(matrix_, 0, sizeof(matrix_));
+}
+
+FilterOperation::FilterOperation(FilterType type,
+ gfx::Point offset,
+ float stdDeviation,
+ SkColor color)
+ : type_(type),
+ amount_(stdDeviation),
+ drop_shadow_offset_(offset),
+ drop_shadow_color_(color),
+ zoom_inset_(0) {
+ DCHECK_EQ(type_, DROP_SHADOW);
+ memset(matrix_, 0, sizeof(matrix_));
+}
+
+FilterOperation::FilterOperation(FilterType type, SkScalar matrix[20])
+ : type_(type),
+ amount_(0),
+ drop_shadow_offset_(0, 0),
+ drop_shadow_color_(0),
+ zoom_inset_(0) {
+ DCHECK_EQ(type_, COLOR_MATRIX);
+ memcpy(matrix_, matrix, sizeof(matrix_));
+}
+
+FilterOperation::FilterOperation(FilterType type, float amount, int inset)
+ : type_(type),
+ amount_(amount),
+ drop_shadow_offset_(0, 0),
+ drop_shadow_color_(0),
+ zoom_inset_(inset) {
+ DCHECK_EQ(type_, ZOOM);
+ memset(matrix_, 0, sizeof(matrix_));
+}
+
+// TODO(ajuma): Define a version of ui::Tween::ValueBetween for floats, and use
+// that instead.
+static float BlendFloats(float from, float to, double progress) {
+ return from * (1.0 - progress) + to * progress;
+}
+
+static int BlendInts(int from, int to, double progress) {
+ return static_cast<int>(
+ MathUtil::Round(from * (1.0 - progress) + to * progress));
+}
+
+static uint8_t BlendColorComponents(uint8_t from,
+ uint8_t to,
+ uint8_t from_alpha,
+ uint8_t to_alpha,
+ uint8_t blended_alpha,
+ double progress) {
+ // Since progress can be outside [0, 1], blending can produce a value outside
+ // [0, 255].
+ int blended_premultiplied = BlendInts(SkMulDiv255Round(from, from_alpha),
+ SkMulDiv255Round(to, to_alpha),
+ progress);
+ int blended = static_cast<int>(
+ MathUtil::Round(blended_premultiplied * 255.f / blended_alpha));
+ return static_cast<uint8_t>(MathUtil::ClampToRange(blended, 0, 255));
+}
+
+static SkColor BlendSkColors(SkColor from, SkColor to, double progress) {
+ int from_a = SkColorGetA(from);
+ int to_a = SkColorGetA(to);
+ int blended_a = BlendInts(from_a, to_a, progress);
+ if (blended_a <= 0)
+ return SkColorSetARGB(0, 0, 0, 0);
+ blended_a = std::min(blended_a, 255);
+
+ // TODO(ajuma): Use SkFourByteInterp once http://crbug.com/260369 is fixed.
+ uint8_t blended_r = BlendColorComponents(
+ SkColorGetR(from), SkColorGetR(to), from_a, to_a, blended_a, progress);
+ uint8_t blended_g = BlendColorComponents(
+ SkColorGetG(from), SkColorGetG(to), from_a, to_a, blended_a, progress);
+ uint8_t blended_b = BlendColorComponents(
+ SkColorGetB(from), SkColorGetB(to), from_a, to_a, blended_a, progress);
+
+ return SkColorSetARGB(blended_a, blended_r, blended_g, blended_b);
+}
+
+static FilterOperation CreateNoOpFilter(FilterOperation::FilterType type) {
+ switch (type) {
+ case FilterOperation::GRAYSCALE:
+ return FilterOperation::CreateGrayscaleFilter(0.f);
+ case FilterOperation::SEPIA:
+ return FilterOperation::CreateSepiaFilter(0.f);
+ case FilterOperation::SATURATE:
+ return FilterOperation::CreateSaturateFilter(1.f);
+ case FilterOperation::HUE_ROTATE:
+ return FilterOperation::CreateHueRotateFilter(0.f);
+ case FilterOperation::INVERT:
+ return FilterOperation::CreateInvertFilter(0.f);
+ case FilterOperation::BRIGHTNESS:
+ return FilterOperation::CreateBrightnessFilter(1.f);
+ case FilterOperation::CONTRAST:
+ return FilterOperation::CreateContrastFilter(1.f);
+ case FilterOperation::OPACITY:
+ return FilterOperation::CreateOpacityFilter(1.f);
+ case FilterOperation::BLUR:
+ return FilterOperation::CreateBlurFilter(0.f);
+ case FilterOperation::DROP_SHADOW:
+ return FilterOperation::CreateDropShadowFilter(
+ gfx::Point(0, 0), 0.f, SK_ColorTRANSPARENT);
+ case FilterOperation::COLOR_MATRIX: {
+ SkScalar matrix[20];
+ memset(matrix, 0, 20 * sizeof(SkScalar));
+ matrix[0] = matrix[6] = matrix[12] = matrix[18] = 1.f;
+ return FilterOperation::CreateColorMatrixFilter(matrix);
+ }
+ case FilterOperation::ZOOM:
+ return FilterOperation::CreateZoomFilter(1.f, 0);
+ case FilterOperation::SATURATING_BRIGHTNESS:
+ return FilterOperation::CreateSaturatingBrightnessFilter(0.f);
+ default:
+ NOTREACHED();
+ return FilterOperation::CreateEmptyFilter();
+ }
+}
+
+static float ClampAmountForFilterType(float amount,
+ FilterOperation::FilterType type) {
+ switch (type) {
+ case FilterOperation::GRAYSCALE:
+ case FilterOperation::SEPIA:
+ case FilterOperation::INVERT:
+ case FilterOperation::OPACITY:
+ return MathUtil::ClampToRange(amount, 0.f, 1.f);
+ case FilterOperation::SATURATE:
+ case FilterOperation::BRIGHTNESS:
+ case FilterOperation::CONTRAST:
+ case FilterOperation::BLUR:
+ case FilterOperation::DROP_SHADOW:
+ return std::max(amount, 0.f);
+ case FilterOperation::ZOOM:
+ return std::max(amount, 1.f);
+ case FilterOperation::HUE_ROTATE:
+ case FilterOperation::SATURATING_BRIGHTNESS:
+ return amount;
+ case FilterOperation::COLOR_MATRIX:
+ default:
+ NOTREACHED();
+ return amount;
+ }
+}
+
+// static
+FilterOperation FilterOperation::Blend(const FilterOperation* from,
+ const FilterOperation* to,
+ double progress) {
+ FilterOperation blended_filter = FilterOperation::CreateEmptyFilter();
+
+ if (!from && !to)
+ return blended_filter;
+
+ const FilterOperation& from_op = from ? *from : CreateNoOpFilter(to->type());
+ const FilterOperation& to_op = to ? *to : CreateNoOpFilter(from->type());
+
+ if (from_op.type() != to_op.type())
+ return blended_filter;
+
+ DCHECK(to_op.type() != FilterOperation::COLOR_MATRIX);
+ blended_filter.set_type(to_op.type());
+
+ blended_filter.set_amount(ClampAmountForFilterType(
+ BlendFloats(from_op.amount(), to_op.amount(), progress), to_op.type()));
+
+ if (to_op.type() == FilterOperation::DROP_SHADOW) {
+ gfx::Point blended_offset(BlendInts(from_op.drop_shadow_offset().x(),
+ to_op.drop_shadow_offset().x(),
+ progress),
+ BlendInts(from_op.drop_shadow_offset().y(),
+ to_op.drop_shadow_offset().y(),
+ progress));
+ blended_filter.set_drop_shadow_offset(blended_offset);
+ blended_filter.set_drop_shadow_color(BlendSkColors(
+ from_op.drop_shadow_color(), to_op.drop_shadow_color(), progress));
+ } else if (to_op.type() == FilterOperation::ZOOM) {
+ blended_filter.set_zoom_inset(std::max(
+ BlendInts(from_op.zoom_inset(), to_op.zoom_inset(), progress), 0));
+ }
+
+ return blended_filter;
+}
+
+scoped_ptr<base::Value> FilterOperation::AsValue() const {
+ scoped_ptr<base::DictionaryValue> value(new DictionaryValue);
+ value->SetInteger("type", type_);
+ switch (type_) {
+ case FilterOperation::GRAYSCALE:
+ case FilterOperation::SEPIA:
+ case FilterOperation::SATURATE:
+ case FilterOperation::HUE_ROTATE:
+ case FilterOperation::INVERT:
+ case FilterOperation::BRIGHTNESS:
+ case FilterOperation::CONTRAST:
+ case FilterOperation::OPACITY:
+ case FilterOperation::BLUR:
+ case FilterOperation::SATURATING_BRIGHTNESS:
+ value->SetDouble("amount", amount_);
+ break;
+ case FilterOperation::DROP_SHADOW:
+ value->SetDouble("std_deviation", amount_);
+ value->Set("offset", MathUtil::AsValue(drop_shadow_offset_).release());
+ value->SetInteger("color", drop_shadow_color_);
+ break;
+ case FilterOperation::COLOR_MATRIX: {
+ scoped_ptr<ListValue> matrix(new ListValue);
+ for (size_t i = 0; i < arraysize(matrix_); ++i)
+ matrix->AppendDouble(matrix_[i]);
+ value->Set("matrix", matrix.release());
+ break;
+ }
+ case FilterOperation::ZOOM:
+ value->SetDouble("amount", amount_);
+ value->SetDouble("inset", zoom_inset_);
+ break;
+ }
+ return value.PassAs<base::Value>();
+}
+
+} // namespace cc
diff --git a/chromium/cc/output/filter_operation.h b/chromium/cc/output/filter_operation.h
new file mode 100644
index 00000000000..f56176213b1
--- /dev/null
+++ b/chromium/cc/output/filter_operation.h
@@ -0,0 +1,193 @@
+// Copyright 2013 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.
+
+#ifndef CC_OUTPUT_FILTER_OPERATION_H_
+#define CC_OUTPUT_FILTER_OPERATION_H_
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/skia/include/core/SkScalar.h"
+#include "ui/gfx/point.h"
+
+namespace base {
+class Value;
+}
+
+namespace cc {
+
+class CC_EXPORT FilterOperation {
+ public:
+ enum FilterType {
+ GRAYSCALE,
+ SEPIA,
+ SATURATE,
+ HUE_ROTATE,
+ INVERT,
+ BRIGHTNESS,
+ CONTRAST,
+ OPACITY,
+ BLUR,
+ DROP_SHADOW,
+ COLOR_MATRIX,
+ ZOOM,
+ SATURATING_BRIGHTNESS, // Not used in CSS/SVG.
+ };
+
+ FilterType type() const { return type_; }
+
+ float amount() const {
+ DCHECK_NE(type_, COLOR_MATRIX);
+ return amount_;
+ }
+
+ gfx::Point drop_shadow_offset() const {
+ DCHECK_EQ(type_, DROP_SHADOW);
+ return drop_shadow_offset_;
+ }
+
+ SkColor drop_shadow_color() const {
+ DCHECK_EQ(type_, DROP_SHADOW);
+ return drop_shadow_color_;
+ }
+
+ const SkScalar* matrix() const {
+ DCHECK_EQ(type_, COLOR_MATRIX);
+ return matrix_;
+ }
+
+ int zoom_inset() const {
+ DCHECK_EQ(type_, ZOOM);
+ return zoom_inset_;
+ }
+
+ static FilterOperation CreateGrayscaleFilter(float amount) {
+ return FilterOperation(GRAYSCALE, amount);
+ }
+
+ static FilterOperation CreateSepiaFilter(float amount) {
+ return FilterOperation(SEPIA, amount);
+ }
+
+ static FilterOperation CreateSaturateFilter(float amount) {
+ return FilterOperation(SATURATE, amount);
+ }
+
+ static FilterOperation CreateHueRotateFilter(float amount) {
+ return FilterOperation(HUE_ROTATE, amount);
+ }
+
+ static FilterOperation CreateInvertFilter(float amount) {
+ return FilterOperation(INVERT, amount);
+ }
+
+ static FilterOperation CreateBrightnessFilter(float amount) {
+ return FilterOperation(BRIGHTNESS, amount);
+ }
+
+ static FilterOperation CreateContrastFilter(float amount) {
+ return FilterOperation(CONTRAST, amount);
+ }
+
+ static FilterOperation CreateOpacityFilter(float amount) {
+ return FilterOperation(OPACITY, amount);
+ }
+
+ static FilterOperation CreateBlurFilter(float amount) {
+ return FilterOperation(BLUR, amount);
+ }
+
+ static FilterOperation CreateDropShadowFilter(gfx::Point offset,
+ float std_deviation,
+ SkColor color) {
+ return FilterOperation(DROP_SHADOW, offset, std_deviation, color);
+ }
+
+ static FilterOperation CreateColorMatrixFilter(SkScalar matrix[20]) {
+ return FilterOperation(COLOR_MATRIX, matrix);
+ }
+
+ static FilterOperation CreateZoomFilter(float amount, int inset) {
+ return FilterOperation(ZOOM, amount, inset);
+ }
+
+ static FilterOperation CreateSaturatingBrightnessFilter(float amount) {
+ return FilterOperation(SATURATING_BRIGHTNESS, amount);
+ }
+
+ bool operator==(const FilterOperation& other) const;
+
+ bool operator!=(const FilterOperation& other) const {
+ return !(*this == other);
+ }
+
+ // Methods for restoring a FilterOperation.
+ static FilterOperation CreateEmptyFilter() {
+ return FilterOperation(GRAYSCALE, 0.f);
+ }
+
+ void set_type(FilterType type) { type_ = type; }
+
+ void set_amount(float amount) {
+ DCHECK_NE(type_, COLOR_MATRIX);
+ amount_ = amount;
+ }
+
+ void set_drop_shadow_offset(gfx::Point offset) {
+ DCHECK_EQ(type_, DROP_SHADOW);
+ drop_shadow_offset_ = offset;
+ }
+
+ void set_drop_shadow_color(SkColor color) {
+ DCHECK_EQ(type_, DROP_SHADOW);
+ drop_shadow_color_ = color;
+ }
+
+ void set_matrix(const SkScalar matrix[20]) {
+ DCHECK_EQ(type_, COLOR_MATRIX);
+ for (unsigned i = 0; i < 20; ++i)
+ matrix_[i] = matrix[i];
+ }
+
+ void set_zoom_inset(int inset) {
+ DCHECK_EQ(type_, ZOOM);
+ zoom_inset_ = inset;
+ }
+
+ // Given two filters of the same type, returns a filter operation created by
+ // linearly interpolating a |progress| fraction from |from| to |to|. If either
+ // |from| or |to| (but not both) is null, it is treated as a no-op filter of
+ // the same type as the other given filter. If both |from| and |to| are null,
+ // or if |from| and |to| are non-null but of different types, returns a
+ // no-op filter.
+ static FilterOperation Blend(const FilterOperation* from,
+ const FilterOperation* to,
+ double progress);
+
+ scoped_ptr<base::Value> AsValue() const;
+
+ private:
+ FilterOperation(FilterType type, float amount);
+
+ FilterOperation(FilterType type,
+ gfx::Point offset,
+ float stdDeviation,
+ SkColor color);
+
+ FilterOperation(FilterType, SkScalar matrix[20]);
+
+ FilterOperation(FilterType type, float amount, int inset);
+
+ FilterType type_;
+ float amount_;
+ gfx::Point drop_shadow_offset_;
+ SkColor drop_shadow_color_;
+ SkScalar matrix_[20];
+ int zoom_inset_;
+};
+
+} // namespace cc
+
+#endif // CC_OUTPUT_FILTER_OPERATION_H_
diff --git a/chromium/cc/output/filter_operations.cc b/chromium/cc/output/filter_operations.cc
new file mode 100644
index 00000000000..24208418026
--- /dev/null
+++ b/chromium/cc/output/filter_operations.cc
@@ -0,0 +1,157 @@
+// Copyright 2013 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 <cmath>
+
+#include "cc/output/filter_operations.h"
+
+#include "base/values.h"
+#include "cc/output/filter_operation.h"
+
+namespace cc {
+
+FilterOperations::FilterOperations() {}
+
+FilterOperations::FilterOperations(const FilterOperations& other)
+ : operations_(other.operations_) {}
+
+FilterOperations::~FilterOperations() {}
+
+FilterOperations& FilterOperations::operator=(const FilterOperations& other) {
+ operations_ = other.operations_;
+ return *this;
+}
+
+bool FilterOperations::operator==(const FilterOperations& other) const {
+ if (other.size() != size())
+ return false;
+ for (size_t i = 0; i < size(); ++i) {
+ if (other.at(i) != at(i))
+ return false;
+ }
+ return true;
+}
+
+void FilterOperations::Append(const FilterOperation& filter) {
+ operations_.push_back(filter);
+}
+
+void FilterOperations::Clear() {
+ operations_.clear();
+}
+
+bool FilterOperations::IsEmpty() const {
+ return operations_.empty();
+}
+
+static int SpreadForStdDeviation(float std_deviation) {
+ // https://dvcs.w3.org/hg/FXTF/raw-file/tip/filters/index.html#feGaussianBlurElement
+ // provides this approximation for evaluating a gaussian blur by a triple box
+ // filter.
+ float d = floorf(std_deviation * 3.f * sqrt(8.f * atan(1.f)) / 4.f + 0.5f);
+ return static_cast<int>(ceilf(d * 3.f / 2.f));
+}
+
+void FilterOperations::GetOutsets(int* top,
+ int* right,
+ int* bottom,
+ int* left) const {
+ *top = *right = *bottom = *left = 0;
+ for (size_t i = 0; i < operations_.size(); ++i) {
+ const FilterOperation op = operations_[i];
+ if (op.type() == FilterOperation::BLUR ||
+ op.type() == FilterOperation::DROP_SHADOW) {
+ int spread = SpreadForStdDeviation(op.amount());
+ if (op.type() == FilterOperation::BLUR) {
+ *top += spread;
+ *right += spread;
+ *bottom += spread;
+ *left += spread;
+ } else {
+ *top += spread - op.drop_shadow_offset().y();
+ *right += spread + op.drop_shadow_offset().x();
+ *bottom += spread + op.drop_shadow_offset().y();
+ *left += spread - op.drop_shadow_offset().x();
+ }
+ }
+ }
+}
+
+bool FilterOperations::HasFilterThatMovesPixels() const {
+ for (size_t i = 0; i < operations_.size(); ++i) {
+ const FilterOperation op = operations_[i];
+ switch (op.type()) {
+ case FilterOperation::BLUR:
+ case FilterOperation::DROP_SHADOW:
+ case FilterOperation::ZOOM:
+ return true;
+ default:
+ break;
+ }
+ }
+ return false;
+}
+
+bool FilterOperations::HasFilterThatAffectsOpacity() const {
+ for (size_t i = 0; i < operations_.size(); ++i) {
+ const FilterOperation op = operations_[i];
+ switch (op.type()) {
+ case FilterOperation::OPACITY:
+ case FilterOperation::BLUR:
+ case FilterOperation::DROP_SHADOW:
+ case FilterOperation::ZOOM:
+ return true;
+ case FilterOperation::COLOR_MATRIX: {
+ const SkScalar* matrix = op.matrix();
+ return matrix[15] || matrix[16] || matrix[17] || matrix[18] != 1 ||
+ matrix[19];
+ }
+ default:
+ break;
+ }
+ }
+ return false;
+}
+
+FilterOperations FilterOperations::Blend(const FilterOperations& from,
+ double progress) const {
+ FilterOperations blended_filters;
+ if (from.size() == 0) {
+ for (size_t i = 0; i < size(); i++)
+ blended_filters.Append(FilterOperation::Blend(NULL, &at(i), progress));
+ return blended_filters;
+ }
+
+ if (size() == 0) {
+ for (size_t i = 0; i < from.size(); i++) {
+ blended_filters.Append(
+ FilterOperation::Blend(&from.at(i), NULL, progress));
+ }
+ return blended_filters;
+ }
+
+ if (from.size() != size())
+ return *this;
+
+ for (size_t i = 0; i < size(); i++) {
+ if (from.at(i).type() != at(i).type())
+ return *this;
+ }
+
+ for (size_t i = 0; i < size(); i++) {
+ blended_filters.Append(
+ FilterOperation::Blend(&from.at(i), &at(i), progress));
+ }
+
+ return blended_filters;
+}
+
+scoped_ptr<base::Value> FilterOperations::AsValue() const {
+ scoped_ptr<base::ListValue> value(new ListValue);
+ for (size_t i = 0; i < operations_.size(); ++i)
+ value->Append(operations_[i].AsValue().release());
+ return value.PassAs<base::Value>();
+}
+
+} // namespace cc
diff --git a/chromium/cc/output/filter_operations.h b/chromium/cc/output/filter_operations.h
new file mode 100644
index 00000000000..aa8d58dd8d2
--- /dev/null
+++ b/chromium/cc/output/filter_operations.h
@@ -0,0 +1,78 @@
+// Copyright 2013 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.
+
+#ifndef CC_OUTPUT_FILTER_OPERATIONS_H_
+#define CC_OUTPUT_FILTER_OPERATIONS_H_
+
+#include <vector>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/output/filter_operation.h"
+
+namespace base {
+class Value;
+}
+
+namespace cc {
+
+// An ordered list of filter operations.
+class CC_EXPORT FilterOperations {
+ public:
+ FilterOperations();
+
+ FilterOperations(const FilterOperations& other);
+
+ ~FilterOperations();
+
+ FilterOperations& operator=(const FilterOperations& other);
+
+ bool operator==(const FilterOperations& other) const;
+
+ bool operator!=(const FilterOperations& other) const {
+ return !(*this == other);
+ }
+
+ void Append(const FilterOperation& filter);
+
+ // Removes all filter operations.
+ void Clear();
+
+ bool IsEmpty() const;
+
+ void GetOutsets(int* top, int* right, int* bottom, int* left) const;
+ bool HasFilterThatMovesPixels() const;
+ bool HasFilterThatAffectsOpacity() const;
+
+ size_t size() const {
+ return operations_.size();
+ }
+
+ const FilterOperation& at(size_t index) const {
+ DCHECK_LT(index, size());
+ return operations_[index];
+ }
+
+ // If |from| is of the same size as this, where in each position, the filter
+ // in |from| is of the same type as the filter in this, returns a
+ // FilterOperations formed by linearly interpolating at each position a
+ // |progress| fraction of the way from the filter in |from|
+ // to the filter in this. If either |from| or this is an empty sequence,
+ // it is treated as a sequence of the same length as the other sequence,
+ // where the filter at each position is a no-op filter of the same type
+ // as the filter in that position in the other sequence. Otherwise, if
+ // both |from| and this are non-empty sequences but are either of different
+ // lengths or if there is a type mismatch at some position, returns a copy
+ // of this.
+ FilterOperations Blend(const FilterOperations& from, double progress) const;
+
+ scoped_ptr<base::Value> AsValue() const;
+
+ private:
+ std::vector<FilterOperation> operations_;
+};
+
+} // namespace cc
+
+#endif // CC_OUTPUT_FILTER_OPERATIONS_H_
diff --git a/chromium/cc/output/filter_operations_unittest.cc b/chromium/cc/output/filter_operations_unittest.cc
new file mode 100644
index 00000000000..dbf3736ccb0
--- /dev/null
+++ b/chromium/cc/output/filter_operations_unittest.cc
@@ -0,0 +1,628 @@
+// Copyright 2013 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 "cc/output/filter_operations.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/point.h"
+
+namespace cc {
+namespace {
+
+TEST(FilterOperationsTest, GetOutsetsBlur) {
+ FilterOperations ops;
+ ops.Append(FilterOperation::CreateBlurFilter(20));
+ int top, right, bottom, left;
+ top = right = bottom = left = 0;
+ ops.GetOutsets(&top, &right, &bottom, &left);
+ EXPECT_EQ(57, top);
+ EXPECT_EQ(57, right);
+ EXPECT_EQ(57, bottom);
+ EXPECT_EQ(57, left);
+}
+
+TEST(FilterOperationsTest, GetOutsetsDropShadow) {
+ FilterOperations ops;
+ ops.Append(FilterOperation::CreateDropShadowFilter(gfx::Point(3, 8), 20, 0));
+ int top, right, bottom, left;
+ top = right = bottom = left = 0;
+ ops.GetOutsets(&top, &right, &bottom, &left);
+ EXPECT_EQ(49, top);
+ EXPECT_EQ(60, right);
+ EXPECT_EQ(65, bottom);
+ EXPECT_EQ(54, left);
+}
+
+#define SAVE_RESTORE_AMOUNT(filter_name, filter_type, a) \
+ { \
+ FilterOperation op = FilterOperation::Create##filter_name##Filter(a); \
+ EXPECT_EQ(FilterOperation::filter_type, op.type()); \
+ EXPECT_EQ(a, op.amount()); \
+ \
+ FilterOperation op2 = FilterOperation::CreateEmptyFilter(); \
+ op2.set_type(FilterOperation::filter_type); \
+ \
+ EXPECT_NE(a, op2.amount()); \
+ \
+ op2.set_amount(a); \
+ \
+ EXPECT_EQ(FilterOperation::filter_type, op2.type()); \
+ EXPECT_EQ(a, op2.amount()); \
+ }
+
+#define SAVE_RESTORE_OFFSET_AMOUNT_COLOR(filter_name, filter_type, a, b, c) \
+ { \
+ FilterOperation op = \
+ FilterOperation::Create##filter_name##Filter(a, b, c); \
+ EXPECT_EQ(FilterOperation::filter_type, op.type()); \
+ EXPECT_EQ(a, op.drop_shadow_offset()); \
+ EXPECT_EQ(b, op.amount()); \
+ EXPECT_EQ(c, op.drop_shadow_color()); \
+ \
+ FilterOperation op2 = FilterOperation::CreateEmptyFilter(); \
+ op2.set_type(FilterOperation::filter_type); \
+ \
+ EXPECT_NE(a, op2.drop_shadow_offset()); \
+ EXPECT_NE(b, op2.amount()); \
+ EXPECT_NE(c, op2.drop_shadow_color()); \
+ \
+ op2.set_drop_shadow_offset(a); \
+ op2.set_amount(b); \
+ op2.set_drop_shadow_color(c); \
+ \
+ EXPECT_EQ(FilterOperation::filter_type, op2.type()); \
+ EXPECT_EQ(a, op2.drop_shadow_offset()); \
+ EXPECT_EQ(b, op2.amount()); \
+ EXPECT_EQ(c, op2.drop_shadow_color()); \
+ }
+
+#define SAVE_RESTORE_MATRIX(filter_name, filter_type, a) \
+ { \
+ FilterOperation op = FilterOperation::Create##filter_name##Filter(a); \
+ EXPECT_EQ(FilterOperation::filter_type, op.type()); \
+ for (size_t i = 0; i < 20; ++i) \
+ EXPECT_EQ(a[i], op.matrix()[i]); \
+ \
+ FilterOperation op2 = FilterOperation::CreateEmptyFilter(); \
+ op2.set_type(FilterOperation::filter_type); \
+ \
+ for (size_t i = 0; i < 20; ++i) \
+ EXPECT_NE(a[i], op2.matrix()[i]); \
+ \
+ op2.set_matrix(a); \
+ \
+ EXPECT_EQ(FilterOperation::filter_type, op2.type()); \
+ for (size_t i = 0; i < 20; ++i) \
+ EXPECT_EQ(a[i], op.matrix()[i]); \
+ }
+
+#define SAVE_RESTORE_AMOUNT_INSET(filter_name, filter_type, a, b) \
+ { \
+ FilterOperation op = FilterOperation::Create##filter_name##Filter(a, b); \
+ EXPECT_EQ(FilterOperation::filter_type, op.type()); \
+ EXPECT_EQ(a, op.amount()); \
+ EXPECT_EQ(b, op.zoom_inset()); \
+ \
+ FilterOperation op2 = FilterOperation::CreateEmptyFilter(); \
+ op2.set_type(FilterOperation::filter_type); \
+ \
+ EXPECT_NE(a, op2.amount()); \
+ EXPECT_NE(b, op2.zoom_inset()); \
+ \
+ op2.set_amount(a); \
+ op2.set_zoom_inset(b); \
+ \
+ EXPECT_EQ(FilterOperation::filter_type, op2.type()); \
+ EXPECT_EQ(a, op2.amount()); \
+ EXPECT_EQ(b, op2.zoom_inset()); \
+ }
+
+TEST(FilterOperationsTest, SaveAndRestore) {
+ SAVE_RESTORE_AMOUNT(Grayscale, GRAYSCALE, 0.6f);
+ SAVE_RESTORE_AMOUNT(Sepia, SEPIA, 0.6f);
+ SAVE_RESTORE_AMOUNT(Saturate, SATURATE, 0.6f);
+ SAVE_RESTORE_AMOUNT(HueRotate, HUE_ROTATE, 0.6f);
+ SAVE_RESTORE_AMOUNT(Invert, INVERT, 0.6f);
+ SAVE_RESTORE_AMOUNT(Brightness, BRIGHTNESS, 0.6f);
+ SAVE_RESTORE_AMOUNT(Contrast, CONTRAST, 0.6f);
+ SAVE_RESTORE_AMOUNT(Opacity, OPACITY, 0.6f);
+ SAVE_RESTORE_AMOUNT(Blur, BLUR, 0.6f);
+ SAVE_RESTORE_AMOUNT(SaturatingBrightness, SATURATING_BRIGHTNESS, 0.6f);
+ SAVE_RESTORE_OFFSET_AMOUNT_COLOR(
+ DropShadow, DROP_SHADOW, gfx::Point(3, 4), 0.4f, 0xffffff00);
+
+ SkScalar matrix[20] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20};
+ SAVE_RESTORE_MATRIX(ColorMatrix, COLOR_MATRIX, matrix);
+
+ SAVE_RESTORE_AMOUNT_INSET(Zoom, ZOOM, 0.5f, 32);
+}
+
+TEST(FilterOperationsTest, BlendGrayscaleFilters) {
+ FilterOperation from = FilterOperation::CreateGrayscaleFilter(0.25f);
+ FilterOperation to = FilterOperation::CreateGrayscaleFilter(0.75f);
+
+ FilterOperation blended = FilterOperation::Blend(&from, &to, -0.75);
+ FilterOperation expected = FilterOperation::CreateGrayscaleFilter(0.f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(&from, &to, 0.75);
+ expected = FilterOperation::CreateGrayscaleFilter(0.625f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(&from, &to, 1.8);
+ expected = FilterOperation::CreateGrayscaleFilter(1.f);
+ EXPECT_EQ(expected, blended);
+}
+
+TEST(FilterOperationsTest, BlendGrayscaleWithNull) {
+ FilterOperation filter = FilterOperation::CreateGrayscaleFilter(1.f);
+
+ FilterOperation blended = FilterOperation::Blend(&filter, NULL, 0.25);
+ FilterOperation expected = FilterOperation::CreateGrayscaleFilter(0.75f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(NULL, &filter, 0.25);
+ expected = FilterOperation::CreateGrayscaleFilter(0.25f);
+ EXPECT_EQ(expected, blended);
+}
+
+TEST(FilterOperationsTest, BlendSepiaFilters) {
+ FilterOperation from = FilterOperation::CreateSepiaFilter(0.25f);
+ FilterOperation to = FilterOperation::CreateSepiaFilter(0.75f);
+
+ FilterOperation blended = FilterOperation::Blend(&from, &to, -0.75);
+ FilterOperation expected = FilterOperation::CreateSepiaFilter(0.f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(&from, &to, 0.75);
+ expected = FilterOperation::CreateSepiaFilter(0.625f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(&from, &to, 1.8);
+ expected = FilterOperation::CreateSepiaFilter(1.f);
+ EXPECT_EQ(expected, blended);
+}
+
+TEST(FilterOperationsTest, BlendSepiaWithNull) {
+ FilterOperation filter = FilterOperation::CreateSepiaFilter(1.f);
+
+ FilterOperation blended = FilterOperation::Blend(&filter, NULL, 0.25);
+ FilterOperation expected = FilterOperation::CreateSepiaFilter(0.75f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(NULL, &filter, 0.25);
+ expected = FilterOperation::CreateSepiaFilter(0.25f);
+ EXPECT_EQ(expected, blended);
+}
+
+TEST(FilterOperationsTest, BlendSaturateFilters) {
+ FilterOperation from = FilterOperation::CreateSaturateFilter(0.25f);
+ FilterOperation to = FilterOperation::CreateSaturateFilter(0.75f);
+
+ FilterOperation blended = FilterOperation::Blend(&from, &to, -0.75);
+ FilterOperation expected = FilterOperation::CreateSaturateFilter(0.f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(&from, &to, 0.75);
+ expected = FilterOperation::CreateSaturateFilter(0.625f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(&from, &to, 2.0);
+ expected = FilterOperation::CreateSaturateFilter(1.25f);
+ EXPECT_EQ(expected, blended);
+}
+
+TEST(FilterOperationsTest, BlendSaturateWithNull) {
+ FilterOperation filter = FilterOperation::CreateSaturateFilter(0.f);
+
+ FilterOperation blended = FilterOperation::Blend(&filter, NULL, 0.25);
+ FilterOperation expected = FilterOperation::CreateSaturateFilter(0.25f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(NULL, &filter, 0.25);
+ expected = FilterOperation::CreateSaturateFilter(0.75f);
+ EXPECT_EQ(expected, blended);
+}
+
+TEST(FilterOperationsTest, BlendHueRotateFilters) {
+ FilterOperation from = FilterOperation::CreateHueRotateFilter(3.f);
+ FilterOperation to = FilterOperation::CreateHueRotateFilter(7.f);
+
+ FilterOperation blended = FilterOperation::Blend(&from, &to, -0.75);
+ FilterOperation expected = FilterOperation::CreateHueRotateFilter(0.f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(&from, &to, 0.75);
+ expected = FilterOperation::CreateHueRotateFilter(6.f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(&from, &to, 1.5);
+ expected = FilterOperation::CreateHueRotateFilter(9.f);
+ EXPECT_EQ(expected, blended);
+}
+
+TEST(FilterOperationsTest, BlendHueRotateWithNull) {
+ FilterOperation filter = FilterOperation::CreateHueRotateFilter(1.f);
+
+ FilterOperation blended = FilterOperation::Blend(&filter, NULL, 0.25);
+ FilterOperation expected = FilterOperation::CreateHueRotateFilter(0.75f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(NULL, &filter, 0.25);
+ expected = FilterOperation::CreateHueRotateFilter(0.25f);
+ EXPECT_EQ(expected, blended);
+}
+
+TEST(FilterOperationsTest, BlendInvertFilters) {
+ FilterOperation from = FilterOperation::CreateInvertFilter(0.25f);
+ FilterOperation to = FilterOperation::CreateInvertFilter(0.75f);
+
+ FilterOperation blended = FilterOperation::Blend(&from, &to, -0.75);
+ FilterOperation expected = FilterOperation::CreateInvertFilter(0.f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(&from, &to, 0.75);
+ expected = FilterOperation::CreateInvertFilter(0.625f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(&from, &to, 1.8);
+ expected = FilterOperation::CreateInvertFilter(1.f);
+ EXPECT_EQ(expected, blended);
+}
+
+TEST(FilterOperationsTest, BlendInvertWithNull) {
+ FilterOperation filter = FilterOperation::CreateInvertFilter(1.f);
+
+ FilterOperation blended = FilterOperation::Blend(&filter, NULL, 0.25);
+ FilterOperation expected = FilterOperation::CreateInvertFilter(0.75f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(NULL, &filter, 0.25);
+ expected = FilterOperation::CreateInvertFilter(0.25f);
+ EXPECT_EQ(expected, blended);
+}
+
+TEST(FilterOperationsTest, BlendBrightnessFilters) {
+ FilterOperation from = FilterOperation::CreateBrightnessFilter(3.f);
+ FilterOperation to = FilterOperation::CreateBrightnessFilter(7.f);
+
+ FilterOperation blended = FilterOperation::Blend(&from, &to, -0.9);
+ FilterOperation expected = FilterOperation::CreateBrightnessFilter(0.f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(&from, &to, 0.75);
+ expected = FilterOperation::CreateBrightnessFilter(6.f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(&from, &to, 1.5);
+ expected = FilterOperation::CreateBrightnessFilter(9.f);
+ EXPECT_EQ(expected, blended);
+}
+
+TEST(FilterOperationsTest, BlendBrightnessWithNull) {
+ FilterOperation filter = FilterOperation::CreateBrightnessFilter(0.f);
+
+ FilterOperation blended = FilterOperation::Blend(&filter, NULL, 0.25);
+ FilterOperation expected = FilterOperation::CreateBrightnessFilter(0.25f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(NULL, &filter, 0.25);
+ expected = FilterOperation::CreateBrightnessFilter(0.75f);
+ EXPECT_EQ(expected, blended);
+}
+
+TEST(FilterOperationsTest, BlendContrastFilters) {
+ FilterOperation from = FilterOperation::CreateContrastFilter(3.f);
+ FilterOperation to = FilterOperation::CreateContrastFilter(7.f);
+
+ FilterOperation blended = FilterOperation::Blend(&from, &to, -0.9);
+ FilterOperation expected = FilterOperation::CreateContrastFilter(0.f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(&from, &to, 0.75);
+ expected = FilterOperation::CreateContrastFilter(6.f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(&from, &to, 1.5);
+ expected = FilterOperation::CreateContrastFilter(9.f);
+ EXPECT_EQ(expected, blended);
+}
+
+TEST(FilterOperationsTest, BlendContrastWithNull) {
+ FilterOperation filter = FilterOperation::CreateContrastFilter(0.f);
+
+ FilterOperation blended = FilterOperation::Blend(&filter, NULL, 0.25);
+ FilterOperation expected = FilterOperation::CreateContrastFilter(0.25f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(NULL, &filter, 0.25);
+ expected = FilterOperation::CreateContrastFilter(0.75f);
+ EXPECT_EQ(expected, blended);
+}
+
+TEST(FilterOperationsTest, BlendOpacityFilters) {
+ FilterOperation from = FilterOperation::CreateOpacityFilter(0.25f);
+ FilterOperation to = FilterOperation::CreateOpacityFilter(0.75f);
+
+ FilterOperation blended = FilterOperation::Blend(&from, &to, -0.75);
+ FilterOperation expected = FilterOperation::CreateOpacityFilter(0.f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(&from, &to, 0.75);
+ expected = FilterOperation::CreateOpacityFilter(0.625f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(&from, &to, 1.8);
+ expected = FilterOperation::CreateOpacityFilter(1.f);
+ EXPECT_EQ(expected, blended);
+}
+
+TEST(FilterOperationsTest, BlendOpacityWithNull) {
+ FilterOperation filter = FilterOperation::CreateOpacityFilter(0.f);
+
+ FilterOperation blended = FilterOperation::Blend(&filter, NULL, 0.25);
+ FilterOperation expected = FilterOperation::CreateOpacityFilter(0.25f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(NULL, &filter, 0.25);
+ expected = FilterOperation::CreateOpacityFilter(0.75f);
+ EXPECT_EQ(expected, blended);
+}
+
+TEST(FilterOperationsTest, BlendBlurFilters) {
+ FilterOperation from = FilterOperation::CreateBlurFilter(3.f);
+ FilterOperation to = FilterOperation::CreateBlurFilter(7.f);
+
+ FilterOperation blended = FilterOperation::Blend(&from, &to, -0.9);
+ FilterOperation expected = FilterOperation::CreateBlurFilter(0.f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(&from, &to, 0.75);
+ expected = FilterOperation::CreateBlurFilter(6.f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(&from, &to, 1.5);
+ expected = FilterOperation::CreateBlurFilter(9.f);
+ EXPECT_EQ(expected, blended);
+}
+
+TEST(FilterOperationsTest, BlendBlurWithNull) {
+ FilterOperation filter = FilterOperation::CreateBlurFilter(1.f);
+
+ FilterOperation blended = FilterOperation::Blend(&filter, NULL, 0.25);
+ FilterOperation expected = FilterOperation::CreateBlurFilter(0.75f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(NULL, &filter, 0.25);
+ expected = FilterOperation::CreateBlurFilter(0.25f);
+ EXPECT_EQ(expected, blended);
+}
+
+TEST(FilterOperationsTest, BlendDropShadowFilters) {
+ FilterOperation from = FilterOperation::CreateDropShadowFilter(
+ gfx::Point(0, 0), 2.f, SkColorSetARGB(15, 34, 68, 136));
+ FilterOperation to = FilterOperation::CreateDropShadowFilter(
+ gfx::Point(3, 5), 6.f, SkColorSetARGB(51, 30, 60, 120));
+
+ FilterOperation blended = FilterOperation::Blend(&from, &to, -0.75);
+ FilterOperation expected = FilterOperation::CreateDropShadowFilter(
+ gfx::Point(-2, -4), 0.f, SkColorSetARGB(0, 0, 0, 0));
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(&from, &to, 0.75);
+ expected = FilterOperation::CreateDropShadowFilter(
+ gfx::Point(2, 4), 5.f, SkColorSetARGB(42, 30, 61, 121));
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(&from, &to, 1.5);
+ expected = FilterOperation::CreateDropShadowFilter(
+ gfx::Point(5, 8), 8.f, SkColorSetARGB(69, 30, 59, 118));
+ EXPECT_EQ(expected, blended);
+}
+
+TEST(FilterOperationsTest, BlendDropShadowWithNull) {
+ FilterOperation filter = FilterOperation::CreateDropShadowFilter(
+ gfx::Point(4, 4), 4.f, SkColorSetARGB(255, 40, 0, 0));
+
+ FilterOperation blended = FilterOperation::Blend(&filter, NULL, 0.25);
+ FilterOperation expected = FilterOperation::CreateDropShadowFilter(
+ gfx::Point(3, 3), 3.f, SkColorSetARGB(191, 40, 0, 0));
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(NULL, &filter, 0.25);
+ expected = FilterOperation::CreateDropShadowFilter(
+ gfx::Point(1, 1), 1.f, SkColorSetARGB(64, 40, 0, 0));
+ EXPECT_EQ(expected, blended);
+}
+
+TEST(FilterOperationsTest, BlendZoomFilters) {
+ FilterOperation from = FilterOperation::CreateZoomFilter(2.f, 3);
+ FilterOperation to = FilterOperation::CreateZoomFilter(6.f, 0);
+
+ FilterOperation blended = FilterOperation::Blend(&from, &to, -0.75);
+ FilterOperation expected = FilterOperation::CreateZoomFilter(1.f, 5);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(&from, &to, 0.75);
+ expected = FilterOperation::CreateZoomFilter(5.f, 1);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(&from, &to, 1.5);
+ expected = FilterOperation::CreateZoomFilter(8.f, 0);
+ EXPECT_EQ(expected, blended);
+}
+
+TEST(FilterOperationsTest, BlendZoomWithNull) {
+ FilterOperation filter = FilterOperation::CreateZoomFilter(2.f, 1);
+
+ FilterOperation blended = FilterOperation::Blend(&filter, NULL, 0.25);
+ FilterOperation expected = FilterOperation::CreateZoomFilter(1.75f, 1);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(NULL, &filter, 0.25);
+ expected = FilterOperation::CreateZoomFilter(1.25f, 0);
+ EXPECT_EQ(expected, blended);
+}
+
+TEST(FilterOperationsTest, BlendSaturatingBrightnessFilters) {
+ FilterOperation from = FilterOperation::CreateSaturatingBrightnessFilter(3.f);
+ FilterOperation to = FilterOperation::CreateSaturatingBrightnessFilter(7.f);
+
+ FilterOperation blended = FilterOperation::Blend(&from, &to, -0.75);
+ FilterOperation expected =
+ FilterOperation::CreateSaturatingBrightnessFilter(0.f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(&from, &to, 0.75);
+ expected = FilterOperation::CreateSaturatingBrightnessFilter(6.f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(&from, &to, 1.5);
+ expected = FilterOperation::CreateSaturatingBrightnessFilter(9.f);
+ EXPECT_EQ(expected, blended);
+}
+
+TEST(FilterOperationsTest, BlendSaturatingBrightnessWithNull) {
+ FilterOperation filter =
+ FilterOperation::CreateSaturatingBrightnessFilter(1.f);
+
+ FilterOperation blended = FilterOperation::Blend(&filter, NULL, 0.25);
+ FilterOperation expected =
+ FilterOperation::CreateSaturatingBrightnessFilter(0.75f);
+ EXPECT_EQ(expected, blended);
+
+ blended = FilterOperation::Blend(NULL, &filter, 0.25);
+ expected = FilterOperation::CreateSaturatingBrightnessFilter(0.25f);
+ EXPECT_EQ(expected, blended);
+}
+
+// Tests blending non-empty sequences that have the same length and matching
+// operations.
+TEST(FilterOperationsTest, BlendMatchingSequences) {
+ FilterOperations from;
+ FilterOperations to;
+
+ from.Append(FilterOperation::CreateBlurFilter(0.f));
+ to.Append(FilterOperation::CreateBlurFilter(2.f));
+
+ from.Append(FilterOperation::CreateSaturateFilter(4.f));
+ to.Append(FilterOperation::CreateSaturateFilter(0.f));
+
+ from.Append(FilterOperation::CreateZoomFilter(2.0f, 1));
+ to.Append(FilterOperation::CreateZoomFilter(10.f, 9));
+
+ FilterOperations blended = to.Blend(from, -0.75);
+ FilterOperations expected;
+ expected.Append(FilterOperation::CreateBlurFilter(0.f));
+ expected.Append(FilterOperation::CreateSaturateFilter(7.f));
+ expected.Append(FilterOperation::CreateZoomFilter(1.f, 0));
+ EXPECT_EQ(blended, expected);
+
+ blended = to.Blend(from, 0.75);
+ expected.Clear();
+ expected.Append(FilterOperation::CreateBlurFilter(1.5f));
+ expected.Append(FilterOperation::CreateSaturateFilter(1.f));
+ expected.Append(FilterOperation::CreateZoomFilter(8.f, 7));
+ EXPECT_EQ(blended, expected);
+
+ blended = to.Blend(from, 1.5);
+ expected.Clear();
+ expected.Append(FilterOperation::CreateBlurFilter(3.f));
+ expected.Append(FilterOperation::CreateSaturateFilter(0.f));
+ expected.Append(FilterOperation::CreateZoomFilter(14.f, 13));
+ EXPECT_EQ(blended, expected);
+}
+
+TEST(FilterOperationsTest, BlendEmptyAndNonEmptySequences) {
+ FilterOperations empty;
+ FilterOperations filters;
+
+ filters.Append(FilterOperation::CreateGrayscaleFilter(0.75f));
+ filters.Append(FilterOperation::CreateBrightnessFilter(2.f));
+ filters.Append(FilterOperation::CreateHueRotateFilter(10.0f));
+
+ FilterOperations blended = empty.Blend(filters, -0.75);
+ FilterOperations expected;
+ expected.Append(FilterOperation::CreateGrayscaleFilter(1.f));
+ expected.Append(FilterOperation::CreateBrightnessFilter(2.75f));
+ expected.Append(FilterOperation::CreateHueRotateFilter(17.5f));
+ EXPECT_EQ(blended, expected);
+
+ blended = empty.Blend(filters, 0.75);
+ expected.Clear();
+ expected.Append(FilterOperation::CreateGrayscaleFilter(0.1875f));
+ expected.Append(FilterOperation::CreateBrightnessFilter(1.25f));
+ expected.Append(FilterOperation::CreateHueRotateFilter(2.5f));
+ EXPECT_EQ(blended, expected);
+
+ blended = empty.Blend(filters, 1.5);
+ expected.Clear();
+ expected.Append(FilterOperation::CreateGrayscaleFilter(0.f));
+ expected.Append(FilterOperation::CreateBrightnessFilter(0.5f));
+ expected.Append(FilterOperation::CreateHueRotateFilter(-5.f));
+ EXPECT_EQ(blended, expected);
+
+ blended = filters.Blend(empty, -0.75);
+ expected.Clear();
+ expected.Append(FilterOperation::CreateGrayscaleFilter(0.f));
+ expected.Append(FilterOperation::CreateBrightnessFilter(0.25f));
+ expected.Append(FilterOperation::CreateHueRotateFilter(-7.5f));
+ EXPECT_EQ(blended, expected);
+
+ blended = filters.Blend(empty, 0.75);
+ expected.Clear();
+ expected.Append(FilterOperation::CreateGrayscaleFilter(0.5625f));
+ expected.Append(FilterOperation::CreateBrightnessFilter(1.75f));
+ expected.Append(FilterOperation::CreateHueRotateFilter(7.5f));
+ EXPECT_EQ(blended, expected);
+
+ blended = filters.Blend(empty, 1.5);
+ expected.Clear();
+ expected.Append(FilterOperation::CreateGrayscaleFilter(1.f));
+ expected.Append(FilterOperation::CreateBrightnessFilter(2.5f));
+ expected.Append(FilterOperation::CreateHueRotateFilter(15.f));
+ EXPECT_EQ(blended, expected);
+}
+
+TEST(FilterOperationsTest, BlendEmptySequences) {
+ FilterOperations empty;
+
+ FilterOperations blended = empty.Blend(empty, -0.75);
+ EXPECT_EQ(blended, empty);
+
+ blended = empty.Blend(empty, 0.75);
+ EXPECT_EQ(blended, empty);
+
+ blended = empty.Blend(empty, 1.5);
+ EXPECT_EQ(blended, empty);
+}
+
+// Tests blending non-empty sequences that either have different lengths or
+// have non-matching operations.
+TEST(FilterOperationsTest, BlendNonMatchingSequences) {
+ FilterOperations from;
+ FilterOperations to;
+
+ from.Append(FilterOperation::CreateSaturateFilter(3.f));
+ from.Append(FilterOperation::CreateBlurFilter(2.f));
+ to.Append(FilterOperation::CreateSaturateFilter(4.f));
+
+ FilterOperations blended = to.Blend(from, -0.75);
+ EXPECT_EQ(to, blended);
+ blended = to.Blend(from, 0.75);
+ EXPECT_EQ(to, blended);
+ blended = to.Blend(from, 1.5);
+ EXPECT_EQ(to, blended);
+
+ to.Append(FilterOperation::CreateHueRotateFilter(0.5f));
+ blended = to.Blend(from, -0.75);
+ EXPECT_EQ(to, blended);
+ blended = to.Blend(from, 0.75);
+ EXPECT_EQ(to, blended);
+ blended = to.Blend(from, 1.5);
+ EXPECT_EQ(to, blended);
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/output/geometry_binding.cc b/chromium/cc/output/geometry_binding.cc
new file mode 100644
index 00000000000..3b177c2fe20
--- /dev/null
+++ b/chromium/cc/output/geometry_binding.cc
@@ -0,0 +1,122 @@
+// Copyright 2011 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 "cc/output/geometry_binding.h"
+
+#include "cc/output/gl_renderer.h" // For the GLC() macro.
+#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "ui/gfx/rect_f.h"
+
+namespace cc {
+
+GeometryBinding::GeometryBinding(WebKit::WebGraphicsContext3D* context,
+ const gfx::RectF& quad_vertex_rect)
+ : context_(context),
+ quad_vertices_vbo_(0),
+ quad_elements_vbo_(0) {
+ struct Vertex {
+ float a_position[3];
+ float a_texCoord[2];
+ // Index of the vertex, divide by 4 to have the matrix for this quad.
+ float a_index;
+ };
+ struct Quad {
+ Vertex v0, v1, v2, v3;
+ };
+ struct QuadIndex {
+ uint16 data[6];
+ };
+
+ COMPILE_ASSERT(
+ sizeof(Quad) == 24 * sizeof(float), // NOLINT(runtime/sizeof)
+ struct_is_densely_packed);
+ COMPILE_ASSERT(
+ sizeof(QuadIndex) == 6 * sizeof(uint16_t), // NOLINT(runtime/sizeof)
+ struct_is_densely_packed);
+
+ Quad quad_list[8];
+ QuadIndex quad_index_list[8];
+ for (int i = 0; i < 8; i++) {
+ Vertex v0 = { { quad_vertex_rect.x(), quad_vertex_rect.bottom(), 0.0f, },
+ { 0.0f, 1.0f, },
+ i * 4.0f + 0.0f };
+ Vertex v1 = { { quad_vertex_rect.x(), quad_vertex_rect.y(), 0.0f, },
+ { 0.0f, 0.0f, },
+ i * 4.0f + 1.0f };
+ Vertex v2 = { { quad_vertex_rect.right(), quad_vertex_rect.y(), 0.0f, },
+ { 1.0f, .0f, },
+ i * 4.0f + 2.0f };
+ Vertex v3 = { { quad_vertex_rect.right(),
+ quad_vertex_rect.bottom(),
+ 0.0f, },
+ { 1.0f, 1.0f, },
+ i * 4.0f + 3.0f };
+ Quad x = { v0, v1, v2, v3 };
+ quad_list[i] = x;
+ QuadIndex y = { { static_cast<uint16>(0 + 4 * i),
+ static_cast<uint16>(1 + 4 * i),
+ static_cast<uint16>(2 + 4 * i),
+ static_cast<uint16>(3 + 4 * i),
+ static_cast<uint16>(0 + 4 * i),
+ static_cast<uint16>(2 + 4 * i) } };
+ quad_index_list[i] = y;
+ }
+
+ GLC(context_, quad_vertices_vbo_ = context_->createBuffer());
+ GLC(context_, quad_elements_vbo_ = context_->createBuffer());
+ GLC(context_, context_->bindBuffer(GL_ARRAY_BUFFER, quad_vertices_vbo_));
+ GLC(context_,
+ context_->bufferData(
+ GL_ARRAY_BUFFER, sizeof(quad_list), quad_list, GL_STATIC_DRAW));
+ GLC(context_,
+ context_->bindBuffer(GL_ELEMENT_ARRAY_BUFFER, quad_elements_vbo_));
+ GLC(context_,
+ context_->bufferData(GL_ELEMENT_ARRAY_BUFFER,
+ sizeof(quad_index_list),
+ quad_index_list,
+ GL_STATIC_DRAW));
+}
+
+GeometryBinding::~GeometryBinding() {
+ GLC(context_, context_->deleteBuffer(quad_vertices_vbo_));
+ GLC(context_, context_->deleteBuffer(quad_elements_vbo_));
+}
+
+void GeometryBinding::PrepareForDraw() {
+ GLC(context_,
+ context_->bindBuffer(GL_ELEMENT_ARRAY_BUFFER, quad_elements_vbo_));
+
+ GLC(context_, context_->bindBuffer(GL_ARRAY_BUFFER, quad_vertices_vbo_));
+ GLC(context_,
+ context_->vertexAttribPointer(
+ PositionAttribLocation(),
+ 3,
+ GL_FLOAT,
+ false,
+ 6 * sizeof(float), // NOLINT(runtime/sizeof)
+ 0));
+ GLC(context_,
+ context_->vertexAttribPointer(
+ TexCoordAttribLocation(),
+ 2,
+ GL_FLOAT,
+ false,
+ 6 * sizeof(float), // NOLINT(runtime/sizeof)
+ 3 * sizeof(float))); // NOLINT(runtime/sizeof)
+ GLC(context_,
+ context_->vertexAttribPointer(
+ TriangleIndexAttribLocation(),
+ 1,
+ GL_FLOAT,
+ false,
+ 6 * sizeof(float), // NOLINT(runtime/sizeof)
+ 5 * sizeof(float))); // NOLINT(runtime/sizeof)
+ GLC(context_, context_->enableVertexAttribArray(PositionAttribLocation()));
+ GLC(context_, context_->enableVertexAttribArray(TexCoordAttribLocation()));
+ GLC(context_,
+ context_->enableVertexAttribArray(TriangleIndexAttribLocation()));
+}
+
+} // namespace cc
diff --git a/chromium/cc/output/geometry_binding.h b/chromium/cc/output/geometry_binding.h
new file mode 100644
index 00000000000..2318b27ecff
--- /dev/null
+++ b/chromium/cc/output/geometry_binding.h
@@ -0,0 +1,42 @@
+// Copyright 2011 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.
+
+#ifndef CC_OUTPUT_GEOMETRY_BINDING_H_
+#define CC_OUTPUT_GEOMETRY_BINDING_H_
+
+#include "base/basictypes.h"
+
+namespace gfx { class RectF; }
+
+namespace WebKit { class WebGraphicsContext3D; }
+
+namespace cc {
+
+class GeometryBinding {
+ public:
+ GeometryBinding(WebKit::WebGraphicsContext3D* context,
+ const gfx::RectF& quad_vertex_rect);
+ ~GeometryBinding();
+
+ void PrepareForDraw();
+
+ // All layer shaders share the same attribute locations for the vertex
+ // positions and texture coordinates. This allows switching shaders without
+ // rebinding attribute arrays.
+ static int PositionAttribLocation() { return 0; }
+ static int TexCoordAttribLocation() { return 1; }
+ static int TriangleIndexAttribLocation() { return 2; }
+
+ private:
+ WebKit::WebGraphicsContext3D* context_;
+
+ unsigned quad_vertices_vbo_;
+ unsigned quad_elements_vbo_;
+
+ DISALLOW_COPY_AND_ASSIGN(GeometryBinding);
+};
+
+} // namespace cc
+
+#endif // CC_OUTPUT_GEOMETRY_BINDING_H_
diff --git a/chromium/cc/output/gl_frame_data.cc b/chromium/cc/output/gl_frame_data.cc
new file mode 100644
index 00000000000..aa6cfbc2df1
--- /dev/null
+++ b/chromium/cc/output/gl_frame_data.cc
@@ -0,0 +1,13 @@
+// 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 "cc/output/gl_frame_data.h"
+
+namespace cc {
+
+GLFrameData::GLFrameData() : sync_point(0) {}
+
+GLFrameData::~GLFrameData() {}
+
+} // namespace cc
diff --git a/chromium/cc/output/gl_frame_data.h b/chromium/cc/output/gl_frame_data.h
new file mode 100644
index 00000000000..a8132f9f5b7
--- /dev/null
+++ b/chromium/cc/output/gl_frame_data.h
@@ -0,0 +1,31 @@
+// 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.
+
+#ifndef CC_OUTPUT_GL_FRAME_DATA_H_
+#define CC_OUTPUT_GL_FRAME_DATA_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "cc/base/cc_export.h"
+#include "gpu/command_buffer/common/mailbox.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size.h"
+
+namespace cc {
+
+class CC_EXPORT GLFrameData {
+ public:
+ GLFrameData();
+ ~GLFrameData();
+
+ gpu::Mailbox mailbox;
+ uint32 sync_point;
+ gfx::Size size;
+ gfx::Rect sub_buffer_rect;
+};
+
+} // namespace cc
+
+#endif // CC_OUTPUT_GL_FRAME_DATA_H_
diff --git a/chromium/cc/output/gl_renderer.cc b/chromium/cc/output/gl_renderer.cc
new file mode 100644
index 00000000000..61d0eaaafc4
--- /dev/null
+++ b/chromium/cc/output/gl_renderer.cc
@@ -0,0 +1,3165 @@
+// Copyright 2010 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 "cc/output/gl_renderer.h"
+
+#include <algorithm>
+#include <limits>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "build/build_config.h"
+#include "cc/base/math_util.h"
+#include "cc/layers/video_layer_impl.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/output/compositor_frame_metadata.h"
+#include "cc/output/context_provider.h"
+#include "cc/output/copy_output_request.h"
+#include "cc/output/geometry_binding.h"
+#include "cc/output/gl_frame_data.h"
+#include "cc/output/output_surface.h"
+#include "cc/output/render_surface_filters.h"
+#include "cc/quads/picture_draw_quad.h"
+#include "cc/quads/render_pass.h"
+#include "cc/quads/stream_video_draw_quad.h"
+#include "cc/quads/texture_draw_quad.h"
+#include "cc/resources/layer_quad.h"
+#include "cc/resources/scoped_resource.h"
+#include "cc/resources/sync_point_helper.h"
+#include "cc/trees/damage_tracker.h"
+#include "cc/trees/proxy.h"
+#include "cc/trees/single_thread_proxy.h"
+#include "gpu/GLES2/gl2extchromium.h"
+#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "third_party/khronos/GLES2/gl2ext.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/skia/include/core/SkColorFilter.h"
+#include "third_party/skia/include/core/SkSurface.h"
+#include "third_party/skia/include/gpu/GrContext.h"
+#include "third_party/skia/include/gpu/GrTexture.h"
+#include "third_party/skia/include/gpu/SkGpuDevice.h"
+#include "third_party/skia/include/gpu/SkGrTexturePixelRef.h"
+#include "third_party/skia/include/gpu/gl/GrGLInterface.h"
+#include "ui/gfx/quad_f.h"
+#include "ui/gfx/rect_conversions.h"
+
+using WebKit::WebGraphicsContext3D;
+
+namespace cc {
+
+namespace {
+
+// TODO(epenner): This should probably be moved to output surface.
+//
+// This implements a simple fence based on client side swaps.
+// This is to isolate the ResourceProvider from 'frames' which
+// it shouldn't need to care about, while still allowing us to
+// enforce good texture recycling behavior strictly throughout
+// the compositor (don't recycle a texture while it's in use).
+class SimpleSwapFence : public ResourceProvider::Fence {
+ public:
+ SimpleSwapFence() : has_passed_(false) {}
+ virtual bool HasPassed() OVERRIDE { return has_passed_; }
+ void SetHasPassed() { has_passed_ = true; }
+ private:
+ virtual ~SimpleSwapFence() {}
+ bool has_passed_;
+};
+
+bool NeedsIOSurfaceReadbackWorkaround() {
+#if defined(OS_MACOSX)
+ // This isn't strictly required in DumpRenderTree-mode when Mesa is used,
+ // but it doesn't seem to hurt.
+ return true;
+#else
+ return false;
+#endif
+}
+
+Float4 UVTransform(const TextureDrawQuad* quad) {
+ gfx::PointF uv0 = quad->uv_top_left;
+ gfx::PointF uv1 = quad->uv_bottom_right;
+ Float4 xform = { { uv0.x(), uv0.y(), uv1.x() - uv0.x(), uv1.y() - uv0.y() } };
+ if (quad->flipped) {
+ xform.data[1] = 1.0f - xform.data[1];
+ xform.data[3] = -xform.data[3];
+ }
+ return xform;
+}
+
+Float4 PremultipliedColor(SkColor color) {
+ const float factor = 1.0f / 255.0f;
+ const float alpha = SkColorGetA(color) * factor;
+
+ Float4 result = { {
+ SkColorGetR(color) * factor * alpha,
+ SkColorGetG(color) * factor * alpha,
+ SkColorGetB(color) * factor * alpha,
+ alpha
+ } };
+ return result;
+}
+
+// Smallest unit that impact anti-aliasing output. We use this to
+// determine when anti-aliasing is unnecessary.
+const float kAntiAliasingEpsilon = 1.0f / 1024.0f;
+
+} // anonymous namespace
+
+struct GLRenderer::PendingAsyncReadPixels {
+ PendingAsyncReadPixels() : buffer(0) {}
+
+ scoped_ptr<CopyOutputRequest> copy_request;
+ base::CancelableClosure finished_read_pixels_callback;
+ unsigned buffer;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PendingAsyncReadPixels);
+};
+
+scoped_ptr<GLRenderer> GLRenderer::Create(RendererClient* client,
+ OutputSurface* output_surface,
+ ResourceProvider* resource_provider,
+ int highp_threshold_min,
+ bool use_skia_gpu_backend) {
+ scoped_ptr<GLRenderer> renderer(new GLRenderer(
+ client, output_surface, resource_provider, highp_threshold_min));
+ if (!renderer->Initialize())
+ return scoped_ptr<GLRenderer>();
+ if (use_skia_gpu_backend) {
+ renderer->InitializeGrContext();
+ DCHECK(renderer->CanUseSkiaGPUBackend())
+ << "Requested Skia GPU backend, but can't use it.";
+ }
+
+ return renderer.Pass();
+}
+
+GLRenderer::GLRenderer(RendererClient* client,
+ OutputSurface* output_surface,
+ ResourceProvider* resource_provider,
+ int highp_threshold_min)
+ : DirectRenderer(client, output_surface, resource_provider),
+ offscreen_framebuffer_id_(0),
+ shared_geometry_quad_(gfx::RectF(-0.5f, -0.5f, 1.0f, 1.0f)),
+ context_(output_surface->context3d()),
+ is_backbuffer_discarded_(false),
+ discard_backbuffer_when_not_visible_(false),
+ is_using_bind_uniform_(false),
+ visible_(true),
+ is_scissor_enabled_(false),
+ stencil_shadow_(false),
+ blend_shadow_(false),
+ highp_threshold_min_(highp_threshold_min),
+ highp_threshold_cache_(0),
+ offscreen_context_labelled_(false),
+ on_demand_tile_raster_resource_id_(0),
+ weak_factory_(this) {
+ DCHECK(context_);
+}
+
+bool GLRenderer::Initialize() {
+ if (!context_->makeContextCurrent())
+ return false;
+
+ std::string unique_context_name = base::StringPrintf(
+ "%s-%p",
+ Settings().compositor_name.c_str(),
+ context_);
+ context_->pushGroupMarkerEXT(unique_context_name.c_str());
+
+ std::string extensions_string =
+ UTF16ToASCII(context_->getString(GL_EXTENSIONS));
+ std::vector<std::string> extensions_list;
+ base::SplitString(extensions_string, ' ', &extensions_list);
+ std::set<std::string> extensions(extensions_list.begin(),
+ extensions_list.end());
+
+ capabilities_.using_partial_swap =
+ Settings().partial_swap_enabled &&
+ extensions.count("GL_CHROMIUM_post_sub_buffer");
+
+ capabilities_.using_set_visibility =
+ extensions.count("GL_CHROMIUM_set_visibility") > 0;
+
+ if (extensions.count("GL_CHROMIUM_iosurface") > 0)
+ DCHECK_GT(extensions.count("GL_ARB_texture_rectangle"), 0u);
+
+ capabilities_.using_egl_image =
+ extensions.count("GL_OES_EGL_image_external") > 0;
+
+ capabilities_.max_texture_size = resource_provider_->max_texture_size();
+ capabilities_.best_texture_format = resource_provider_->best_texture_format();
+
+ // The updater can access textures while the GLRenderer is using them.
+ capabilities_.allow_partial_texture_updates = true;
+
+ // Check for texture fast paths. Currently we always use MO8 textures,
+ // so we only need to avoid POT textures if we have an NPOT fast-path.
+ capabilities_.avoid_pow2_textures =
+ extensions.count("GL_CHROMIUM_fast_NPOT_MO8_textures") > 0;
+
+ capabilities_.using_offscreen_context3d = true;
+
+ capabilities_.using_map_image =
+ extensions.count("GL_CHROMIUM_map_image") > 0 &&
+ Settings().use_map_image;
+
+ is_using_bind_uniform_ =
+ extensions.count("GL_CHROMIUM_bind_uniform_location") > 0;
+
+ if (!InitializeSharedObjects())
+ return false;
+
+ // Make sure the viewport and context gets initialized, even if it is to zero.
+ ViewportChanged();
+ return true;
+}
+
+void GLRenderer::InitializeGrContext() {
+ skia::RefPtr<GrGLInterface> interface = skia::AdoptRef(
+ context_->createGrGLInterface());
+ if (!interface)
+ return;
+
+ gr_context_ = skia::AdoptRef(GrContext::Create(
+ kOpenGL_GrBackend,
+ reinterpret_cast<GrBackendContext>(interface.get())));
+ ReinitializeGrCanvas();
+}
+
+GLRenderer::~GLRenderer() {
+ while (!pending_async_read_pixels_.empty()) {
+ PendingAsyncReadPixels* pending_read = pending_async_read_pixels_.back();
+ pending_read->finished_read_pixels_callback.Cancel();
+ pending_async_read_pixels_.pop_back();
+ }
+
+ context_->setMemoryAllocationChangedCallbackCHROMIUM(NULL);
+ CleanupSharedObjects();
+}
+
+const RendererCapabilities& GLRenderer::Capabilities() const {
+ return capabilities_;
+}
+
+WebGraphicsContext3D* GLRenderer::Context() { return context_; }
+
+void GLRenderer::DebugGLCall(WebGraphicsContext3D* context,
+ const char* command,
+ const char* file,
+ int line) {
+ unsigned error = context->getError();
+ if (error != GL_NO_ERROR)
+ LOG(ERROR) << "GL command failed: File: " << file << "\n\tLine " << line
+ << "\n\tcommand: " << command << ", error "
+ << static_cast<int>(error) << "\n";
+}
+
+void GLRenderer::SetVisible(bool visible) {
+ if (visible_ == visible)
+ return;
+ visible_ = visible;
+
+ EnforceMemoryPolicy();
+
+ // TODO(jamesr): Replace setVisibilityCHROMIUM() with an extension to
+ // explicitly manage front/backbuffers
+ // crbug.com/116049
+ if (capabilities_.using_set_visibility)
+ context_->setVisibilityCHROMIUM(visible);
+}
+
+void GLRenderer::SendManagedMemoryStats(size_t bytes_visible,
+ size_t bytes_visible_and_nearby,
+ size_t bytes_allocated) {
+ WebKit::WebGraphicsManagedMemoryStats stats;
+ stats.bytesVisible = bytes_visible;
+ stats.bytesVisibleAndNearby = bytes_visible_and_nearby;
+ stats.bytesAllocated = bytes_allocated;
+ stats.backbufferRequested = !is_backbuffer_discarded_;
+ context_->sendManagedMemoryStatsCHROMIUM(&stats);
+}
+
+void GLRenderer::ReleaseRenderPassTextures() { render_pass_textures_.clear(); }
+
+void GLRenderer::ViewportChanged() {
+ ReinitializeGrCanvas();
+}
+
+void GLRenderer::ClearFramebuffer(DrawingFrame* frame) {
+ // It's unsafe to clear when we have a stencil test because glClear ignores
+ // stencil.
+ if (client_->ExternalStencilTestEnabled() &&
+ frame->current_render_pass == frame->root_render_pass) {
+ DCHECK(!frame->current_render_pass->has_transparent_background);
+ return;
+ }
+
+ // On DEBUG builds, opaque render passes are cleared to blue to easily see
+ // regions that were not drawn on the screen.
+ if (frame->current_render_pass->has_transparent_background)
+ GLC(context_, context_->clearColor(0, 0, 0, 0));
+ else
+ GLC(context_, context_->clearColor(0, 0, 1, 1));
+
+ bool always_clear = false;
+#ifndef NDEBUG
+ always_clear = true;
+#endif
+ if (always_clear || frame->current_render_pass->has_transparent_background) {
+ GLbitfield clear_bits = GL_COLOR_BUFFER_BIT;
+ // Only the Skia GPU backend uses the stencil buffer. No need to clear it
+ // otherwise.
+ if (always_clear || CanUseSkiaGPUBackend()) {
+ GLC(context_, context_->clearStencil(0));
+ clear_bits |= GL_STENCIL_BUFFER_BIT;
+ }
+ context_->clear(clear_bits);
+ }
+}
+
+void GLRenderer::BeginDrawingFrame(DrawingFrame* frame) {
+ if (client_->DeviceViewport().IsEmpty())
+ return;
+
+ TRACE_EVENT0("cc", "GLRenderer::DrawLayers");
+
+ MakeContextCurrent();
+
+ ReinitializeGLState();
+}
+
+void GLRenderer::DoNoOp() {
+ GLC(context_, context_->bindFramebuffer(GL_FRAMEBUFFER, 0));
+ GLC(context_, context_->flush());
+}
+
+void GLRenderer::DoDrawQuad(DrawingFrame* frame, const DrawQuad* quad) {
+ DCHECK(quad->rect.Contains(quad->visible_rect));
+ if (quad->material != DrawQuad::TEXTURE_CONTENT) {
+ FlushTextureQuadCache();
+ }
+
+ switch (quad->material) {
+ case DrawQuad::INVALID:
+ NOTREACHED();
+ break;
+ case DrawQuad::CHECKERBOARD:
+ DrawCheckerboardQuad(frame, CheckerboardDrawQuad::MaterialCast(quad));
+ break;
+ case DrawQuad::DEBUG_BORDER:
+ DrawDebugBorderQuad(frame, DebugBorderDrawQuad::MaterialCast(quad));
+ break;
+ case DrawQuad::IO_SURFACE_CONTENT:
+ DrawIOSurfaceQuad(frame, IOSurfaceDrawQuad::MaterialCast(quad));
+ break;
+ case DrawQuad::PICTURE_CONTENT:
+ DrawPictureQuad(frame, PictureDrawQuad::MaterialCast(quad));
+ break;
+ case DrawQuad::RENDER_PASS:
+ DrawRenderPassQuad(frame, RenderPassDrawQuad::MaterialCast(quad));
+ break;
+ case DrawQuad::SOLID_COLOR:
+ DrawSolidColorQuad(frame, SolidColorDrawQuad::MaterialCast(quad));
+ break;
+ case DrawQuad::STREAM_VIDEO_CONTENT:
+ DrawStreamVideoQuad(frame, StreamVideoDrawQuad::MaterialCast(quad));
+ break;
+ case DrawQuad::TEXTURE_CONTENT:
+ EnqueueTextureQuad(frame, TextureDrawQuad::MaterialCast(quad));
+ break;
+ case DrawQuad::TILED_CONTENT:
+ DrawTileQuad(frame, TileDrawQuad::MaterialCast(quad));
+ break;
+ case DrawQuad::YUV_VIDEO_CONTENT:
+ DrawYUVVideoQuad(frame, YUVVideoDrawQuad::MaterialCast(quad));
+ break;
+ }
+}
+
+void GLRenderer::DrawCheckerboardQuad(const DrawingFrame* frame,
+ const CheckerboardDrawQuad* quad) {
+ SetBlendEnabled(quad->ShouldDrawWithBlending());
+
+ const TileCheckerboardProgram* program = GetTileCheckerboardProgram();
+ DCHECK(program && (program->initialized() || IsContextLost()));
+ SetUseProgram(program->program());
+
+ SkColor color = quad->color;
+ GLC(Context(),
+ Context()->uniform4f(program->fragment_shader().color_location(),
+ SkColorGetR(color) * (1.0f / 255.0f),
+ SkColorGetG(color) * (1.0f / 255.0f),
+ SkColorGetB(color) * (1.0f / 255.0f),
+ 1));
+
+ const int checkerboard_width = 16;
+ float frequency = 1.0f / checkerboard_width;
+
+ gfx::Rect tile_rect = quad->rect;
+ float tex_offset_x = tile_rect.x() % checkerboard_width;
+ float tex_offset_y = tile_rect.y() % checkerboard_width;
+ float tex_scale_x = tile_rect.width();
+ float tex_scale_y = tile_rect.height();
+ GLC(Context(),
+ Context()->uniform4f(program->fragment_shader().tex_transform_location(),
+ tex_offset_x,
+ tex_offset_y,
+ tex_scale_x,
+ tex_scale_y));
+
+ GLC(Context(),
+ Context()->uniform1f(program->fragment_shader().frequency_location(),
+ frequency));
+
+ SetShaderOpacity(quad->opacity(),
+ program->fragment_shader().alpha_location());
+ DrawQuadGeometry(frame,
+ quad->quadTransform(),
+ quad->rect,
+ program->vertex_shader().matrix_location());
+}
+
+void GLRenderer::DrawDebugBorderQuad(const DrawingFrame* frame,
+ const DebugBorderDrawQuad* quad) {
+ SetBlendEnabled(quad->ShouldDrawWithBlending());
+
+ static float gl_matrix[16];
+ const DebugBorderProgram* program = GetDebugBorderProgram();
+ DCHECK(program && (program->initialized() || IsContextLost()));
+ SetUseProgram(program->program());
+
+ // Use the full quad_rect for debug quads to not move the edges based on
+ // partial swaps.
+ gfx::Rect layer_rect = quad->rect;
+ gfx::Transform render_matrix = quad->quadTransform();
+ render_matrix.Translate(0.5f * layer_rect.width() + layer_rect.x(),
+ 0.5f * layer_rect.height() + layer_rect.y());
+ render_matrix.Scale(layer_rect.width(), layer_rect.height());
+ GLRenderer::ToGLMatrix(&gl_matrix[0],
+ frame->projection_matrix * render_matrix);
+ GLC(Context(),
+ Context()->uniformMatrix4fv(
+ program->vertex_shader().matrix_location(), 1, false, &gl_matrix[0]));
+
+ SkColor color = quad->color;
+ float alpha = SkColorGetA(color) * (1.0f / 255.0f);
+
+ GLC(Context(),
+ Context()->uniform4f(program->fragment_shader().color_location(),
+ (SkColorGetR(color) * (1.0f / 255.0f)) * alpha,
+ (SkColorGetG(color) * (1.0f / 255.0f)) * alpha,
+ (SkColorGetB(color) * (1.0f / 255.0f)) * alpha,
+ alpha));
+
+ GLC(Context(), Context()->lineWidth(quad->width));
+
+ // The indices for the line are stored in the same array as the triangle
+ // indices.
+ GLC(Context(),
+ Context()->drawElements(GL_LINE_LOOP, 4, GL_UNSIGNED_SHORT, 0));
+}
+
+static inline SkBitmap ApplyFilters(GLRenderer* renderer,
+ const FilterOperations& filters,
+ ScopedResource* source_texture_resource) {
+ if (filters.IsEmpty())
+ return SkBitmap();
+
+ ContextProvider* offscreen_contexts =
+ renderer->resource_provider()->offscreen_context_provider();
+ if (!offscreen_contexts || !offscreen_contexts->GrContext())
+ return SkBitmap();
+
+ ResourceProvider::ScopedWriteLockGL lock(renderer->resource_provider(),
+ source_texture_resource->id());
+
+ // Flush the compositor context to ensure that textures there are available
+ // in the shared context. Do this after locking/creating the compositor
+ // texture.
+ renderer->resource_provider()->Flush();
+
+ // Make sure skia uses the correct GL context.
+ offscreen_contexts->Context3d()->makeContextCurrent();
+
+ // Lazily label this context.
+ renderer->LazyLabelOffscreenContext();
+
+ SkBitmap source =
+ RenderSurfaceFilters::Apply(filters,
+ lock.texture_id(),
+ source_texture_resource->size(),
+ offscreen_contexts->GrContext());
+
+ // Flush skia context so that all the rendered stuff appears on the
+ // texture.
+ offscreen_contexts->GrContext()->flush();
+
+ // Flush the GL context so rendering results from this context are
+ // visible in the compositor's context.
+ offscreen_contexts->Context3d()->flush();
+
+ // Use the compositor's GL context again.
+ renderer->resource_provider()->GraphicsContext3D()->makeContextCurrent();
+ return source;
+}
+
+static SkBitmap ApplyImageFilter(GLRenderer* renderer,
+ SkImageFilter* filter,
+ ScopedResource* source_texture_resource) {
+ if (!filter)
+ return SkBitmap();
+
+ ContextProvider* offscreen_contexts =
+ renderer->resource_provider()->offscreen_context_provider();
+ if (!offscreen_contexts || !offscreen_contexts->GrContext())
+ return SkBitmap();
+
+ ResourceProvider::ScopedWriteLockGL lock(renderer->resource_provider(),
+ source_texture_resource->id());
+
+ // Flush the compositor context to ensure that textures there are available
+ // in the shared context. Do this after locking/creating the compositor
+ // texture.
+ renderer->resource_provider()->Flush();
+
+ // Make sure skia uses the correct GL context.
+ offscreen_contexts->Context3d()->makeContextCurrent();
+
+ // Lazily label this context.
+ renderer->LazyLabelOffscreenContext();
+
+ // Wrap the source texture in a Ganesh platform texture.
+ GrBackendTextureDesc backend_texture_description;
+ backend_texture_description.fWidth = source_texture_resource->size().width();
+ backend_texture_description.fHeight =
+ source_texture_resource->size().height();
+ backend_texture_description.fConfig = kSkia8888_GrPixelConfig;
+ backend_texture_description.fTextureHandle = lock.texture_id();
+ backend_texture_description.fOrigin = kBottomLeft_GrSurfaceOrigin;
+ skia::RefPtr<GrTexture> texture =
+ skia::AdoptRef(offscreen_contexts->GrContext()->wrapBackendTexture(
+ backend_texture_description));
+
+ // Place the platform texture inside an SkBitmap.
+ SkBitmap source;
+ source.setConfig(SkBitmap::kARGB_8888_Config,
+ source_texture_resource->size().width(),
+ source_texture_resource->size().height());
+ skia::RefPtr<SkGrPixelRef> pixel_ref =
+ skia::AdoptRef(new SkGrPixelRef(texture.get()));
+ source.setPixelRef(pixel_ref.get());
+
+ // Create a scratch texture for backing store.
+ GrTextureDesc desc;
+ desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
+ desc.fSampleCnt = 0;
+ desc.fWidth = source.width();
+ desc.fHeight = source.height();
+ desc.fConfig = kSkia8888_GrPixelConfig;
+ desc.fOrigin = kBottomLeft_GrSurfaceOrigin;
+ GrAutoScratchTexture scratch_texture(
+ offscreen_contexts->GrContext(), desc, GrContext::kExact_ScratchTexMatch);
+ skia::RefPtr<GrTexture> backing_store =
+ skia::AdoptRef(scratch_texture.detach());
+
+ // Create a device and canvas using that backing store.
+ SkGpuDevice device(offscreen_contexts->GrContext(), backing_store.get());
+ SkCanvas canvas(&device);
+
+ // Draw the source bitmap through the filter to the canvas.
+ SkPaint paint;
+ paint.setImageFilter(filter);
+ canvas.clear(SK_ColorTRANSPARENT);
+ canvas.drawSprite(source, 0, 0, &paint);
+
+ // Flush skia context so that all the rendered stuff appears on the
+ // texture.
+ offscreen_contexts->GrContext()->flush();
+
+ // Flush the GL context so rendering results from this context are
+ // visible in the compositor's context.
+ offscreen_contexts->Context3d()->flush();
+
+ // Use the compositor's GL context again.
+ renderer->resource_provider()->GraphicsContext3D()->makeContextCurrent();
+
+ return device.accessBitmap(false);
+}
+
+scoped_ptr<ScopedResource> GLRenderer::DrawBackgroundFilters(
+ DrawingFrame* frame,
+ const RenderPassDrawQuad* quad,
+ const gfx::Transform& contents_device_transform,
+ const gfx::Transform& contents_device_transform_inverse) {
+ // This method draws a background filter, which applies a filter to any pixels
+ // behind the quad and seen through its background. The algorithm works as
+ // follows:
+ // 1. Compute a bounding box around the pixels that will be visible through
+ // the quad.
+ // 2. Read the pixels in the bounding box into a buffer R.
+ // 3. Apply the background filter to R, so that it is applied in the pixels'
+ // coordinate space.
+ // 4. Apply the quad's inverse transform to map the pixels in R into the
+ // quad's content space. This implicitly clips R by the content bounds of the
+ // quad since the destination texture has bounds matching the quad's content.
+ // 5. Draw the background texture for the contents using the same transform as
+ // used to draw the contents itself. This is done without blending to replace
+ // the current background pixels with the new filtered background.
+ // 6. Draw the contents of the quad over drop of the new background with
+ // blending, as per usual. The filtered background pixels will show through
+ // any non-opaque pixels in this draws.
+ //
+ // Pixel copies in this algorithm occur at steps 2, 3, 4, and 5.
+
+ // TODO(danakj): When this algorithm changes, update
+ // LayerTreeHost::PrioritizeTextures() accordingly.
+
+ FilterOperations filters =
+ RenderSurfaceFilters::Optimize(quad->background_filters);
+ DCHECK(!filters.IsEmpty());
+
+ // TODO(danakj): We only allow background filters on an opaque render surface
+ // because other surfaces may contain translucent pixels, and the contents
+ // behind those translucent pixels wouldn't have the filter applied.
+ if (frame->current_render_pass->has_transparent_background)
+ return scoped_ptr<ScopedResource>();
+ DCHECK(!frame->current_texture);
+
+ // TODO(danakj): Do a single readback for both the surface and replica and
+ // cache the filtered results (once filter textures are not reused).
+ gfx::Rect window_rect = gfx::ToEnclosingRect(MathUtil::MapClippedRect(
+ contents_device_transform, SharedGeometryQuad().BoundingBox()));
+
+ int top, right, bottom, left;
+ filters.GetOutsets(&top, &right, &bottom, &left);
+ window_rect.Inset(-left, -top, -right, -bottom);
+
+ window_rect.Intersect(
+ MoveFromDrawToWindowSpace(frame->current_render_pass->output_rect));
+
+ scoped_ptr<ScopedResource> device_background_texture =
+ ScopedResource::create(resource_provider_);
+ if (!device_background_texture->Allocate(window_rect.size(),
+ GL_RGB,
+ ResourceProvider::TextureUsageAny)) {
+ return scoped_ptr<ScopedResource>();
+ } else {
+ ResourceProvider::ScopedWriteLockGL lock(resource_provider_,
+ device_background_texture->id());
+ GetFramebufferTexture(lock.texture_id(),
+ device_background_texture->format(),
+ window_rect);
+ }
+
+ SkBitmap filtered_device_background =
+ ApplyFilters(this, filters, device_background_texture.get());
+ if (!filtered_device_background.getTexture())
+ return scoped_ptr<ScopedResource>();
+
+ GrTexture* texture =
+ reinterpret_cast<GrTexture*>(filtered_device_background.getTexture());
+ int filtered_device_background_texture_id = texture->getTextureHandle();
+
+ scoped_ptr<ScopedResource> background_texture =
+ ScopedResource::create(resource_provider_);
+ if (!background_texture->Allocate(quad->rect.size(),
+ GL_RGBA,
+ ResourceProvider::TextureUsageFramebuffer))
+ return scoped_ptr<ScopedResource>();
+
+ const RenderPass* target_render_pass = frame->current_render_pass;
+ bool using_background_texture =
+ UseScopedTexture(frame, background_texture.get(), quad->rect);
+
+ if (using_background_texture) {
+ // Copy the readback pixels from device to the background texture for the
+ // surface.
+ gfx::Transform device_to_framebuffer_transform;
+ device_to_framebuffer_transform.Translate(
+ quad->rect.width() * 0.5f + quad->rect.x(),
+ quad->rect.height() * 0.5f + quad->rect.y());
+ device_to_framebuffer_transform.Scale(quad->rect.width(),
+ quad->rect.height());
+ device_to_framebuffer_transform.PreconcatTransform(
+ contents_device_transform_inverse);
+
+#ifndef NDEBUG
+ GLC(Context(), Context()->clearColor(0, 0, 1, 1));
+ Context()->clear(GL_COLOR_BUFFER_BIT);
+#endif
+
+ // The filtered_deveice_background_texture is oriented the same as the frame
+ // buffer. The transform we are copying with has a vertical flip, as well as
+ // the |device_to_framebuffer_transform|, which cancel each other out. So do
+ // not flip the contents in the shader to maintain orientation.
+ bool flip_vertically = false;
+
+ CopyTextureToFramebuffer(frame,
+ filtered_device_background_texture_id,
+ window_rect,
+ device_to_framebuffer_transform,
+ flip_vertically);
+ }
+
+ UseRenderPass(frame, target_render_pass);
+
+ if (!using_background_texture)
+ return scoped_ptr<ScopedResource>();
+ return background_texture.Pass();
+}
+
+void GLRenderer::DrawRenderPassQuad(DrawingFrame* frame,
+ const RenderPassDrawQuad* quad) {
+ SetBlendEnabled(quad->ShouldDrawWithBlending());
+
+ CachedResource* contents_texture =
+ render_pass_textures_.get(quad->render_pass_id);
+ if (!contents_texture || !contents_texture->id())
+ return;
+
+ gfx::Transform quad_rect_matrix;
+ QuadRectTransform(&quad_rect_matrix, quad->quadTransform(), quad->rect);
+ gfx::Transform contents_device_transform =
+ frame->window_matrix * frame->projection_matrix * quad_rect_matrix;
+ contents_device_transform.FlattenTo2d();
+
+ // Can only draw surface if device matrix is invertible.
+ gfx::Transform contents_device_transform_inverse(
+ gfx::Transform::kSkipInitialization);
+ if (!contents_device_transform.GetInverse(&contents_device_transform_inverse))
+ return;
+
+ scoped_ptr<ScopedResource> background_texture;
+ if (!quad->background_filters.IsEmpty()) {
+ // The pixels from the filtered background should completely replace the
+ // current pixel values.
+ bool disable_blending = blend_enabled();
+ if (disable_blending)
+ SetBlendEnabled(false);
+
+ background_texture = DrawBackgroundFilters(
+ frame,
+ quad,
+ contents_device_transform,
+ contents_device_transform_inverse);
+
+ if (disable_blending)
+ SetBlendEnabled(true);
+ }
+
+ // TODO(senorblanco): Cache this value so that we don't have to do it for both
+ // the surface and its replica. Apply filters to the contents texture.
+ SkBitmap filter_bitmap;
+ SkScalar color_matrix[20];
+ bool use_color_matrix = false;
+ if (quad->filter) {
+ skia::RefPtr<SkColorFilter> cf;
+
+ {
+ SkColorFilter* colorfilter_rawptr = NULL;
+ quad->filter->asColorFilter(&colorfilter_rawptr);
+ cf = skia::AdoptRef(colorfilter_rawptr);
+ }
+
+ if (cf && cf->asColorMatrix(color_matrix) && !quad->filter->getInput(0)) {
+ // We have a single color matrix as a filter; apply it locally
+ // in the compositor.
+ use_color_matrix = true;
+ } else {
+ filter_bitmap =
+ ApplyImageFilter(this, quad->filter.get(), contents_texture);
+ }
+ } else if (!quad->filters.IsEmpty()) {
+ FilterOperations optimized_filters =
+ RenderSurfaceFilters::Optimize(quad->filters);
+
+ if ((optimized_filters.size() == 1) &&
+ (optimized_filters.at(0).type() == FilterOperation::COLOR_MATRIX)) {
+ memcpy(
+ color_matrix, optimized_filters.at(0).matrix(), sizeof(color_matrix));
+ use_color_matrix = true;
+ } else {
+ filter_bitmap = ApplyFilters(this, optimized_filters, contents_texture);
+ }
+ }
+
+ // Draw the background texture if there is one.
+ if (background_texture) {
+ DCHECK(background_texture->size() == quad->rect.size());
+ ResourceProvider::ScopedReadLockGL lock(resource_provider_,
+ background_texture->id());
+
+ // The background_texture is oriented the same as the frame buffer. The
+ // transform we are copying with has a vertical flip, so flip the contents
+ // in the shader to maintain orientation
+ bool flip_vertically = true;
+
+ CopyTextureToFramebuffer(frame,
+ lock.texture_id(),
+ quad->rect,
+ quad->quadTransform(),
+ flip_vertically);
+ }
+
+ bool clipped = false;
+ gfx::QuadF device_quad = MathUtil::MapQuad(
+ contents_device_transform, SharedGeometryQuad(), &clipped);
+ LayerQuad device_layer_bounds(gfx::QuadF(device_quad.BoundingBox()));
+ LayerQuad device_layer_edges(device_quad);
+
+ // Use anti-aliasing programs only when necessary.
+ bool use_aa = !clipped &&
+ (!device_quad.IsRectilinear() ||
+ !gfx::IsNearestRectWithinDistance(device_quad.BoundingBox(),
+ kAntiAliasingEpsilon));
+ if (use_aa) {
+ device_layer_bounds.InflateAntiAliasingDistance();
+ device_layer_edges.InflateAntiAliasingDistance();
+ }
+
+ scoped_ptr<ResourceProvider::ScopedReadLockGL> mask_resource_lock;
+ unsigned mask_texture_id = 0;
+ if (quad->mask_resource_id) {
+ mask_resource_lock.reset(new ResourceProvider::ScopedReadLockGL(
+ resource_provider_, quad->mask_resource_id));
+ mask_texture_id = mask_resource_lock->texture_id();
+ }
+
+ // TODO(danakj): use the background_texture and blend the background in with
+ // this draw instead of having a separate copy of the background texture.
+
+ scoped_ptr<ResourceProvider::ScopedReadLockGL> contents_resource_lock;
+ if (filter_bitmap.getTexture()) {
+ GrTexture* texture =
+ reinterpret_cast<GrTexture*>(filter_bitmap.getTexture());
+ DCHECK_EQ(GL_TEXTURE0, ResourceProvider::GetActiveTextureUnit(Context()));
+ Context()->bindTexture(GL_TEXTURE_2D, texture->getTextureHandle());
+ } else {
+ contents_resource_lock = make_scoped_ptr(
+ new ResourceProvider::ScopedSamplerGL(resource_provider_,
+ contents_texture->id(),
+ GL_TEXTURE_2D,
+ GL_LINEAR));
+ }
+
+ TexCoordPrecision tex_coord_precision = TexCoordPrecisionRequired(
+ context_, &highp_threshold_cache_, highp_threshold_min_,
+ quad->shared_quad_state->visible_content_rect.bottom_right());
+
+ int shader_quad_location = -1;
+ int shader_edge_location = -1;
+ int shader_viewport_location = -1;
+ int shader_mask_sampler_location = -1;
+ int shader_mask_tex_coord_scale_location = -1;
+ int shader_mask_tex_coord_offset_location = -1;
+ int shader_matrix_location = -1;
+ int shader_alpha_location = -1;
+ int shader_color_matrix_location = -1;
+ int shader_color_offset_location = -1;
+ int shader_tex_transform_location = -1;
+
+ if (use_aa && mask_texture_id && !use_color_matrix) {
+ const RenderPassMaskProgramAA* program =
+ GetRenderPassMaskProgramAA(tex_coord_precision);
+ SetUseProgram(program->program());
+ GLC(Context(),
+ Context()->uniform1i(program->fragment_shader().sampler_location(), 0));
+
+ shader_quad_location = program->vertex_shader().quad_location();
+ shader_edge_location = program->vertex_shader().edge_location();
+ shader_viewport_location = program->vertex_shader().viewport_location();
+ shader_mask_sampler_location =
+ program->fragment_shader().mask_sampler_location();
+ shader_mask_tex_coord_scale_location =
+ program->fragment_shader().mask_tex_coord_scale_location();
+ shader_mask_tex_coord_offset_location =
+ program->fragment_shader().mask_tex_coord_offset_location();
+ shader_matrix_location = program->vertex_shader().matrix_location();
+ shader_alpha_location = program->fragment_shader().alpha_location();
+ shader_tex_transform_location =
+ program->vertex_shader().tex_transform_location();
+ } else if (!use_aa && mask_texture_id && !use_color_matrix) {
+ const RenderPassMaskProgram* program =
+ GetRenderPassMaskProgram(tex_coord_precision);
+ SetUseProgram(program->program());
+ GLC(Context(),
+ Context()->uniform1i(program->fragment_shader().sampler_location(), 0));
+
+ shader_mask_sampler_location =
+ program->fragment_shader().mask_sampler_location();
+ shader_mask_tex_coord_scale_location =
+ program->fragment_shader().mask_tex_coord_scale_location();
+ shader_mask_tex_coord_offset_location =
+ program->fragment_shader().mask_tex_coord_offset_location();
+ shader_matrix_location = program->vertex_shader().matrix_location();
+ shader_alpha_location = program->fragment_shader().alpha_location();
+ shader_tex_transform_location =
+ program->vertex_shader().tex_transform_location();
+ } else if (use_aa && !mask_texture_id && !use_color_matrix) {
+ const RenderPassProgramAA* program =
+ GetRenderPassProgramAA(tex_coord_precision);
+ SetUseProgram(program->program());
+ GLC(Context(),
+ Context()->uniform1i(program->fragment_shader().sampler_location(), 0));
+
+ shader_quad_location = program->vertex_shader().quad_location();
+ shader_edge_location = program->vertex_shader().edge_location();
+ shader_viewport_location = program->vertex_shader().viewport_location();
+ shader_matrix_location = program->vertex_shader().matrix_location();
+ shader_alpha_location = program->fragment_shader().alpha_location();
+ shader_tex_transform_location =
+ program->vertex_shader().tex_transform_location();
+ } else if (use_aa && mask_texture_id && use_color_matrix) {
+ const RenderPassMaskColorMatrixProgramAA* program =
+ GetRenderPassMaskColorMatrixProgramAA(tex_coord_precision);
+ SetUseProgram(program->program());
+ GLC(Context(),
+ Context()->uniform1i(program->fragment_shader().sampler_location(), 0));
+
+ shader_matrix_location = program->vertex_shader().matrix_location();
+ shader_quad_location = program->vertex_shader().quad_location();
+ shader_tex_transform_location =
+ program->vertex_shader().tex_transform_location();
+ shader_edge_location = program->vertex_shader().edge_location();
+ shader_viewport_location = program->vertex_shader().viewport_location();
+ shader_alpha_location = program->fragment_shader().alpha_location();
+ shader_mask_sampler_location =
+ program->fragment_shader().mask_sampler_location();
+ shader_mask_tex_coord_scale_location =
+ program->fragment_shader().mask_tex_coord_scale_location();
+ shader_mask_tex_coord_offset_location =
+ program->fragment_shader().mask_tex_coord_offset_location();
+ shader_color_matrix_location =
+ program->fragment_shader().color_matrix_location();
+ shader_color_offset_location =
+ program->fragment_shader().color_offset_location();
+ } else if (use_aa && !mask_texture_id && use_color_matrix) {
+ const RenderPassColorMatrixProgramAA* program =
+ GetRenderPassColorMatrixProgramAA(tex_coord_precision);
+ SetUseProgram(program->program());
+ GLC(Context(),
+ Context()->uniform1i(program->fragment_shader().sampler_location(), 0));
+
+ shader_matrix_location = program->vertex_shader().matrix_location();
+ shader_quad_location = program->vertex_shader().quad_location();
+ shader_tex_transform_location =
+ program->vertex_shader().tex_transform_location();
+ shader_edge_location = program->vertex_shader().edge_location();
+ shader_viewport_location = program->vertex_shader().viewport_location();
+ shader_alpha_location = program->fragment_shader().alpha_location();
+ shader_color_matrix_location =
+ program->fragment_shader().color_matrix_location();
+ shader_color_offset_location =
+ program->fragment_shader().color_offset_location();
+ } else if (!use_aa && mask_texture_id && use_color_matrix) {
+ const RenderPassMaskColorMatrixProgram* program =
+ GetRenderPassMaskColorMatrixProgram(tex_coord_precision);
+ SetUseProgram(program->program());
+ GLC(Context(),
+ Context()->uniform1i(program->fragment_shader().sampler_location(), 0));
+
+ shader_matrix_location = program->vertex_shader().matrix_location();
+ shader_tex_transform_location =
+ program->vertex_shader().tex_transform_location();
+ shader_mask_sampler_location =
+ program->fragment_shader().mask_sampler_location();
+ shader_mask_tex_coord_scale_location =
+ program->fragment_shader().mask_tex_coord_scale_location();
+ shader_mask_tex_coord_offset_location =
+ program->fragment_shader().mask_tex_coord_offset_location();
+ shader_alpha_location = program->fragment_shader().alpha_location();
+ shader_color_matrix_location =
+ program->fragment_shader().color_matrix_location();
+ shader_color_offset_location =
+ program->fragment_shader().color_offset_location();
+ } else if (!use_aa && !mask_texture_id && use_color_matrix) {
+ const RenderPassColorMatrixProgram* program =
+ GetRenderPassColorMatrixProgram(tex_coord_precision);
+ SetUseProgram(program->program());
+ GLC(Context(),
+ Context()->uniform1i(program->fragment_shader().sampler_location(), 0));
+
+ shader_matrix_location = program->vertex_shader().matrix_location();
+ shader_tex_transform_location =
+ program->vertex_shader().tex_transform_location();
+ shader_alpha_location = program->fragment_shader().alpha_location();
+ shader_color_matrix_location =
+ program->fragment_shader().color_matrix_location();
+ shader_color_offset_location =
+ program->fragment_shader().color_offset_location();
+ } else {
+ const RenderPassProgram* program =
+ GetRenderPassProgram(tex_coord_precision);
+ SetUseProgram(program->program());
+ GLC(Context(),
+ Context()->uniform1i(program->fragment_shader().sampler_location(), 0));
+
+ shader_matrix_location = program->vertex_shader().matrix_location();
+ shader_alpha_location = program->fragment_shader().alpha_location();
+ shader_tex_transform_location =
+ program->vertex_shader().tex_transform_location();
+ }
+ float tex_scale_x =
+ quad->rect.width() / static_cast<float>(contents_texture->size().width());
+ float tex_scale_y = quad->rect.height() /
+ static_cast<float>(contents_texture->size().height());
+ DCHECK_LE(tex_scale_x, 1.0f);
+ DCHECK_LE(tex_scale_y, 1.0f);
+
+ DCHECK(shader_tex_transform_location != -1 || IsContextLost());
+ // Flip the content vertically in the shader, as the RenderPass input
+ // texture is already oriented the same way as the framebuffer, but the
+ // projection transform does a flip.
+ GLC(Context(), Context()->uniform4f(shader_tex_transform_location,
+ 0.0f,
+ tex_scale_y,
+ tex_scale_x,
+ -tex_scale_y));
+
+ scoped_ptr<ResourceProvider::ScopedReadLockGL> shader_mask_sampler_lock;
+ if (shader_mask_sampler_location != -1) {
+ DCHECK_NE(shader_mask_tex_coord_scale_location, 1);
+ DCHECK_NE(shader_mask_tex_coord_offset_location, 1);
+ GLC(Context(), Context()->uniform1i(shader_mask_sampler_location, 1));
+
+ float mask_tex_scale_x = quad->mask_uv_rect.width() / tex_scale_x;
+ float mask_tex_scale_y = quad->mask_uv_rect.height() / tex_scale_y;
+
+ // Mask textures are oriented vertically flipped relative to the framebuffer
+ // and the RenderPass contents texture, so we flip the tex coords from the
+ // RenderPass texture to find the mask texture coords.
+ GLC(Context(),
+ Context()->uniform2f(shader_mask_tex_coord_offset_location,
+ quad->mask_uv_rect.x(),
+ quad->mask_uv_rect.y() + mask_tex_scale_y));
+ GLC(Context(),
+ Context()->uniform2f(shader_mask_tex_coord_scale_location,
+ mask_tex_scale_x,
+ -mask_tex_scale_y));
+ shader_mask_sampler_lock = make_scoped_ptr(
+ new ResourceProvider::ScopedSamplerGL(resource_provider_,
+ quad->mask_resource_id,
+ GL_TEXTURE_2D,
+ GL_TEXTURE1,
+ GL_LINEAR));
+ }
+
+ if (shader_edge_location != -1) {
+ float edge[24];
+ device_layer_edges.ToFloatArray(edge);
+ device_layer_bounds.ToFloatArray(&edge[12]);
+ GLC(Context(), Context()->uniform3fv(shader_edge_location, 8, edge));
+ }
+
+ if (shader_viewport_location != -1) {
+ float viewport[4] = {
+ static_cast<float>(viewport_.x()),
+ static_cast<float>(viewport_.y()),
+ static_cast<float>(viewport_.width()),
+ static_cast<float>(viewport_.height()),
+ };
+ GLC(Context(),
+ Context()->uniform4fv(shader_viewport_location, 1, viewport));
+ }
+
+ if (shader_color_matrix_location != -1) {
+ float matrix[16];
+ for (int i = 0; i < 4; ++i) {
+ for (int j = 0; j < 4; ++j)
+ matrix[i * 4 + j] = SkScalarToFloat(color_matrix[j * 5 + i]);
+ }
+ GLC(Context(),
+ Context()->uniformMatrix4fv(
+ shader_color_matrix_location, 1, false, matrix));
+ }
+ static const float kScale = 1.0f / 255.0f;
+ if (shader_color_offset_location != -1) {
+ float offset[4];
+ for (int i = 0; i < 4; ++i)
+ offset[i] = SkScalarToFloat(color_matrix[i * 5 + 4]) * kScale;
+
+ GLC(Context(),
+ Context()->uniform4fv(shader_color_offset_location, 1, offset));
+ }
+
+ // Map device space quad to surface space. contents_device_transform has no 3d
+ // component since it was flattened, so we don't need to project.
+ gfx::QuadF surface_quad = MathUtil::MapQuad(contents_device_transform_inverse,
+ device_layer_edges.ToQuadF(),
+ &clipped);
+
+ SetShaderOpacity(quad->opacity(), shader_alpha_location);
+ SetShaderQuadF(surface_quad, shader_quad_location);
+ DrawQuadGeometry(
+ frame, quad->quadTransform(), quad->rect, shader_matrix_location);
+
+ // Flush the compositor context before the filter bitmap goes out of
+ // scope, so the draw gets processed before the filter texture gets deleted.
+ if (filter_bitmap.getTexture())
+ context_->flush();
+}
+
+struct SolidColorProgramUniforms {
+ unsigned program;
+ unsigned matrix_location;
+ unsigned viewport_location;
+ unsigned quad_location;
+ unsigned edge_location;
+ unsigned color_location;
+};
+
+template<class T>
+static void SolidColorUniformLocation(T program,
+ SolidColorProgramUniforms* uniforms) {
+ uniforms->program = program->program();
+ uniforms->matrix_location = program->vertex_shader().matrix_location();
+ uniforms->viewport_location = program->vertex_shader().viewport_location();
+ uniforms->quad_location = program->vertex_shader().quad_location();
+ uniforms->edge_location = program->vertex_shader().edge_location();
+ uniforms->color_location = program->fragment_shader().color_location();
+}
+
+bool GLRenderer::SetupQuadForAntialiasing(
+ const gfx::Transform& device_transform,
+ const DrawQuad* quad,
+ gfx::QuadF* local_quad,
+ float edge[24]) const {
+ gfx::Rect tile_rect = quad->visible_rect;
+
+ bool clipped = false;
+ gfx::QuadF device_layer_quad = MathUtil::MapQuad(
+ device_transform, gfx::QuadF(quad->visibleContentRect()), &clipped);
+
+ bool is_axis_aligned_in_target = device_layer_quad.IsRectilinear();
+ bool is_nearest_rect_within_epsilon = is_axis_aligned_in_target &&
+ gfx::IsNearestRectWithinDistance(device_layer_quad.BoundingBox(),
+ kAntiAliasingEpsilon);
+
+ bool use_aa = Settings().allow_antialiasing &&
+ !clipped && // code can't handle clipped quads
+ !is_nearest_rect_within_epsilon &&
+ quad->IsEdge();
+ if (!use_aa)
+ return false;
+
+ LayerQuad device_layer_bounds(gfx::QuadF(device_layer_quad.BoundingBox()));
+ device_layer_bounds.InflateAntiAliasingDistance();
+
+ LayerQuad device_layer_edges(device_layer_quad);
+ device_layer_edges.InflateAntiAliasingDistance();
+
+ device_layer_edges.ToFloatArray(edge);
+ device_layer_bounds.ToFloatArray(&edge[12]);
+
+ gfx::PointF bottom_right = tile_rect.bottom_right();
+ gfx::PointF bottom_left = tile_rect.bottom_left();
+ gfx::PointF top_left = tile_rect.origin();
+ gfx::PointF top_right = tile_rect.top_right();
+
+ // Map points to device space.
+ bottom_right = MathUtil::MapPoint(device_transform, bottom_right, &clipped);
+ DCHECK(!clipped);
+ bottom_left = MathUtil::MapPoint(device_transform, bottom_left, &clipped);
+ DCHECK(!clipped);
+ top_left = MathUtil::MapPoint(device_transform, top_left, &clipped);
+ DCHECK(!clipped);
+ top_right = MathUtil::MapPoint(device_transform, top_right, &clipped);
+ DCHECK(!clipped);
+
+ LayerQuad::Edge bottom_edge(bottom_right, bottom_left);
+ LayerQuad::Edge left_edge(bottom_left, top_left);
+ LayerQuad::Edge top_edge(top_left, top_right);
+ LayerQuad::Edge right_edge(top_right, bottom_right);
+
+ // Only apply anti-aliasing to edges not clipped by culling or scissoring.
+ if (quad->IsTopEdge() && tile_rect.y() == quad->rect.y())
+ top_edge = device_layer_edges.top();
+ if (quad->IsLeftEdge() && tile_rect.x() == quad->rect.x())
+ left_edge = device_layer_edges.left();
+ if (quad->IsRightEdge() && tile_rect.right() == quad->rect.right())
+ right_edge = device_layer_edges.right();
+ if (quad->IsBottomEdge() && tile_rect.bottom() == quad->rect.bottom())
+ bottom_edge = device_layer_edges.bottom();
+
+ float sign = gfx::QuadF(tile_rect).IsCounterClockwise() ? -1 : 1;
+ bottom_edge.scale(sign);
+ left_edge.scale(sign);
+ top_edge.scale(sign);
+ right_edge.scale(sign);
+
+ // Create device space quad.
+ LayerQuad device_quad(left_edge, top_edge, right_edge, bottom_edge);
+
+ // Map device space quad to local space. device_transform has no 3d
+ // component since it was flattened, so we don't need to project. We should
+ // have already checked that the transform was uninvertible above.
+ gfx::Transform inverse_device_transform(
+ gfx::Transform::kSkipInitialization);
+ bool did_invert = device_transform.GetInverse(&inverse_device_transform);
+ DCHECK(did_invert);
+ *local_quad = MathUtil::MapQuad(
+ inverse_device_transform, device_quad.ToQuadF(), &clipped);
+ // We should not DCHECK(!clipped) here, because anti-aliasing inflation may
+ // cause device_quad to become clipped. To our knowledge this scenario does
+ // not need to be handled differently than the unclipped case.
+
+ return true;
+}
+
+void GLRenderer::DrawSolidColorQuad(const DrawingFrame* frame,
+ const SolidColorDrawQuad* quad) {
+ gfx::Rect tile_rect = quad->visible_rect;
+
+ SkColor color = quad->color;
+ float opacity = quad->opacity();
+ float alpha = (SkColorGetA(color) * (1.0f / 255.0f)) * opacity;
+
+ // Early out if alpha is small enough that quad doesn't contribute to output.
+ if (alpha < std::numeric_limits<float>::epsilon() &&
+ quad->ShouldDrawWithBlending())
+ return;
+
+ gfx::Transform device_transform =
+ frame->window_matrix * frame->projection_matrix * quad->quadTransform();
+ device_transform.FlattenTo2d();
+ if (!device_transform.IsInvertible())
+ return;
+
+ gfx::QuadF local_quad = gfx::QuadF(gfx::RectF(tile_rect));
+ float edge[24];
+ bool use_aa = !quad->force_anti_aliasing_off && SetupQuadForAntialiasing(
+ device_transform, quad, &local_quad, edge);
+
+ SolidColorProgramUniforms uniforms;
+ if (use_aa)
+ SolidColorUniformLocation(GetSolidColorProgramAA(), &uniforms);
+ else
+ SolidColorUniformLocation(GetSolidColorProgram(), &uniforms);
+ SetUseProgram(uniforms.program);
+
+ GLC(Context(),
+ Context()->uniform4f(uniforms.color_location,
+ (SkColorGetR(color) * (1.0f / 255.0f)) * alpha,
+ (SkColorGetG(color) * (1.0f / 255.0f)) * alpha,
+ (SkColorGetB(color) * (1.0f / 255.0f)) * alpha,
+ alpha));
+ if (use_aa) {
+ float viewport[4] = {
+ static_cast<float>(viewport_.x()),
+ static_cast<float>(viewport_.y()),
+ static_cast<float>(viewport_.width()),
+ static_cast<float>(viewport_.height()),
+ };
+ GLC(Context(),
+ Context()->uniform4fv(uniforms.viewport_location, 1, viewport));
+ GLC(Context(), Context()->uniform3fv(uniforms.edge_location, 8, edge));
+ }
+
+ // Enable blending when the quad properties require it or if we decided
+ // to use antialiasing.
+ SetBlendEnabled(quad->ShouldDrawWithBlending() || use_aa);
+
+ // Normalize to tile_rect.
+ local_quad.Scale(1.0f / tile_rect.width(), 1.0f / tile_rect.height());
+
+ SetShaderQuadF(local_quad, uniforms.quad_location);
+
+ // The transform and vertex data are used to figure out the extents that the
+ // un-antialiased quad should have and which vertex this is and the float
+ // quad passed in via uniform is the actual geometry that gets used to draw
+ // it. This is why this centered rect is used and not the original quad_rect.
+ gfx::RectF centered_rect(gfx::PointF(-0.5f * tile_rect.width(),
+ -0.5f * tile_rect.height()),
+ tile_rect.size());
+ DrawQuadGeometry(frame, quad->quadTransform(),
+ centered_rect, uniforms.matrix_location);
+}
+
+struct TileProgramUniforms {
+ unsigned program;
+ unsigned matrix_location;
+ unsigned viewport_location;
+ unsigned quad_location;
+ unsigned edge_location;
+ unsigned vertex_tex_transform_location;
+ unsigned sampler_location;
+ unsigned fragment_tex_transform_location;
+ unsigned alpha_location;
+};
+
+template <class T>
+static void TileUniformLocation(T program, TileProgramUniforms* uniforms) {
+ uniforms->program = program->program();
+ uniforms->matrix_location = program->vertex_shader().matrix_location();
+ uniforms->viewport_location = program->vertex_shader().viewport_location();
+ uniforms->quad_location = program->vertex_shader().quad_location();
+ uniforms->edge_location = program->vertex_shader().edge_location();
+ uniforms->vertex_tex_transform_location =
+ program->vertex_shader().vertex_tex_transform_location();
+
+ uniforms->sampler_location = program->fragment_shader().sampler_location();
+ uniforms->alpha_location = program->fragment_shader().alpha_location();
+ uniforms->fragment_tex_transform_location =
+ program->fragment_shader().fragment_tex_transform_location();
+}
+
+void GLRenderer::DrawTileQuad(const DrawingFrame* frame,
+ const TileDrawQuad* quad) {
+ DrawContentQuad(frame, quad, quad->resource_id);
+}
+
+void GLRenderer::DrawContentQuad(const DrawingFrame* frame,
+ const ContentDrawQuadBase* quad,
+ ResourceProvider::ResourceId resource_id) {
+ gfx::Rect tile_rect = quad->visible_rect;
+
+ gfx::RectF tex_coord_rect = quad->tex_coord_rect;
+ float tex_to_geom_scale_x = quad->rect.width() / tex_coord_rect.width();
+ float tex_to_geom_scale_y = quad->rect.height() / tex_coord_rect.height();
+
+ // tex_coord_rect corresponds to quad_rect, but quad_visible_rect may be
+ // smaller than quad_rect due to occlusion or clipping. Adjust
+ // tex_coord_rect to match.
+ gfx::Vector2d top_left_diff = tile_rect.origin() - quad->rect.origin();
+ gfx::Vector2d bottom_right_diff =
+ tile_rect.bottom_right() - quad->rect.bottom_right();
+ tex_coord_rect.Inset(top_left_diff.x() / tex_to_geom_scale_x,
+ top_left_diff.y() / tex_to_geom_scale_y,
+ -bottom_right_diff.x() / tex_to_geom_scale_x,
+ -bottom_right_diff.y() / tex_to_geom_scale_y);
+
+ gfx::RectF clamp_geom_rect(tile_rect);
+ gfx::RectF clamp_tex_rect(tex_coord_rect);
+ // Clamp texture coordinates to avoid sampling outside the layer
+ // by deflating the tile region half a texel or half a texel
+ // minus epsilon for one pixel layers. The resulting clamp region
+ // is mapped to the unit square by the vertex shader and mapped
+ // back to normalized texture coordinates by the fragment shader
+ // after being clamped to 0-1 range.
+ float tex_clamp_x = std::min(
+ 0.5f, 0.5f * clamp_tex_rect.width() - kAntiAliasingEpsilon);
+ float tex_clamp_y = std::min(
+ 0.5f, 0.5f * clamp_tex_rect.height() - kAntiAliasingEpsilon);
+ float geom_clamp_x = std::min(
+ tex_clamp_x * tex_to_geom_scale_x,
+ 0.5f * clamp_geom_rect.width() - kAntiAliasingEpsilon);
+ float geom_clamp_y = std::min(
+ tex_clamp_y * tex_to_geom_scale_y,
+ 0.5f * clamp_geom_rect.height() - kAntiAliasingEpsilon);
+ clamp_geom_rect.Inset(geom_clamp_x, geom_clamp_y, geom_clamp_x, geom_clamp_y);
+ clamp_tex_rect.Inset(tex_clamp_x, tex_clamp_y, tex_clamp_x, tex_clamp_y);
+
+ // Map clamping rectangle to unit square.
+ float vertex_tex_translate_x = -clamp_geom_rect.x() / clamp_geom_rect.width();
+ float vertex_tex_translate_y =
+ -clamp_geom_rect.y() / clamp_geom_rect.height();
+ float vertex_tex_scale_x = tile_rect.width() / clamp_geom_rect.width();
+ float vertex_tex_scale_y = tile_rect.height() / clamp_geom_rect.height();
+
+ TexCoordPrecision tex_coord_precision = TexCoordPrecisionRequired(
+ context_, &highp_threshold_cache_, highp_threshold_min_,
+ quad->texture_size);
+
+ // Map to normalized texture coordinates.
+ gfx::Size texture_size = quad->texture_size;
+ float fragment_tex_translate_x = clamp_tex_rect.x() / texture_size.width();
+ float fragment_tex_translate_y = clamp_tex_rect.y() / texture_size.height();
+ float fragment_tex_scale_x = clamp_tex_rect.width() / texture_size.width();
+ float fragment_tex_scale_y = clamp_tex_rect.height() / texture_size.height();
+
+ gfx::Transform device_transform =
+ frame->window_matrix * frame->projection_matrix * quad->quadTransform();
+ device_transform.FlattenTo2d();
+ if (!device_transform.IsInvertible())
+ return;
+
+ gfx::QuadF local_quad = gfx::QuadF(gfx::RectF(tile_rect));
+ float edge[24];
+ bool use_aa = SetupQuadForAntialiasing(
+ device_transform, quad, &local_quad, edge);
+
+ TileProgramUniforms uniforms;
+ if (use_aa) {
+ if (quad->swizzle_contents) {
+ TileUniformLocation(GetTileProgramSwizzleAA(tex_coord_precision),
+ &uniforms);
+ } else {
+ TileUniformLocation(GetTileProgramAA(tex_coord_precision), &uniforms);
+ }
+ } else {
+ if (quad->ShouldDrawWithBlending()) {
+ if (quad->swizzle_contents) {
+ TileUniformLocation(GetTileProgramSwizzle(tex_coord_precision),
+ &uniforms);
+ } else {
+ TileUniformLocation(GetTileProgram(tex_coord_precision), &uniforms);
+ }
+ } else {
+ if (quad->swizzle_contents) {
+ TileUniformLocation(GetTileProgramSwizzleOpaque(tex_coord_precision),
+ &uniforms);
+ } else {
+ TileUniformLocation(GetTileProgramOpaque(tex_coord_precision),
+ &uniforms);
+ }
+ }
+ }
+
+ SetUseProgram(uniforms.program);
+ GLC(Context(), Context()->uniform1i(uniforms.sampler_location, 0));
+ bool scaled = (tex_to_geom_scale_x != 1.f || tex_to_geom_scale_y != 1.f);
+ GLenum filter = (use_aa || scaled ||
+ !quad->quadTransform().IsIdentityOrIntegerTranslation())
+ ? GL_LINEAR
+ : GL_NEAREST;
+ ResourceProvider::ScopedSamplerGL quad_resource_lock(
+ resource_provider_, resource_id, GL_TEXTURE_2D, filter);
+
+ if (use_aa) {
+ float viewport[4] = {
+ static_cast<float>(viewport_.x()),
+ static_cast<float>(viewport_.y()),
+ static_cast<float>(viewport_.width()),
+ static_cast<float>(viewport_.height()),
+ };
+ GLC(Context(),
+ Context()->uniform4fv(uniforms.viewport_location, 1, viewport));
+ GLC(Context(), Context()->uniform3fv(uniforms.edge_location, 8, edge));
+
+ GLC(Context(),
+ Context()->uniform4f(uniforms.vertex_tex_transform_location,
+ vertex_tex_translate_x,
+ vertex_tex_translate_y,
+ vertex_tex_scale_x,
+ vertex_tex_scale_y));
+ GLC(Context(),
+ Context()->uniform4f(uniforms.fragment_tex_transform_location,
+ fragment_tex_translate_x,
+ fragment_tex_translate_y,
+ fragment_tex_scale_x,
+ fragment_tex_scale_y));
+ } else {
+ // Move fragment shader transform to vertex shader. We can do this while
+ // still producing correct results as fragment_tex_transform_location
+ // should always be non-negative when tiles are transformed in a way
+ // that could result in sampling outside the layer.
+ vertex_tex_scale_x *= fragment_tex_scale_x;
+ vertex_tex_scale_y *= fragment_tex_scale_y;
+ vertex_tex_translate_x *= fragment_tex_scale_x;
+ vertex_tex_translate_y *= fragment_tex_scale_y;
+ vertex_tex_translate_x += fragment_tex_translate_x;
+ vertex_tex_translate_y += fragment_tex_translate_y;
+
+ GLC(Context(),
+ Context()->uniform4f(uniforms.vertex_tex_transform_location,
+ vertex_tex_translate_x,
+ vertex_tex_translate_y,
+ vertex_tex_scale_x,
+ vertex_tex_scale_y));
+ }
+
+ // Enable blending when the quad properties require it or if we decided
+ // to use antialiasing.
+ SetBlendEnabled(quad->ShouldDrawWithBlending() || use_aa);
+
+ // Normalize to tile_rect.
+ local_quad.Scale(1.0f / tile_rect.width(), 1.0f / tile_rect.height());
+
+ SetShaderOpacity(quad->opacity(), uniforms.alpha_location);
+ SetShaderQuadF(local_quad, uniforms.quad_location);
+
+ // The transform and vertex data are used to figure out the extents that the
+ // un-antialiased quad should have and which vertex this is and the float
+ // quad passed in via uniform is the actual geometry that gets used to draw
+ // it. This is why this centered rect is used and not the original quad_rect.
+ gfx::RectF centered_rect(
+ gfx::PointF(-0.5f * tile_rect.width(), -0.5f * tile_rect.height()),
+ tile_rect.size());
+ DrawQuadGeometry(
+ frame, quad->quadTransform(), centered_rect, uniforms.matrix_location);
+}
+
+void GLRenderer::DrawYUVVideoQuad(const DrawingFrame* frame,
+ const YUVVideoDrawQuad* quad) {
+ SetBlendEnabled(quad->ShouldDrawWithBlending());
+
+ TexCoordPrecision tex_coord_precision = TexCoordPrecisionRequired(
+ context_, &highp_threshold_cache_, highp_threshold_min_,
+ quad->shared_quad_state->visible_content_rect.bottom_right());
+
+ bool use_alpha_plane = quad->a_plane_resource_id != 0;
+
+ ResourceProvider::ScopedSamplerGL y_plane_lock(
+ resource_provider_,
+ quad->y_plane_resource_id,
+ GL_TEXTURE_2D,
+ GL_TEXTURE1,
+ GL_LINEAR);
+ ResourceProvider::ScopedSamplerGL u_plane_lock(
+ resource_provider_,
+ quad->u_plane_resource_id,
+ GL_TEXTURE_2D,
+ GL_TEXTURE2,
+ GL_LINEAR);
+ ResourceProvider::ScopedSamplerGL v_plane_lock(
+ resource_provider_,
+ quad->v_plane_resource_id,
+ GL_TEXTURE_2D,
+ GL_TEXTURE3,
+ GL_LINEAR);
+ scoped_ptr<ResourceProvider::ScopedSamplerGL> a_plane_lock;
+ if (use_alpha_plane) {
+ a_plane_lock.reset(new ResourceProvider::ScopedSamplerGL(
+ resource_provider_,
+ quad->a_plane_resource_id,
+ GL_TEXTURE_2D,
+ GL_TEXTURE4,
+ GL_LINEAR));
+ }
+
+ int tex_scale_location = -1;
+ int matrix_location = -1;
+ int y_texture_location = -1;
+ int u_texture_location = -1;
+ int v_texture_location = -1;
+ int a_texture_location = -1;
+ int yuv_matrix_location = -1;
+ int yuv_adj_location = -1;
+ int alpha_location = -1;
+ if (use_alpha_plane) {
+ const VideoYUVAProgram* program = GetVideoYUVAProgram(tex_coord_precision);
+ DCHECK(program && (program->initialized() || IsContextLost()));
+ SetUseProgram(program->program());
+ tex_scale_location = program->vertex_shader().tex_scale_location();
+ matrix_location = program->vertex_shader().matrix_location();
+ y_texture_location = program->fragment_shader().y_texture_location();
+ u_texture_location = program->fragment_shader().u_texture_location();
+ v_texture_location = program->fragment_shader().v_texture_location();
+ a_texture_location = program->fragment_shader().a_texture_location();
+ yuv_matrix_location = program->fragment_shader().yuv_matrix_location();
+ yuv_adj_location = program->fragment_shader().yuv_adj_location();
+ alpha_location = program->fragment_shader().alpha_location();
+ } else {
+ const VideoYUVProgram* program = GetVideoYUVProgram(tex_coord_precision);
+ DCHECK(program && (program->initialized() || IsContextLost()));
+ SetUseProgram(program->program());
+ tex_scale_location = program->vertex_shader().tex_scale_location();
+ matrix_location = program->vertex_shader().matrix_location();
+ y_texture_location = program->fragment_shader().y_texture_location();
+ u_texture_location = program->fragment_shader().u_texture_location();
+ v_texture_location = program->fragment_shader().v_texture_location();
+ yuv_matrix_location = program->fragment_shader().yuv_matrix_location();
+ yuv_adj_location = program->fragment_shader().yuv_adj_location();
+ alpha_location = program->fragment_shader().alpha_location();
+ }
+
+ GLC(Context(),
+ Context()->uniform2f(tex_scale_location,
+ quad->tex_scale.width(),
+ quad->tex_scale.height()));
+ GLC(Context(), Context()->uniform1i(y_texture_location, 1));
+ GLC(Context(), Context()->uniform1i(u_texture_location, 2));
+ GLC(Context(), Context()->uniform1i(v_texture_location, 3));
+ if (use_alpha_plane)
+ GLC(Context(), Context()->uniform1i(a_texture_location, 4));
+
+ // These values are magic numbers that are used in the transformation from YUV
+ // to RGB color values. They are taken from the following webpage:
+ // http://www.fourcc.org/fccyvrgb.php
+ float yuv_to_rgb[9] = {
+ 1.164f, 1.164f, 1.164f,
+ 0.0f, -.391f, 2.018f,
+ 1.596f, -.813f, 0.0f,
+ };
+ GLC(Context(),
+ Context()->uniformMatrix3fv(yuv_matrix_location, 1, 0, yuv_to_rgb));
+
+ // These values map to 16, 128, and 128 respectively, and are computed
+ // as a fraction over 256 (e.g. 16 / 256 = 0.0625).
+ // They are used in the YUV to RGBA conversion formula:
+ // Y - 16 : Gives 16 values of head and footroom for overshooting
+ // U - 128 : Turns unsigned U into signed U [-128,127]
+ // V - 128 : Turns unsigned V into signed V [-128,127]
+ float yuv_adjust[3] = { -0.0625f, -0.5f, -0.5f, };
+ GLC(Context(), Context()->uniform3fv(yuv_adj_location, 1, yuv_adjust));
+
+
+ SetShaderOpacity(quad->opacity(), alpha_location);
+ DrawQuadGeometry(frame, quad->quadTransform(), quad->rect, matrix_location);
+}
+
+void GLRenderer::DrawStreamVideoQuad(const DrawingFrame* frame,
+ const StreamVideoDrawQuad* quad) {
+ SetBlendEnabled(quad->ShouldDrawWithBlending());
+
+ static float gl_matrix[16];
+
+ DCHECK(capabilities_.using_egl_image);
+
+ TexCoordPrecision tex_coord_precision = TexCoordPrecisionRequired(
+ context_, &highp_threshold_cache_, highp_threshold_min_,
+ quad->shared_quad_state->visible_content_rect.bottom_right());
+
+ const VideoStreamTextureProgram* program =
+ GetVideoStreamTextureProgram(tex_coord_precision);
+ SetUseProgram(program->program());
+
+ ToGLMatrix(&gl_matrix[0], quad->matrix);
+ GLC(Context(),
+ Context()->uniformMatrix4fv(
+ program->vertex_shader().tex_matrix_location(), 1, false, gl_matrix));
+
+ ResourceProvider::ScopedReadLockGL lock(resource_provider_,
+ quad->resource_id);
+ DCHECK_EQ(GL_TEXTURE0, ResourceProvider::GetActiveTextureUnit(Context()));
+ GLC(Context(),
+ Context()->bindTexture(GL_TEXTURE_EXTERNAL_OES, lock.texture_id()));
+
+ GLC(Context(),
+ Context()->uniform1i(program->fragment_shader().sampler_location(), 0));
+
+ SetShaderOpacity(quad->opacity(),
+ program->fragment_shader().alpha_location());
+ DrawQuadGeometry(frame,
+ quad->quadTransform(),
+ quad->rect,
+ program->vertex_shader().matrix_location());
+}
+
+void GLRenderer::DrawPictureQuadDirectToBackbuffer(
+ const DrawingFrame* frame,
+ const PictureDrawQuad* quad) {
+ DCHECK(CanUseSkiaGPUBackend());
+ DCHECK_EQ(quad->opacity(), 1.f) << "Need to composite to a bitmap or a "
+ "render surface for non-1 opacity quads";
+
+ // TODO(enne): This should be done more lazily / efficiently.
+ gr_context_->resetContext();
+
+ // Reset the canvas matrix to identity because the clip rect is in target
+ // space.
+ SkMatrix sk_identity;
+ sk_identity.setIdentity();
+ sk_canvas_->setMatrix(sk_identity);
+
+ if (is_scissor_enabled_) {
+ sk_canvas_->clipRect(gfx::RectToSkRect(scissor_rect_),
+ SkRegion::kReplace_Op);
+ } else {
+ sk_canvas_->clipRect(gfx::RectToSkRect(client_->DeviceViewport()),
+ SkRegion::kReplace_Op);
+ }
+
+ gfx::Transform contents_device_transform = frame->window_matrix *
+ frame->projection_matrix * quad->quadTransform();
+ contents_device_transform.Translate(quad->rect.x(),
+ quad->rect.y());
+ contents_device_transform.FlattenTo2d();
+ SkMatrix sk_device_matrix;
+ gfx::TransformToFlattenedSkMatrix(contents_device_transform,
+ &sk_device_matrix);
+ sk_canvas_->setMatrix(sk_device_matrix);
+
+ quad->picture_pile->RasterDirect(
+ sk_canvas_.get(), quad->content_rect, quad->contents_scale, NULL);
+
+ // Flush any drawing buffers that have been deferred.
+ sk_canvas_->flush();
+
+ // TODO(enne): This should be done more lazily / efficiently.
+ ReinitializeGLState();
+}
+
+void GLRenderer::DrawPictureQuad(const DrawingFrame* frame,
+ const PictureDrawQuad* quad) {
+ if (quad->can_draw_direct_to_backbuffer && CanUseSkiaGPUBackend()) {
+ DrawPictureQuadDirectToBackbuffer(frame, quad);
+ return;
+ }
+
+ if (on_demand_tile_raster_bitmap_.width() != quad->texture_size.width() ||
+ on_demand_tile_raster_bitmap_.height() != quad->texture_size.height()) {
+ on_demand_tile_raster_bitmap_.setConfig(
+ SkBitmap::kARGB_8888_Config,
+ quad->texture_size.width(),
+ quad->texture_size.height());
+ on_demand_tile_raster_bitmap_.allocPixels();
+
+ if (on_demand_tile_raster_resource_id_)
+ resource_provider_->DeleteResource(on_demand_tile_raster_resource_id_);
+
+ on_demand_tile_raster_resource_id_ = resource_provider_->CreateGLTexture(
+ quad->texture_size,
+ GL_RGBA,
+ GL_TEXTURE_POOL_UNMANAGED_CHROMIUM,
+ ResourceProvider::TextureUsageAny);
+ }
+
+ SkDevice device(on_demand_tile_raster_bitmap_);
+ SkCanvas canvas(&device);
+
+ quad->picture_pile->RasterToBitmap(&canvas, quad->content_rect,
+ quad->contents_scale, NULL);
+
+ resource_provider_->SetPixels(
+ on_demand_tile_raster_resource_id_,
+ reinterpret_cast<uint8_t*>(on_demand_tile_raster_bitmap_.getPixels()),
+ gfx::Rect(quad->texture_size),
+ gfx::Rect(quad->texture_size),
+ gfx::Vector2d());
+
+ DrawContentQuad(frame, quad, on_demand_tile_raster_resource_id_);
+}
+
+struct TextureProgramBinding {
+ template <class Program>
+ void Set(Program* program, WebKit::WebGraphicsContext3D* context) {
+ DCHECK(program && (program->initialized() || context->isContextLost()));
+ program_id = program->program();
+ sampler_location = program->fragment_shader().sampler_location();
+ matrix_location = program->vertex_shader().matrix_location();
+ background_color_location =
+ program->fragment_shader().background_color_location();
+ }
+ int program_id;
+ int sampler_location;
+ int matrix_location;
+ int background_color_location;
+};
+
+struct TexTransformTextureProgramBinding : TextureProgramBinding {
+ template <class Program>
+ void Set(Program* program, WebKit::WebGraphicsContext3D* context) {
+ TextureProgramBinding::Set(program, context);
+ tex_transform_location = program->vertex_shader().tex_transform_location();
+ vertex_opacity_location =
+ program->vertex_shader().vertex_opacity_location();
+ }
+ int tex_transform_location;
+ int vertex_opacity_location;
+};
+
+void GLRenderer::FlushTextureQuadCache() {
+ // Check to see if we have anything to draw.
+ if (draw_cache_.program_id == 0)
+ return;
+
+ // Set the correct blending mode.
+ SetBlendEnabled(draw_cache_.needs_blending);
+
+ // Bind the program to the GL state.
+ SetUseProgram(draw_cache_.program_id);
+
+ // Bind the correct texture sampler location.
+ GLC(Context(), Context()->uniform1i(draw_cache_.sampler_location, 0));
+
+ // Assume the current active textures is 0.
+ ResourceProvider::ScopedReadLockGL locked_quad(resource_provider_,
+ draw_cache_.resource_id);
+ DCHECK_EQ(GL_TEXTURE0, ResourceProvider::GetActiveTextureUnit(Context()));
+ GLC(Context(),
+ Context()->bindTexture(GL_TEXTURE_2D, locked_quad.texture_id()));
+
+ COMPILE_ASSERT(
+ sizeof(Float4) == 4 * sizeof(float), // NOLINT(runtime/sizeof)
+ struct_is_densely_packed);
+ COMPILE_ASSERT(
+ sizeof(Float16) == 16 * sizeof(float), // NOLINT(runtime/sizeof)
+ struct_is_densely_packed);
+
+ // Upload the tranforms for both points and uvs.
+ GLC(context_,
+ context_->uniformMatrix4fv(
+ static_cast<int>(draw_cache_.matrix_location),
+ static_cast<int>(draw_cache_.matrix_data.size()),
+ false,
+ reinterpret_cast<float*>(&draw_cache_.matrix_data.front())));
+ GLC(context_,
+ context_->uniform4fv(
+ static_cast<int>(draw_cache_.uv_xform_location),
+ static_cast<int>(draw_cache_.uv_xform_data.size()),
+ reinterpret_cast<float*>(&draw_cache_.uv_xform_data.front())));
+
+ if (draw_cache_.background_color != SK_ColorTRANSPARENT) {
+ Float4 background_color = PremultipliedColor(draw_cache_.background_color);
+ GLC(context_,
+ context_->uniform4fv(
+ draw_cache_.background_color_location, 1, background_color.data));
+ }
+
+ GLC(context_,
+ context_->uniform1fv(
+ static_cast<int>(draw_cache_.vertex_opacity_location),
+ static_cast<int>(draw_cache_.vertex_opacity_data.size()),
+ static_cast<float*>(&draw_cache_.vertex_opacity_data.front())));
+
+ // Draw the quads!
+ GLC(context_,
+ context_->drawElements(GL_TRIANGLES,
+ 6 * draw_cache_.matrix_data.size(),
+ GL_UNSIGNED_SHORT,
+ 0));
+
+ // Clear the cache.
+ draw_cache_.program_id = 0;
+ draw_cache_.uv_xform_data.resize(0);
+ draw_cache_.vertex_opacity_data.resize(0);
+ draw_cache_.matrix_data.resize(0);
+}
+
+void GLRenderer::EnqueueTextureQuad(const DrawingFrame* frame,
+ const TextureDrawQuad* quad) {
+ TexCoordPrecision tex_coord_precision = TexCoordPrecisionRequired(
+ context_, &highp_threshold_cache_, highp_threshold_min_,
+ quad->shared_quad_state->visible_content_rect.bottom_right());
+
+ // Choose the correct texture program binding
+ TexTransformTextureProgramBinding binding;
+ if (quad->premultiplied_alpha) {
+ if (quad->background_color == SK_ColorTRANSPARENT) {
+ binding.Set(GetTextureProgram(tex_coord_precision), Context());
+ } else {
+ binding.Set(GetTextureBackgroundProgram(tex_coord_precision), Context());
+ }
+ } else {
+ if (quad->background_color == SK_ColorTRANSPARENT) {
+ binding.Set(GetNonPremultipliedTextureProgram(tex_coord_precision),
+ Context());
+ } else {
+ binding.Set(
+ GetNonPremultipliedTextureBackgroundProgram(tex_coord_precision),
+ Context());
+ }
+ }
+
+ int resource_id = quad->resource_id;
+
+ if (draw_cache_.program_id != binding.program_id ||
+ draw_cache_.resource_id != resource_id ||
+ draw_cache_.needs_blending != quad->ShouldDrawWithBlending() ||
+ draw_cache_.background_color != quad->background_color ||
+ draw_cache_.matrix_data.size() >= 8) {
+ FlushTextureQuadCache();
+ draw_cache_.program_id = binding.program_id;
+ draw_cache_.resource_id = resource_id;
+ draw_cache_.needs_blending = quad->ShouldDrawWithBlending();
+ draw_cache_.background_color = quad->background_color;
+
+ draw_cache_.uv_xform_location = binding.tex_transform_location;
+ draw_cache_.background_color_location = binding.background_color_location;
+ draw_cache_.vertex_opacity_location = binding.vertex_opacity_location;
+ draw_cache_.matrix_location = binding.matrix_location;
+ draw_cache_.sampler_location = binding.sampler_location;
+ }
+
+ // Generate the uv-transform
+ draw_cache_.uv_xform_data.push_back(UVTransform(quad));
+
+ // Generate the vertex opacity
+ const float opacity = quad->opacity();
+ draw_cache_.vertex_opacity_data.push_back(quad->vertex_opacity[0] * opacity);
+ draw_cache_.vertex_opacity_data.push_back(quad->vertex_opacity[1] * opacity);
+ draw_cache_.vertex_opacity_data.push_back(quad->vertex_opacity[2] * opacity);
+ draw_cache_.vertex_opacity_data.push_back(quad->vertex_opacity[3] * opacity);
+
+ // Generate the transform matrix
+ gfx::Transform quad_rect_matrix;
+ QuadRectTransform(&quad_rect_matrix, quad->quadTransform(), quad->rect);
+ quad_rect_matrix = frame->projection_matrix * quad_rect_matrix;
+
+ Float16 m;
+ quad_rect_matrix.matrix().asColMajorf(m.data);
+ draw_cache_.matrix_data.push_back(m);
+}
+
+void GLRenderer::DrawIOSurfaceQuad(const DrawingFrame* frame,
+ const IOSurfaceDrawQuad* quad) {
+ SetBlendEnabled(quad->ShouldDrawWithBlending());
+
+ TexCoordPrecision tex_coord_precision = TexCoordPrecisionRequired(
+ context_, &highp_threshold_cache_, highp_threshold_min_,
+ quad->shared_quad_state->visible_content_rect.bottom_right());
+
+ TexTransformTextureProgramBinding binding;
+ binding.Set(GetTextureIOSurfaceProgram(tex_coord_precision), Context());
+
+ SetUseProgram(binding.program_id);
+ GLC(Context(), Context()->uniform1i(binding.sampler_location, 0));
+ if (quad->orientation == IOSurfaceDrawQuad::FLIPPED) {
+ GLC(Context(),
+ Context()->uniform4f(binding.tex_transform_location,
+ 0,
+ quad->io_surface_size.height(),
+ quad->io_surface_size.width(),
+ quad->io_surface_size.height() * -1.0f));
+ } else {
+ GLC(Context(),
+ Context()->uniform4f(binding.tex_transform_location,
+ 0,
+ 0,
+ quad->io_surface_size.width(),
+ quad->io_surface_size.height()));
+ }
+
+ const float vertex_opacity[] = { quad->opacity(), quad->opacity(),
+ quad->opacity(), quad->opacity() };
+ GLC(Context(),
+ Context()->uniform1fv(
+ binding.vertex_opacity_location, 4, vertex_opacity));
+
+ ResourceProvider::ScopedReadLockGL lock(resource_provider_,
+ quad->io_surface_resource_id);
+ DCHECK_EQ(GL_TEXTURE0, ResourceProvider::GetActiveTextureUnit(Context()));
+ GLC(Context(),
+ Context()->bindTexture(GL_TEXTURE_RECTANGLE_ARB,
+ lock.texture_id()));
+
+ DrawQuadGeometry(
+ frame, quad->quadTransform(), quad->rect, binding.matrix_location);
+
+ GLC(Context(), Context()->bindTexture(GL_TEXTURE_RECTANGLE_ARB, 0));
+}
+
+void GLRenderer::FinishDrawingFrame(DrawingFrame* frame) {
+ current_framebuffer_lock_.reset();
+ swap_buffer_rect_.Union(gfx::ToEnclosingRect(frame->root_damage_rect));
+
+ GLC(context_, context_->disable(GL_BLEND));
+ blend_shadow_ = false;
+}
+
+void GLRenderer::FinishDrawingQuadList() { FlushTextureQuadCache(); }
+
+bool GLRenderer::FlippedFramebuffer() const { return true; }
+
+void GLRenderer::EnsureScissorTestEnabled() {
+ if (is_scissor_enabled_)
+ return;
+
+ FlushTextureQuadCache();
+ GLC(context_, context_->enable(GL_SCISSOR_TEST));
+ is_scissor_enabled_ = true;
+}
+
+void GLRenderer::EnsureScissorTestDisabled() {
+ if (!is_scissor_enabled_)
+ return;
+
+ FlushTextureQuadCache();
+ GLC(context_, context_->disable(GL_SCISSOR_TEST));
+ is_scissor_enabled_ = false;
+}
+
+void GLRenderer::CopyCurrentRenderPassToBitmap(
+ DrawingFrame* frame,
+ scoped_ptr<CopyOutputRequest> request) {
+ gfx::Rect copy_rect = frame->current_render_pass->output_rect;
+ if (request->has_area()) {
+ // Intersect with the request's area, positioned with its origin at the
+ // origin of the full copy_rect.
+ copy_rect.Intersect(request->area() - copy_rect.OffsetFromOrigin());
+ }
+ GetFramebufferPixelsAsync(copy_rect, request.Pass());
+}
+
+void GLRenderer::ToGLMatrix(float* gl_matrix, const gfx::Transform& transform) {
+ transform.matrix().asColMajorf(gl_matrix);
+}
+
+void GLRenderer::SetShaderQuadF(const gfx::QuadF& quad, int quad_location) {
+ if (quad_location == -1)
+ return;
+
+ float gl_quad[8];
+ gl_quad[0] = quad.p1().x();
+ gl_quad[1] = quad.p1().y();
+ gl_quad[2] = quad.p2().x();
+ gl_quad[3] = quad.p2().y();
+ gl_quad[4] = quad.p3().x();
+ gl_quad[5] = quad.p3().y();
+ gl_quad[6] = quad.p4().x();
+ gl_quad[7] = quad.p4().y();
+ GLC(context_, context_->uniform2fv(quad_location, 4, gl_quad));
+}
+
+void GLRenderer::SetShaderOpacity(float opacity, int alpha_location) {
+ if (alpha_location != -1)
+ GLC(context_, context_->uniform1f(alpha_location, opacity));
+}
+
+void GLRenderer::SetStencilEnabled(bool enabled) {
+ if (enabled == stencil_shadow_)
+ return;
+
+ if (enabled)
+ GLC(context_, context_->enable(GL_STENCIL_TEST));
+ else
+ GLC(context_, context_->disable(GL_STENCIL_TEST));
+ stencil_shadow_ = enabled;
+}
+
+void GLRenderer::SetBlendEnabled(bool enabled) {
+ if (enabled == blend_shadow_)
+ return;
+
+ if (enabled)
+ GLC(context_, context_->enable(GL_BLEND));
+ else
+ GLC(context_, context_->disable(GL_BLEND));
+ blend_shadow_ = enabled;
+}
+
+void GLRenderer::SetUseProgram(unsigned program) {
+ if (program == program_shadow_)
+ return;
+ GLC(context_, context_->useProgram(program));
+ program_shadow_ = program;
+}
+
+void GLRenderer::DrawQuadGeometry(const DrawingFrame* frame,
+ const gfx::Transform& draw_transform,
+ const gfx::RectF& quad_rect,
+ int matrix_location) {
+ gfx::Transform quad_rect_matrix;
+ QuadRectTransform(&quad_rect_matrix, draw_transform, quad_rect);
+ static float gl_matrix[16];
+ ToGLMatrix(&gl_matrix[0], frame->projection_matrix * quad_rect_matrix);
+ GLC(context_,
+ context_->uniformMatrix4fv(matrix_location, 1, false, &gl_matrix[0]));
+
+ GLC(context_, context_->drawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0));
+}
+
+void GLRenderer::CopyTextureToFramebuffer(const DrawingFrame* frame,
+ int texture_id,
+ gfx::Rect rect,
+ const gfx::Transform& draw_matrix,
+ bool flip_vertically) {
+ TexCoordPrecision tex_coord_precision = TexCoordPrecisionRequired(
+ context_, &highp_threshold_cache_, highp_threshold_min_,
+ rect.bottom_right());
+
+ const RenderPassProgram* program = GetRenderPassProgram(tex_coord_precision);
+ SetUseProgram(program->program());
+
+ GLC(Context(), Context()->uniform1i(
+ program->fragment_shader().sampler_location(), 0));
+
+ if (flip_vertically) {
+ GLC(Context(), Context()->uniform4f(
+ program->vertex_shader().tex_transform_location(),
+ 0.f,
+ 1.f,
+ 1.f,
+ -1.f));
+ } else {
+ GLC(Context(), Context()->uniform4f(
+ program->vertex_shader().tex_transform_location(),
+ 0.f,
+ 0.f,
+ 1.f,
+ 1.f));
+ }
+
+ SetShaderOpacity(1.f, program->fragment_shader().alpha_location());
+ DCHECK_EQ(GL_TEXTURE0, ResourceProvider::GetActiveTextureUnit(Context()));
+ GLC(Context(), Context()->bindTexture(GL_TEXTURE_2D, texture_id));
+ DrawQuadGeometry(
+ frame, draw_matrix, rect, program->vertex_shader().matrix_location());
+}
+
+void GLRenderer::Finish() {
+ TRACE_EVENT0("cc", "GLRenderer::finish");
+ context_->finish();
+}
+
+void GLRenderer::SwapBuffers() {
+ DCHECK(visible_);
+ DCHECK(!is_backbuffer_discarded_);
+
+ TRACE_EVENT0("cc", "GLRenderer::SwapBuffers");
+ // We're done! Time to swapbuffers!
+
+ CompositorFrame compositor_frame;
+ compositor_frame.metadata = client_->MakeCompositorFrameMetadata();
+ compositor_frame.gl_frame_data = make_scoped_ptr(new GLFrameData);
+ compositor_frame.gl_frame_data->size = output_surface_->SurfaceSize();
+ if (capabilities_.using_partial_swap && client_->AllowPartialSwap()) {
+ // If supported, we can save significant bandwidth by only swapping the
+ // damaged/scissored region (clamped to the viewport)
+ swap_buffer_rect_.Intersect(client_->DeviceViewport());
+ int flipped_y_pos_of_rect_bottom =
+ client_->DeviceViewport().height() - swap_buffer_rect_.y() -
+ swap_buffer_rect_.height();
+ compositor_frame.gl_frame_data->sub_buffer_rect =
+ gfx::Rect(swap_buffer_rect_.x(),
+ flipped_y_pos_of_rect_bottom,
+ swap_buffer_rect_.width(),
+ swap_buffer_rect_.height());
+ } else {
+ compositor_frame.gl_frame_data->sub_buffer_rect =
+ gfx::Rect(output_surface_->SurfaceSize());
+ }
+ output_surface_->SwapBuffers(&compositor_frame);
+
+ swap_buffer_rect_ = gfx::Rect();
+
+ // We don't have real fences, so we mark read fences as passed
+ // assuming a double-buffered GPU pipeline. A texture can be
+ // written to after one full frame has past since it was last read.
+ if (last_swap_fence_.get())
+ static_cast<SimpleSwapFence*>(last_swap_fence_.get())->SetHasPassed();
+ last_swap_fence_ = resource_provider_->GetReadLockFence();
+ resource_provider_->SetReadLockFence(new SimpleSwapFence());
+}
+
+void GLRenderer::SetDiscardBackBufferWhenNotVisible(bool discard) {
+ discard_backbuffer_when_not_visible_ = discard;
+ EnforceMemoryPolicy();
+}
+
+void GLRenderer::EnforceMemoryPolicy() {
+ if (!visible_) {
+ TRACE_EVENT0("cc", "GLRenderer::EnforceMemoryPolicy dropping resources");
+ ReleaseRenderPassTextures();
+ if (discard_backbuffer_when_not_visible_)
+ DiscardBackbuffer();
+ resource_provider_->ReleaseCachedData();
+ GLC(context_, context_->flush());
+ }
+}
+
+void GLRenderer::DiscardBackbuffer() {
+ if (is_backbuffer_discarded_)
+ return;
+
+ output_surface_->DiscardBackbuffer();
+
+ is_backbuffer_discarded_ = true;
+
+ // Damage tracker needs a full reset every time framebuffer is discarded.
+ client_->SetFullRootLayerDamage();
+}
+
+void GLRenderer::EnsureBackbuffer() {
+ if (!is_backbuffer_discarded_)
+ return;
+
+ output_surface_->EnsureBackbuffer();
+ is_backbuffer_discarded_ = false;
+}
+
+void GLRenderer::GetFramebufferPixels(void* pixels, gfx::Rect rect) {
+ if (!pixels || rect.IsEmpty())
+ return;
+
+ // This function assumes that it is reading the root frame buffer.
+ DCHECK(!current_framebuffer_lock_);
+
+ scoped_ptr<PendingAsyncReadPixels> pending_read(new PendingAsyncReadPixels);
+ pending_async_read_pixels_.insert(pending_async_read_pixels_.begin(),
+ pending_read.Pass());
+
+ // This is a syncronous call since the callback is null.
+ gfx::Rect window_rect = MoveFromDrawToWindowSpace(rect);
+ DoGetFramebufferPixels(static_cast<uint8*>(pixels),
+ window_rect,
+ AsyncGetFramebufferPixelsCleanupCallback());
+}
+
+void GLRenderer::DeleteTextureReleaseCallbackOnImplThread(unsigned texture_id,
+ unsigned sync_point,
+ bool lost_resource) {
+ if (sync_point)
+ context_->waitSyncPoint(sync_point);
+ context_->deleteTexture(texture_id);
+}
+
+// static
+void GLRenderer::DeleteTextureReleaseCallback(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ base::WeakPtr<GLRenderer> gl_renderer,
+ unsigned texture_id,
+ unsigned sync_point,
+ bool lost_resource) {
+ task_runner->PostTask(
+ FROM_HERE,
+ base::Bind(&GLRenderer::DeleteTextureReleaseCallbackOnImplThread,
+ gl_renderer,
+ texture_id,
+ sync_point,
+ lost_resource));
+}
+
+void GLRenderer::GetFramebufferPixelsAsync(
+ gfx::Rect rect, scoped_ptr<CopyOutputRequest> request) {
+ DCHECK(!request->IsEmpty());
+ if (request->IsEmpty())
+ return;
+ if (rect.IsEmpty())
+ return;
+
+ DCHECK(gfx::Rect(current_surface_size_).Contains(rect)) <<
+ "current_surface_size_: " << current_surface_size_.ToString() <<
+ " rect: " << rect.ToString();
+
+ gfx::Rect window_rect = MoveFromDrawToWindowSpace(rect);
+
+ if (!request->force_bitmap_result()) {
+ unsigned int texture_id = context_->createTexture();
+ GLC(context_, context_->bindTexture(GL_TEXTURE_2D, texture_id));
+ GLC(context_, context_->texParameteri(
+ GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
+ GLC(context_, context_->texParameteri(
+ GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
+ GLC(context_, context_->texParameteri(
+ GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
+ GLC(context_, context_->texParameteri(
+ GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
+ GetFramebufferTexture(texture_id, GL_RGBA, window_rect);
+
+ gpu::Mailbox mailbox;
+ unsigned sync_point = 0;
+ GLC(context_, context_->genMailboxCHROMIUM(mailbox.name));
+ if (mailbox.IsZero()) {
+ context_->deleteTexture(texture_id);
+ request->SendEmptyResult();
+ return;
+ }
+
+ GLC(context_, context_->bindTexture(GL_TEXTURE_2D, texture_id));
+ GLC(context_, context_->produceTextureCHROMIUM(
+ GL_TEXTURE_2D, mailbox.name));
+ GLC(context_, context_->bindTexture(GL_TEXTURE_2D, 0));
+ sync_point = context_->insertSyncPoint();
+ scoped_ptr<TextureMailbox> texture_mailbox = make_scoped_ptr(
+ new TextureMailbox(mailbox,
+ base::Bind(&GLRenderer::DeleteTextureReleaseCallback,
+ base::MessageLoopProxy::current(),
+ weak_factory_.GetWeakPtr(),
+ texture_id),
+ GL_TEXTURE_2D,
+ sync_point));
+ request->SendTextureResult(window_rect.size(), texture_mailbox.Pass());
+ return;
+ }
+
+ DCHECK(request->force_bitmap_result());
+
+ scoped_ptr<SkBitmap> bitmap(new SkBitmap);
+ bitmap->setConfig(SkBitmap::kARGB_8888_Config,
+ window_rect.width(),
+ window_rect.height());
+ bitmap->allocPixels();
+
+ scoped_ptr<SkAutoLockPixels> lock(new SkAutoLockPixels(*bitmap));
+
+ // Save a pointer to the pixels, the bitmap is owned by the cleanup_callback.
+ uint8* pixels = static_cast<uint8*>(bitmap->getPixels());
+
+ AsyncGetFramebufferPixelsCleanupCallback cleanup_callback = base::Bind(
+ &GLRenderer::PassOnSkBitmap,
+ base::Unretained(this),
+ base::Passed(&bitmap),
+ base::Passed(&lock));
+
+ scoped_ptr<PendingAsyncReadPixels> pending_read(new PendingAsyncReadPixels);
+ pending_read->copy_request = request.Pass();
+ pending_async_read_pixels_.insert(pending_async_read_pixels_.begin(),
+ pending_read.Pass());
+
+ // This is an asyncronous call since the callback is not null.
+ DoGetFramebufferPixels(pixels, window_rect, cleanup_callback);
+}
+
+void GLRenderer::DoGetFramebufferPixels(
+ uint8* dest_pixels,
+ gfx::Rect window_rect,
+ const AsyncGetFramebufferPixelsCleanupCallback& cleanup_callback) {
+ DCHECK_GE(window_rect.x(), 0);
+ DCHECK_GE(window_rect.y(), 0);
+ DCHECK_LE(window_rect.right(), current_surface_size_.width());
+ DCHECK_LE(window_rect.bottom(), current_surface_size_.height());
+
+ bool is_async = !cleanup_callback.is_null();
+
+ MakeContextCurrent();
+
+ bool do_workaround = NeedsIOSurfaceReadbackWorkaround();
+
+ unsigned temporary_texture = 0;
+ unsigned temporary_fbo = 0;
+
+ if (do_workaround) {
+ // On Mac OS X, calling glReadPixels() against an FBO whose color attachment
+ // is an IOSurface-backed texture causes corruption of future glReadPixels()
+ // calls, even those on different OpenGL contexts. It is believed that this
+ // is the root cause of top crasher
+ // http://crbug.com/99393. <rdar://problem/10949687>
+
+ temporary_texture = context_->createTexture();
+ GLC(context_, context_->bindTexture(GL_TEXTURE_2D, temporary_texture));
+ GLC(context_, context_->texParameteri(
+ GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
+ GLC(context_, context_->texParameteri(
+ GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
+ GLC(context_, context_->texParameteri(
+ GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
+ GLC(context_, context_->texParameteri(
+ GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
+ // Copy the contents of the current (IOSurface-backed) framebuffer into a
+ // temporary texture.
+ GetFramebufferTexture(temporary_texture,
+ GL_RGBA,
+ gfx::Rect(current_surface_size_));
+ temporary_fbo = context_->createFramebuffer();
+ // Attach this texture to an FBO, and perform the readback from that FBO.
+ GLC(context_, context_->bindFramebuffer(GL_FRAMEBUFFER, temporary_fbo));
+ GLC(context_, context_->framebufferTexture2D(GL_FRAMEBUFFER,
+ GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_2D,
+ temporary_texture,
+ 0));
+
+ DCHECK_EQ(static_cast<unsigned>(GL_FRAMEBUFFER_COMPLETE),
+ context_->checkFramebufferStatus(GL_FRAMEBUFFER));
+ }
+
+ unsigned buffer = context_->createBuffer();
+ GLC(context_, context_->bindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM,
+ buffer));
+ GLC(context_, context_->bufferData(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM,
+ 4 * window_rect.size().GetArea(),
+ NULL,
+ GL_STREAM_READ));
+
+ WebKit::WebGLId query = 0;
+ if (is_async) {
+ query = context_->createQueryEXT();
+ GLC(context_, context_->beginQueryEXT(
+ GL_ASYNC_PIXEL_TRANSFERS_COMPLETED_CHROMIUM,
+ query));
+ }
+
+ GLC(context_,
+ context_->readPixels(window_rect.x(),
+ window_rect.y(),
+ window_rect.width(),
+ window_rect.height(),
+ GL_RGBA,
+ GL_UNSIGNED_BYTE,
+ NULL));
+
+ GLC(context_, context_->bindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM,
+ 0));
+
+ if (do_workaround) {
+ // Clean up.
+ GLC(context_, context_->bindFramebuffer(GL_FRAMEBUFFER, 0));
+ GLC(context_, context_->bindTexture(GL_TEXTURE_2D, 0));
+ GLC(context_, context_->deleteFramebuffer(temporary_fbo));
+ GLC(context_, context_->deleteTexture(temporary_texture));
+ }
+
+ base::Closure finished_callback =
+ base::Bind(&GLRenderer::FinishedReadback,
+ base::Unretained(this),
+ cleanup_callback,
+ buffer,
+ query,
+ dest_pixels,
+ window_rect.size());
+ // Save the finished_callback so it can be cancelled.
+ pending_async_read_pixels_.front()->finished_read_pixels_callback.Reset(
+ finished_callback);
+
+ // Save the buffer to verify the callbacks happen in the expected order.
+ pending_async_read_pixels_.front()->buffer = buffer;
+
+ if (is_async) {
+ GLC(context_, context_->endQueryEXT(
+ GL_ASYNC_PIXEL_TRANSFERS_COMPLETED_CHROMIUM));
+ SyncPointHelper::SignalQuery(
+ context_,
+ query,
+ finished_callback);
+ } else {
+ resource_provider_->Finish();
+ finished_callback.Run();
+ }
+
+ EnforceMemoryPolicy();
+}
+
+void GLRenderer::FinishedReadback(
+ const AsyncGetFramebufferPixelsCleanupCallback& cleanup_callback,
+ unsigned source_buffer,
+ unsigned query,
+ uint8* dest_pixels,
+ gfx::Size size) {
+ DCHECK(!pending_async_read_pixels_.empty());
+
+ if (query != 0) {
+ GLC(context_, context_->deleteQueryEXT(query));
+ }
+
+ PendingAsyncReadPixels* current_read = pending_async_read_pixels_.back();
+ // Make sure we service the readbacks in order.
+ DCHECK_EQ(source_buffer, current_read->buffer);
+
+ uint8* src_pixels = NULL;
+
+ if (source_buffer != 0) {
+ GLC(context_, context_->bindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM,
+ source_buffer));
+ src_pixels = static_cast<uint8*>(
+ context_->mapBufferCHROMIUM(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM,
+ GL_READ_ONLY));
+
+ if (src_pixels) {
+ size_t row_bytes = size.width() * 4;
+ int num_rows = size.height();
+ size_t total_bytes = num_rows * row_bytes;
+ for (size_t dest_y = 0; dest_y < total_bytes; dest_y += row_bytes) {
+ // Flip Y axis.
+ size_t src_y = total_bytes - dest_y - row_bytes;
+ // Swizzle OpenGL -> Skia byte order.
+ for (size_t x = 0; x < row_bytes; x += 4) {
+ dest_pixels[dest_y + x + SK_R32_SHIFT/8] = src_pixels[src_y + x + 0];
+ dest_pixels[dest_y + x + SK_G32_SHIFT/8] = src_pixels[src_y + x + 1];
+ dest_pixels[dest_y + x + SK_B32_SHIFT/8] = src_pixels[src_y + x + 2];
+ dest_pixels[dest_y + x + SK_A32_SHIFT/8] = src_pixels[src_y + x + 3];
+ }
+ }
+
+ GLC(context_, context_->unmapBufferCHROMIUM(
+ GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM));
+ }
+ GLC(context_, context_->bindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM,
+ 0));
+ GLC(context_, context_->deleteBuffer(source_buffer));
+ }
+
+ // TODO(danakj): This can go away when synchronous readback is no more and its
+ // contents can just move here.
+ if (!cleanup_callback.is_null())
+ cleanup_callback.Run(current_read->copy_request.Pass(), src_pixels != NULL);
+
+ pending_async_read_pixels_.pop_back();
+}
+
+void GLRenderer::PassOnSkBitmap(
+ scoped_ptr<SkBitmap> bitmap,
+ scoped_ptr<SkAutoLockPixels> lock,
+ scoped_ptr<CopyOutputRequest> request,
+ bool success) {
+ DCHECK(request->force_bitmap_result());
+
+ lock.reset();
+ if (success)
+ request->SendBitmapResult(bitmap.Pass());
+}
+
+void GLRenderer::GetFramebufferTexture(unsigned texture_id,
+ unsigned texture_format,
+ gfx::Rect window_rect) {
+ DCHECK(texture_id);
+ DCHECK_GE(window_rect.x(), 0);
+ DCHECK_GE(window_rect.y(), 0);
+ DCHECK_LE(window_rect.right(), current_surface_size_.width());
+ DCHECK_LE(window_rect.bottom(), current_surface_size_.height());
+
+ GLC(context_, context_->bindTexture(GL_TEXTURE_2D, texture_id));
+ GLC(context_,
+ context_->copyTexImage2D(GL_TEXTURE_2D,
+ 0,
+ texture_format,
+ window_rect.x(),
+ window_rect.y(),
+ window_rect.width(),
+ window_rect.height(),
+ 0));
+ GLC(context_, context_->bindTexture(GL_TEXTURE_2D, 0));
+}
+
+bool GLRenderer::UseScopedTexture(DrawingFrame* frame,
+ const ScopedResource* texture,
+ gfx::Rect viewport_rect) {
+ DCHECK(texture->id());
+ frame->current_render_pass = NULL;
+ frame->current_texture = texture;
+
+ return BindFramebufferToTexture(frame, texture, viewport_rect);
+}
+
+void GLRenderer::BindFramebufferToOutputSurface(DrawingFrame* frame) {
+ current_framebuffer_lock_.reset();
+ output_surface_->BindFramebuffer();
+
+ if (client_->ExternalStencilTestEnabled()) {
+ SetStencilEnabled(true);
+ GLC(context_, context_->stencilFunc(GL_EQUAL, 1, 1));
+ } else {
+ SetStencilEnabled(false);
+ }
+}
+
+bool GLRenderer::BindFramebufferToTexture(DrawingFrame* frame,
+ const ScopedResource* texture,
+ gfx::Rect target_rect) {
+ DCHECK(texture->id());
+
+ current_framebuffer_lock_.reset();
+
+ SetStencilEnabled(false);
+ GLC(context_,
+ context_->bindFramebuffer(GL_FRAMEBUFFER, offscreen_framebuffer_id_));
+ current_framebuffer_lock_ =
+ make_scoped_ptr(new ResourceProvider::ScopedWriteLockGL(
+ resource_provider_, texture->id()));
+ unsigned texture_id = current_framebuffer_lock_->texture_id();
+ GLC(context_,
+ context_->framebufferTexture2D(
+ GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_id, 0));
+
+ DCHECK(context_->checkFramebufferStatus(GL_FRAMEBUFFER) ==
+ GL_FRAMEBUFFER_COMPLETE || IsContextLost());
+
+ InitializeViewport(frame,
+ target_rect,
+ gfx::Rect(target_rect.size()),
+ target_rect.size());
+ return true;
+}
+
+void GLRenderer::SetScissorTestRect(gfx::Rect scissor_rect) {
+ EnsureScissorTestEnabled();
+
+ // Don't unnecessarily ask the context to change the scissor, because it
+ // may cause undesired GPU pipeline flushes.
+ if (scissor_rect == scissor_rect_)
+ return;
+
+ scissor_rect_ = scissor_rect;
+ FlushTextureQuadCache();
+ GLC(context_,
+ context_->scissor(scissor_rect.x(),
+ scissor_rect.y(),
+ scissor_rect.width(),
+ scissor_rect.height()));
+}
+
+void GLRenderer::SetDrawViewport(gfx::Rect window_space_viewport) {
+ viewport_ = window_space_viewport;
+ GLC(context_, context_->viewport(window_space_viewport.x(),
+ window_space_viewport.y(),
+ window_space_viewport.width(),
+ window_space_viewport.height()));
+}
+
+bool GLRenderer::MakeContextCurrent() { return context_->makeContextCurrent(); }
+
+bool GLRenderer::InitializeSharedObjects() {
+ TRACE_EVENT0("cc", "GLRenderer::InitializeSharedObjects");
+ MakeContextCurrent();
+
+ // Create an FBO for doing offscreen rendering.
+ GLC(context_, offscreen_framebuffer_id_ = context_->createFramebuffer());
+
+ // We will always need these programs to render, so create the programs
+ // eagerly so that the shader compilation can start while we do other work.
+ // Other programs are created lazily on first access.
+ shared_geometry_ = make_scoped_ptr(
+ new GeometryBinding(context_, QuadVertexRect()));
+ render_pass_program_ = make_scoped_ptr(
+ new RenderPassProgram(context_, TexCoordPrecisionMedium));
+ render_pass_program_highp_ = make_scoped_ptr(
+ new RenderPassProgram(context_, TexCoordPrecisionHigh));
+ tile_program_ = make_scoped_ptr(
+ new TileProgram(context_, TexCoordPrecisionMedium));
+ tile_program_opaque_ = make_scoped_ptr(
+ new TileProgramOpaque(context_, TexCoordPrecisionMedium));
+ tile_program_highp_ = make_scoped_ptr(
+ new TileProgram(context_, TexCoordPrecisionHigh));
+ tile_program_opaque_highp_ = make_scoped_ptr(
+ new TileProgramOpaque(context_, TexCoordPrecisionHigh));
+
+ GLC(context_, context_->flush());
+
+ return true;
+}
+
+const GLRenderer::TileCheckerboardProgram*
+GLRenderer::GetTileCheckerboardProgram() {
+ if (!tile_checkerboard_program_)
+ tile_checkerboard_program_ = make_scoped_ptr(
+ new TileCheckerboardProgram(context_, TexCoordPrecisionNA));
+ if (!tile_checkerboard_program_->initialized()) {
+ TRACE_EVENT0("cc", "GLRenderer::checkerboardProgram::initalize");
+ tile_checkerboard_program_->Initialize(context_, is_using_bind_uniform_);
+ }
+ return tile_checkerboard_program_.get();
+}
+
+const GLRenderer::DebugBorderProgram* GLRenderer::GetDebugBorderProgram() {
+ if (!debug_border_program_)
+ debug_border_program_ = make_scoped_ptr(
+ new DebugBorderProgram(context_, TexCoordPrecisionNA));
+ if (!debug_border_program_->initialized()) {
+ TRACE_EVENT0("cc", "GLRenderer::debugBorderProgram::initialize");
+ debug_border_program_->Initialize(context_, is_using_bind_uniform_);
+ }
+ return debug_border_program_.get();
+}
+
+const GLRenderer::SolidColorProgram* GLRenderer::GetSolidColorProgram() {
+ if (!solid_color_program_)
+ solid_color_program_ = make_scoped_ptr(
+ new SolidColorProgram(context_, TexCoordPrecisionNA));
+ if (!solid_color_program_->initialized()) {
+ TRACE_EVENT0("cc", "GLRenderer::solidColorProgram::initialize");
+ solid_color_program_->Initialize(context_, is_using_bind_uniform_);
+ }
+ return solid_color_program_.get();
+}
+
+const GLRenderer::SolidColorProgramAA* GLRenderer::GetSolidColorProgramAA() {
+ if (!solid_color_program_aa_) {
+ solid_color_program_aa_ =
+ make_scoped_ptr(new SolidColorProgramAA(context_, TexCoordPrecisionNA));
+ }
+ if (!solid_color_program_aa_->initialized()) {
+ TRACE_EVENT0("cc", "GLRenderer::solidColorProgramAA::initialize");
+ solid_color_program_aa_->Initialize(context_, is_using_bind_uniform_);
+ }
+ return solid_color_program_aa_.get();
+}
+
+const GLRenderer::RenderPassProgram* GLRenderer::GetRenderPassProgram(
+ TexCoordPrecision precision) {
+ scoped_ptr<RenderPassProgram>& program =
+ (precision == TexCoordPrecisionHigh) ? render_pass_program_highp_
+ : render_pass_program_;
+ DCHECK(program);
+ if (!program->initialized()) {
+ TRACE_EVENT0("cc", "GLRenderer::renderPassProgram::initialize");
+ program->Initialize(context_, is_using_bind_uniform_);
+ }
+ return program.get();
+}
+
+const GLRenderer::RenderPassProgramAA* GLRenderer::GetRenderPassProgramAA(
+ TexCoordPrecision precision) {
+ scoped_ptr<RenderPassProgramAA>& program =
+ (precision == TexCoordPrecisionHigh) ? render_pass_program_aa_highp_
+ : render_pass_program_aa_;
+ if (!program)
+ program =
+ make_scoped_ptr(new RenderPassProgramAA(context_, precision));
+ if (!program->initialized()) {
+ TRACE_EVENT0("cc", "GLRenderer::renderPassProgramAA::initialize");
+ program->Initialize(context_, is_using_bind_uniform_);
+ }
+ return program.get();
+}
+
+const GLRenderer::RenderPassMaskProgram*
+GLRenderer::GetRenderPassMaskProgram(TexCoordPrecision precision) {
+ scoped_ptr<RenderPassMaskProgram>& program =
+ (precision == TexCoordPrecisionHigh) ? render_pass_mask_program_highp_
+ : render_pass_mask_program_;
+ if (!program)
+ program = make_scoped_ptr(new RenderPassMaskProgram(context_, precision));
+ if (!program->initialized()) {
+ TRACE_EVENT0("cc", "GLRenderer::renderPassMaskProgram::initialize");
+ program->Initialize(context_, is_using_bind_uniform_);
+ }
+ return program.get();
+}
+
+const GLRenderer::RenderPassMaskProgramAA*
+GLRenderer::GetRenderPassMaskProgramAA(TexCoordPrecision precision) {
+ scoped_ptr<RenderPassMaskProgramAA>& program =
+ (precision == TexCoordPrecisionHigh) ? render_pass_mask_program_aa_highp_
+ : render_pass_mask_program_aa_;
+ if (!program)
+ program =
+ make_scoped_ptr(new RenderPassMaskProgramAA(context_, precision));
+ if (!program->initialized()) {
+ TRACE_EVENT0("cc", "GLRenderer::renderPassMaskProgramAA::initialize");
+ program->Initialize(context_, is_using_bind_uniform_);
+ }
+ return program.get();
+}
+
+const GLRenderer::RenderPassColorMatrixProgram*
+GLRenderer::GetRenderPassColorMatrixProgram(TexCoordPrecision precision) {
+ scoped_ptr<RenderPassColorMatrixProgram>& program =
+ (precision == TexCoordPrecisionHigh) ?
+ render_pass_color_matrix_program_highp_ :
+ render_pass_color_matrix_program_;
+ if (!program)
+ program = make_scoped_ptr(
+ new RenderPassColorMatrixProgram(context_, precision));
+ if (!program->initialized()) {
+ TRACE_EVENT0("cc", "GLRenderer::renderPassColorMatrixProgram::initialize");
+ program->Initialize(context_, is_using_bind_uniform_);
+ }
+ return program.get();
+}
+
+const GLRenderer::RenderPassColorMatrixProgramAA*
+GLRenderer::GetRenderPassColorMatrixProgramAA(TexCoordPrecision precision) {
+ scoped_ptr<RenderPassColorMatrixProgramAA>& program =
+ (precision == TexCoordPrecisionHigh) ?
+ render_pass_color_matrix_program_aa_highp_ :
+ render_pass_color_matrix_program_aa_;
+ if (!program)
+ program = make_scoped_ptr(
+ new RenderPassColorMatrixProgramAA(context_, precision));
+ if (!program->initialized()) {
+ TRACE_EVENT0("cc",
+ "GLRenderer::renderPassColorMatrixProgramAA::initialize");
+ program->Initialize(context_, is_using_bind_uniform_);
+ }
+ return program.get();
+}
+
+const GLRenderer::RenderPassMaskColorMatrixProgram*
+GLRenderer::GetRenderPassMaskColorMatrixProgram(TexCoordPrecision precision) {
+ scoped_ptr<RenderPassMaskColorMatrixProgram>& program =
+ (precision == TexCoordPrecisionHigh) ?
+ render_pass_mask_color_matrix_program_highp_ :
+ render_pass_mask_color_matrix_program_;
+ if (!program)
+ program = make_scoped_ptr(
+ new RenderPassMaskColorMatrixProgram(context_, precision));
+ if (!program->initialized()) {
+ TRACE_EVENT0("cc",
+ "GLRenderer::renderPassMaskColorMatrixProgram::initialize");
+ program->Initialize(context_, is_using_bind_uniform_);
+ }
+ return program.get();
+}
+
+const GLRenderer::RenderPassMaskColorMatrixProgramAA*
+GLRenderer::GetRenderPassMaskColorMatrixProgramAA(TexCoordPrecision precision) {
+ scoped_ptr<RenderPassMaskColorMatrixProgramAA>& program =
+ (precision == TexCoordPrecisionHigh) ?
+ render_pass_mask_color_matrix_program_aa_highp_ :
+ render_pass_mask_color_matrix_program_aa_;
+ if (!program)
+ program = make_scoped_ptr(
+ new RenderPassMaskColorMatrixProgramAA(context_, precision));
+ if (!program->initialized()) {
+ TRACE_EVENT0("cc",
+ "GLRenderer::renderPassMaskColorMatrixProgramAA::initialize");
+ program->Initialize(context_, is_using_bind_uniform_);
+ }
+ return program.get();
+}
+
+const GLRenderer::TileProgram* GLRenderer::GetTileProgram(
+ TexCoordPrecision precision) {
+ scoped_ptr<TileProgram>& program =
+ (precision == TexCoordPrecisionHigh) ? tile_program_highp_
+ : tile_program_;
+ DCHECK(program);
+ if (!program->initialized()) {
+ TRACE_EVENT0("cc", "GLRenderer::tileProgram::initialize");
+ program->Initialize(context_, is_using_bind_uniform_);
+ }
+ return program.get();
+}
+
+const GLRenderer::TileProgramOpaque* GLRenderer::GetTileProgramOpaque(
+ TexCoordPrecision precision) {
+ scoped_ptr<TileProgramOpaque>& program =
+ (precision == TexCoordPrecisionHigh) ? tile_program_opaque_highp_
+ : tile_program_opaque_;
+ DCHECK(program);
+ if (!program->initialized()) {
+ TRACE_EVENT0("cc", "GLRenderer::tileProgramOpaque::initialize");
+ program->Initialize(context_, is_using_bind_uniform_);
+ }
+ return program.get();
+}
+
+const GLRenderer::TileProgramAA* GLRenderer::GetTileProgramAA(
+ TexCoordPrecision precision) {
+ scoped_ptr<TileProgramAA>& program =
+ (precision == TexCoordPrecisionHigh) ? tile_program_aa_highp_
+ : tile_program_aa_;
+ if (!program)
+ program = make_scoped_ptr(new TileProgramAA(context_, precision));
+ if (!program->initialized()) {
+ TRACE_EVENT0("cc", "GLRenderer::tileProgramAA::initialize");
+ program->Initialize(context_, is_using_bind_uniform_);
+ }
+ return program.get();
+}
+
+const GLRenderer::TileProgramSwizzle* GLRenderer::GetTileProgramSwizzle(
+ TexCoordPrecision precision) {
+ scoped_ptr<TileProgramSwizzle>& program =
+ (precision == TexCoordPrecisionHigh) ? tile_program_swizzle_highp_
+ : tile_program_swizzle_;
+ if (!program)
+ program = make_scoped_ptr(new TileProgramSwizzle(context_, precision));
+ if (!program->initialized()) {
+ TRACE_EVENT0("cc", "GLRenderer::tileProgramSwizzle::initialize");
+ program->Initialize(context_, is_using_bind_uniform_);
+ }
+ return program.get();
+}
+
+const GLRenderer::TileProgramSwizzleOpaque*
+GLRenderer::GetTileProgramSwizzleOpaque(TexCoordPrecision precision) {
+ scoped_ptr<TileProgramSwizzleOpaque>& program =
+ (precision == TexCoordPrecisionHigh) ? tile_program_swizzle_opaque_highp_
+ : tile_program_swizzle_opaque_;
+ if (!program)
+ program = make_scoped_ptr(
+ new TileProgramSwizzleOpaque(context_, precision));
+ if (!program->initialized()) {
+ TRACE_EVENT0("cc", "GLRenderer::tileProgramSwizzleOpaque::initialize");
+ program->Initialize(context_, is_using_bind_uniform_);
+ }
+ return program.get();
+}
+
+const GLRenderer::TileProgramSwizzleAA* GLRenderer::GetTileProgramSwizzleAA(
+ TexCoordPrecision precision) {
+ scoped_ptr<TileProgramSwizzleAA>& program =
+ (precision == TexCoordPrecisionHigh) ? tile_program_swizzle_aa_highp_
+ : tile_program_swizzle_aa_;
+ if (!program)
+ program = make_scoped_ptr(new TileProgramSwizzleAA(context_, precision));
+ if (!program->initialized()) {
+ TRACE_EVENT0("cc", "GLRenderer::tileProgramSwizzleAA::initialize");
+ program->Initialize(context_, is_using_bind_uniform_);
+ }
+ return program.get();
+}
+
+const GLRenderer::TextureProgram* GLRenderer::GetTextureProgram(
+ TexCoordPrecision precision) {
+ scoped_ptr<TextureProgram>& program =
+ (precision == TexCoordPrecisionHigh) ? texture_program_highp_
+ : texture_program_;
+ if (!program)
+ program = make_scoped_ptr(new TextureProgram(context_, precision));
+ if (!program->initialized()) {
+ TRACE_EVENT0("cc", "GLRenderer::textureProgram::initialize");
+ program->Initialize(context_, is_using_bind_uniform_);
+ }
+ return program.get();
+}
+
+const GLRenderer::NonPremultipliedTextureProgram*
+ GLRenderer::GetNonPremultipliedTextureProgram(TexCoordPrecision precision) {
+ scoped_ptr<NonPremultipliedTextureProgram>& program =
+ (precision == TexCoordPrecisionHigh) ?
+ nonpremultiplied_texture_program_highp_ :
+ nonpremultiplied_texture_program_;
+ if (!program) {
+ program = make_scoped_ptr(
+ new NonPremultipliedTextureProgram(context_, precision));
+ }
+ if (!program->initialized()) {
+ TRACE_EVENT0("cc",
+ "GLRenderer::NonPremultipliedTextureProgram::Initialize");
+ program->Initialize(context_, is_using_bind_uniform_);
+ }
+ return program.get();
+}
+
+const GLRenderer::TextureBackgroundProgram*
+GLRenderer::GetTextureBackgroundProgram(TexCoordPrecision precision) {
+ scoped_ptr<TextureBackgroundProgram>& program =
+ (precision == TexCoordPrecisionHigh) ? texture_background_program_highp_
+ : texture_background_program_;
+ if (!program) {
+ program = make_scoped_ptr(
+ new TextureBackgroundProgram(context_, precision));
+ }
+ if (!program->initialized()) {
+ TRACE_EVENT0("cc", "GLRenderer::textureProgram::initialize");
+ program->Initialize(context_, is_using_bind_uniform_);
+ }
+ return program.get();
+}
+
+const GLRenderer::NonPremultipliedTextureBackgroundProgram*
+GLRenderer::GetNonPremultipliedTextureBackgroundProgram(
+ TexCoordPrecision precision) {
+ scoped_ptr<NonPremultipliedTextureBackgroundProgram>& program =
+ (precision == TexCoordPrecisionHigh) ?
+ nonpremultiplied_texture_background_program_highp_ :
+ nonpremultiplied_texture_background_program_;
+ if (!program) {
+ program = make_scoped_ptr(
+ new NonPremultipliedTextureBackgroundProgram(context_, precision));
+ }
+ if (!program->initialized()) {
+ TRACE_EVENT0("cc",
+ "GLRenderer::NonPremultipliedTextureProgram::Initialize");
+ program->Initialize(context_, is_using_bind_uniform_);
+ }
+ return program.get();
+}
+
+const GLRenderer::TextureIOSurfaceProgram*
+GLRenderer::GetTextureIOSurfaceProgram(TexCoordPrecision precision) {
+ scoped_ptr<TextureIOSurfaceProgram>& program =
+ (precision == TexCoordPrecisionHigh) ? texture_io_surface_program_highp_
+ : texture_io_surface_program_;
+ if (!program)
+ program =
+ make_scoped_ptr(new TextureIOSurfaceProgram(context_, precision));
+ if (!program->initialized()) {
+ TRACE_EVENT0("cc", "GLRenderer::textureIOSurfaceProgram::initialize");
+ program->Initialize(context_, is_using_bind_uniform_);
+ }
+ return program.get();
+}
+
+const GLRenderer::VideoYUVProgram* GLRenderer::GetVideoYUVProgram(
+ TexCoordPrecision precision) {
+ scoped_ptr<VideoYUVProgram>& program =
+ (precision == TexCoordPrecisionHigh) ? video_yuv_program_highp_
+ : video_yuv_program_;
+ if (!program)
+ program = make_scoped_ptr(new VideoYUVProgram(context_, precision));
+ if (!program->initialized()) {
+ TRACE_EVENT0("cc", "GLRenderer::videoYUVProgram::initialize");
+ program->Initialize(context_, is_using_bind_uniform_);
+ }
+ return program.get();
+}
+
+const GLRenderer::VideoYUVAProgram* GLRenderer::GetVideoYUVAProgram(
+ TexCoordPrecision precision) {
+ scoped_ptr<VideoYUVAProgram>& program =
+ (precision == TexCoordPrecisionHigh) ? video_yuva_program_highp_
+ : video_yuva_program_;
+ if (!program)
+ program = make_scoped_ptr(new VideoYUVAProgram(context_, precision));
+ if (!program->initialized()) {
+ TRACE_EVENT0("cc", "GLRenderer::videoYUVAProgram::initialize");
+ program->Initialize(context_, is_using_bind_uniform_);
+ }
+ return program.get();
+}
+
+const GLRenderer::VideoStreamTextureProgram*
+GLRenderer::GetVideoStreamTextureProgram(TexCoordPrecision precision) {
+ if (!Capabilities().using_egl_image)
+ return NULL;
+ scoped_ptr<VideoStreamTextureProgram>& program =
+ (precision == TexCoordPrecisionHigh) ? video_stream_texture_program_highp_
+ : video_stream_texture_program_;
+ if (!program)
+ program =
+ make_scoped_ptr(new VideoStreamTextureProgram(context_, precision));
+ if (!program->initialized()) {
+ TRACE_EVENT0("cc", "GLRenderer::streamTextureProgram::initialize");
+ program->Initialize(context_, is_using_bind_uniform_);
+ }
+ return program.get();
+}
+
+void GLRenderer::CleanupSharedObjects() {
+ MakeContextCurrent();
+
+ shared_geometry_.reset();
+
+ if (tile_program_)
+ tile_program_->Cleanup(context_);
+ if (tile_program_opaque_)
+ tile_program_opaque_->Cleanup(context_);
+ if (tile_program_swizzle_)
+ tile_program_swizzle_->Cleanup(context_);
+ if (tile_program_swizzle_opaque_)
+ tile_program_swizzle_opaque_->Cleanup(context_);
+ if (tile_program_aa_)
+ tile_program_aa_->Cleanup(context_);
+ if (tile_program_swizzle_aa_)
+ tile_program_swizzle_aa_->Cleanup(context_);
+ if (tile_checkerboard_program_)
+ tile_checkerboard_program_->Cleanup(context_);
+
+ if (tile_program_highp_)
+ tile_program_highp_->Cleanup(context_);
+ if (tile_program_opaque_highp_)
+ tile_program_opaque_highp_->Cleanup(context_);
+ if (tile_program_swizzle_highp_)
+ tile_program_swizzle_highp_->Cleanup(context_);
+ if (tile_program_swizzle_opaque_highp_)
+ tile_program_swizzle_opaque_highp_->Cleanup(context_);
+ if (tile_program_aa_highp_)
+ tile_program_aa_highp_->Cleanup(context_);
+ if (tile_program_swizzle_aa_highp_)
+ tile_program_swizzle_aa_highp_->Cleanup(context_);
+
+ if (render_pass_mask_program_)
+ render_pass_mask_program_->Cleanup(context_);
+ if (render_pass_program_)
+ render_pass_program_->Cleanup(context_);
+ if (render_pass_mask_program_aa_)
+ render_pass_mask_program_aa_->Cleanup(context_);
+ if (render_pass_program_aa_)
+ render_pass_program_aa_->Cleanup(context_);
+ if (render_pass_color_matrix_program_)
+ render_pass_color_matrix_program_->Cleanup(context_);
+ if (render_pass_mask_color_matrix_program_aa_)
+ render_pass_mask_color_matrix_program_aa_->Cleanup(context_);
+ if (render_pass_color_matrix_program_aa_)
+ render_pass_color_matrix_program_aa_->Cleanup(context_);
+ if (render_pass_mask_color_matrix_program_)
+ render_pass_mask_color_matrix_program_->Cleanup(context_);
+
+ if (render_pass_mask_program_highp_)
+ render_pass_mask_program_highp_->Cleanup(context_);
+ if (render_pass_program_highp_)
+ render_pass_program_highp_->Cleanup(context_);
+ if (render_pass_mask_program_aa_highp_)
+ render_pass_mask_program_aa_highp_->Cleanup(context_);
+ if (render_pass_program_aa_highp_)
+ render_pass_program_aa_highp_->Cleanup(context_);
+ if (render_pass_color_matrix_program_highp_)
+ render_pass_color_matrix_program_highp_->Cleanup(context_);
+ if (render_pass_mask_color_matrix_program_aa_highp_)
+ render_pass_mask_color_matrix_program_aa_highp_->Cleanup(context_);
+ if (render_pass_color_matrix_program_aa_highp_)
+ render_pass_color_matrix_program_aa_highp_->Cleanup(context_);
+ if (render_pass_mask_color_matrix_program_highp_)
+ render_pass_mask_color_matrix_program_highp_->Cleanup(context_);
+
+ if (texture_program_)
+ texture_program_->Cleanup(context_);
+ if (nonpremultiplied_texture_program_)
+ nonpremultiplied_texture_program_->Cleanup(context_);
+ if (texture_background_program_)
+ texture_background_program_->Cleanup(context_);
+ if (nonpremultiplied_texture_background_program_)
+ nonpremultiplied_texture_background_program_->Cleanup(context_);
+ if (texture_io_surface_program_)
+ texture_io_surface_program_->Cleanup(context_);
+
+ if (texture_program_highp_)
+ texture_program_highp_->Cleanup(context_);
+ if (nonpremultiplied_texture_program_highp_)
+ nonpremultiplied_texture_program_highp_->Cleanup(context_);
+ if (texture_background_program_highp_)
+ texture_background_program_highp_->Cleanup(context_);
+ if (nonpremultiplied_texture_background_program_highp_)
+ nonpremultiplied_texture_background_program_highp_->Cleanup(context_);
+ if (texture_io_surface_program_highp_)
+ texture_io_surface_program_highp_->Cleanup(context_);
+
+ if (video_yuv_program_)
+ video_yuv_program_->Cleanup(context_);
+ if (video_yuva_program_)
+ video_yuva_program_->Cleanup(context_);
+ if (video_stream_texture_program_)
+ video_stream_texture_program_->Cleanup(context_);
+
+ if (video_yuv_program_highp_)
+ video_yuv_program_highp_->Cleanup(context_);
+ if (video_yuva_program_highp_)
+ video_yuva_program_highp_->Cleanup(context_);
+ if (video_stream_texture_program_highp_)
+ video_stream_texture_program_highp_->Cleanup(context_);
+
+ if (debug_border_program_)
+ debug_border_program_->Cleanup(context_);
+ if (solid_color_program_)
+ solid_color_program_->Cleanup(context_);
+ if (solid_color_program_aa_)
+ solid_color_program_aa_->Cleanup(context_);
+
+ if (offscreen_framebuffer_id_)
+ GLC(context_, context_->deleteFramebuffer(offscreen_framebuffer_id_));
+
+ if (on_demand_tile_raster_resource_id_)
+ resource_provider_->DeleteResource(on_demand_tile_raster_resource_id_);
+
+ ReleaseRenderPassTextures();
+}
+
+void GLRenderer::ReinitializeGrCanvas() {
+ if (!CanUseSkiaGPUBackend())
+ return;
+
+ GrBackendRenderTargetDesc desc;
+ desc.fWidth = client_->DeviceViewport().width();
+ desc.fHeight = client_->DeviceViewport().height();
+ desc.fConfig = kRGBA_8888_GrPixelConfig;
+ desc.fOrigin = kTopLeft_GrSurfaceOrigin;
+ desc.fSampleCnt = 1;
+ desc.fStencilBits = 8;
+ desc.fRenderTargetHandle = 0;
+
+ skia::RefPtr<GrSurface> surface(
+ skia::AdoptRef(gr_context_->wrapBackendRenderTarget(desc)));
+ skia::RefPtr<SkDevice> device(
+ skia::AdoptRef(SkGpuDevice::Create(surface.get())));
+ sk_canvas_ = skia::AdoptRef(new SkCanvas(device.get()));
+}
+
+void GLRenderer::ReinitializeGLState() {
+ // Bind the common vertex attributes used for drawing all the layers.
+ shared_geometry_->PrepareForDraw();
+
+ GLC(context_, context_->disable(GL_DEPTH_TEST));
+ GLC(context_, context_->disable(GL_CULL_FACE));
+ GLC(context_, context_->colorMask(true, true, true, true));
+ GLC(context_, context_->disable(GL_STENCIL_TEST));
+ stencil_shadow_ = false;
+ GLC(context_, context_->enable(GL_BLEND));
+ blend_shadow_ = true;
+ GLC(context_, context_->blendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
+ GLC(context_, context_->activeTexture(GL_TEXTURE0));
+ program_shadow_ = 0;
+
+ // Make sure scissoring starts as disabled.
+ is_scissor_enabled_ = false;
+ GLC(context_, context_->disable(GL_SCISSOR_TEST));
+}
+
+bool GLRenderer::CanUseSkiaGPUBackend() const {
+ // The Skia GPU backend requires a stencil buffer. See ReinitializeGrCanvas
+ // implementation.
+ return gr_context_ && context_->getContextAttributes().stencil;
+}
+
+bool GLRenderer::IsContextLost() {
+ return (context_->getGraphicsResetStatusARB() != GL_NO_ERROR);
+}
+
+void GLRenderer::LazyLabelOffscreenContext() {
+ if (offscreen_context_labelled_)
+ return;
+ offscreen_context_labelled_ = true;
+ std::string unique_context_name = base::StringPrintf(
+ "%s-Offscreen-%p",
+ Settings().compositor_name.c_str(),
+ context_);
+ resource_provider()->offscreen_context_provider()->Context3d()->
+ pushGroupMarkerEXT(unique_context_name.c_str());
+}
+
+
+} // namespace cc
diff --git a/chromium/cc/output/gl_renderer.h b/chromium/cc/output/gl_renderer.h
new file mode 100644
index 00000000000..b1a3643b2ba
--- /dev/null
+++ b/chromium/cc/output/gl_renderer.h
@@ -0,0 +1,481 @@
+// Copyright 2010 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.
+
+#ifndef CC_OUTPUT_GL_RENDERER_H_
+#define CC_OUTPUT_GL_RENDERER_H_
+
+#include "base/cancelable_callback.h"
+#include "cc/base/cc_export.h"
+#include "cc/base/scoped_ptr_vector.h"
+#include "cc/output/direct_renderer.h"
+#include "cc/output/gl_renderer_draw_cache.h"
+#include "cc/output/program_binding.h"
+#include "cc/output/renderer.h"
+#include "cc/quads/checkerboard_draw_quad.h"
+#include "cc/quads/debug_border_draw_quad.h"
+#include "cc/quads/io_surface_draw_quad.h"
+#include "cc/quads/render_pass_draw_quad.h"
+#include "cc/quads/solid_color_draw_quad.h"
+#include "cc/quads/tile_draw_quad.h"
+#include "cc/quads/yuv_video_draw_quad.h"
+#include "ui/gfx/quad_f.h"
+
+class SkBitmap;
+
+namespace WebKit { class WebGraphicsContext3D; }
+
+namespace cc {
+
+class GLRendererShaderTest;
+class OutputSurface;
+class PictureDrawQuad;
+class ScopedResource;
+class StreamVideoDrawQuad;
+class TextureDrawQuad;
+class GeometryBinding;
+class ScopedEnsureFramebufferAllocation;
+
+// Class that handles drawing of composited render layers using GL.
+class CC_EXPORT GLRenderer : public DirectRenderer {
+ public:
+ static scoped_ptr<GLRenderer> Create(RendererClient* client,
+ OutputSurface* output_surface,
+ ResourceProvider* resource_provider,
+ int highp_threshold_min,
+ bool use_skia_gpu_backend);
+
+ virtual ~GLRenderer();
+
+ virtual const RendererCapabilities& Capabilities() const OVERRIDE;
+
+ WebKit::WebGraphicsContext3D* Context();
+
+ virtual void ViewportChanged() OVERRIDE;
+
+ // Waits for rendering to finish.
+ virtual void Finish() OVERRIDE;
+
+ virtual void DoNoOp() OVERRIDE;
+ virtual void SwapBuffers() OVERRIDE;
+
+ virtual void GetFramebufferPixels(void* pixels, gfx::Rect rect) OVERRIDE;
+
+ virtual bool IsContextLost() OVERRIDE;
+
+ virtual void SetVisible(bool visible) OVERRIDE;
+
+ virtual void SendManagedMemoryStats(size_t bytes_visible,
+ size_t bytes_visible_and_nearby,
+ size_t bytes_allocated) OVERRIDE;
+
+ virtual void SetDiscardBackBufferWhenNotVisible(bool discard) OVERRIDE;
+
+ static void DebugGLCall(WebKit::WebGraphicsContext3D* context,
+ const char* command,
+ const char* file,
+ int line);
+
+ bool CanUseSkiaGPUBackend() const;
+ void LazyLabelOffscreenContext();
+
+ protected:
+ GLRenderer(RendererClient* client,
+ OutputSurface* output_surface,
+ ResourceProvider* resource_provider,
+ int highp_threshold_min);
+
+ bool IsBackbufferDiscarded() const { return is_backbuffer_discarded_; }
+ bool Initialize();
+ void InitializeGrContext();
+
+ const gfx::QuadF& SharedGeometryQuad() const { return shared_geometry_quad_; }
+ const GeometryBinding* SharedGeometry() const {
+ return shared_geometry_.get();
+ }
+
+ void GetFramebufferPixelsAsync(gfx::Rect rect,
+ scoped_ptr<CopyOutputRequest> request);
+ void GetFramebufferTexture(unsigned texture_id,
+ unsigned texture_format,
+ gfx::Rect device_rect);
+ void ReleaseRenderPassTextures();
+
+ void SetStencilEnabled(bool enabled);
+ bool stencil_enabled() const { return stencil_shadow_; }
+ void SetBlendEnabled(bool enabled);
+ bool blend_enabled() const { return blend_shadow_; }
+
+ virtual void BindFramebufferToOutputSurface(DrawingFrame* frame) OVERRIDE;
+ virtual bool BindFramebufferToTexture(DrawingFrame* frame,
+ const ScopedResource* resource,
+ gfx::Rect target_rect) OVERRIDE;
+ virtual void SetDrawViewport(gfx::Rect window_space_viewport) OVERRIDE;
+ virtual void SetScissorTestRect(gfx::Rect scissor_rect) OVERRIDE;
+ virtual void ClearFramebuffer(DrawingFrame* frame) OVERRIDE;
+ virtual void DoDrawQuad(DrawingFrame* frame, const class DrawQuad*) OVERRIDE;
+ virtual void BeginDrawingFrame(DrawingFrame* frame) OVERRIDE;
+ virtual void FinishDrawingFrame(DrawingFrame* frame) OVERRIDE;
+ virtual bool FlippedFramebuffer() const OVERRIDE;
+ virtual void EnsureScissorTestEnabled() OVERRIDE;
+ virtual void EnsureScissorTestDisabled() OVERRIDE;
+ virtual void CopyCurrentRenderPassToBitmap(
+ DrawingFrame* frame,
+ scoped_ptr<CopyOutputRequest> request) OVERRIDE;
+ virtual void FinishDrawingQuadList() OVERRIDE;
+
+ private:
+ friend class GLRendererShaderPixelTest;
+ friend class GLRendererShaderTest;
+
+ static void ToGLMatrix(float* gl_matrix, const gfx::Transform& transform);
+
+ void DrawCheckerboardQuad(const DrawingFrame* frame,
+ const CheckerboardDrawQuad* quad);
+ void DrawDebugBorderQuad(const DrawingFrame* frame,
+ const DebugBorderDrawQuad* quad);
+ scoped_ptr<ScopedResource> DrawBackgroundFilters(
+ DrawingFrame* frame,
+ const RenderPassDrawQuad* quad,
+ const gfx::Transform& contents_device_transform,
+ const gfx::Transform& contents_device_transformInverse);
+ void DrawRenderPassQuad(DrawingFrame* frame, const RenderPassDrawQuad* quad);
+ void DrawSolidColorQuad(const DrawingFrame* frame,
+ const SolidColorDrawQuad* quad);
+ void DrawStreamVideoQuad(const DrawingFrame* frame,
+ const StreamVideoDrawQuad* quad);
+ void EnqueueTextureQuad(const DrawingFrame* frame,
+ const TextureDrawQuad* quad);
+ void FlushTextureQuadCache();
+ void DrawIOSurfaceQuad(const DrawingFrame* frame,
+ const IOSurfaceDrawQuad* quad);
+ void DrawTileQuad(const DrawingFrame* frame, const TileDrawQuad* quad);
+ void DrawContentQuad(const DrawingFrame* frame,
+ const ContentDrawQuadBase* quad,
+ ResourceProvider::ResourceId resource_id);
+ void DrawYUVVideoQuad(const DrawingFrame* frame,
+ const YUVVideoDrawQuad* quad);
+ void DrawPictureQuad(const DrawingFrame* frame,
+ const PictureDrawQuad* quad);
+ void DrawPictureQuadDirectToBackbuffer(const DrawingFrame* frame,
+ const PictureDrawQuad* quad);
+
+ void SetShaderOpacity(float opacity, int alpha_location);
+ void SetShaderQuadF(const gfx::QuadF& quad, int quad_location);
+ void DrawQuadGeometry(const DrawingFrame* frame,
+ const gfx::Transform& draw_transform,
+ const gfx::RectF& quad_rect,
+ int matrix_location);
+ void SetUseProgram(unsigned program);
+
+ void CopyTextureToFramebuffer(const DrawingFrame* frame,
+ int texture_id,
+ gfx::Rect rect,
+ const gfx::Transform& draw_matrix,
+ bool flip_vertically);
+
+ // Check if quad needs antialiasing and if so, inflate the quad and
+ // fill edge array for fragment shader. local_quad is set to
+ // inflated quad if antialiasing is required, otherwise it is left
+ // unchanged. edge array is filled with inflated quad's edge data
+ // if antialiasing is required, otherwise it is left unchanged.
+ // Returns true if quad requires antialiasing and false otherwise.
+ bool SetupQuadForAntialiasing(const gfx::Transform& device_transform,
+ const DrawQuad* quad,
+ gfx::QuadF* local_quad,
+ float edge[24]) const;
+
+ bool UseScopedTexture(DrawingFrame* frame,
+ const ScopedResource* resource,
+ gfx::Rect viewport_rect);
+
+ bool MakeContextCurrent();
+
+ bool InitializeSharedObjects();
+ void CleanupSharedObjects();
+
+ typedef base::Callback<void(scoped_ptr<CopyOutputRequest> copy_request,
+ bool success)>
+ AsyncGetFramebufferPixelsCleanupCallback;
+ void DoGetFramebufferPixels(
+ uint8* pixels,
+ gfx::Rect window_rect,
+ const AsyncGetFramebufferPixelsCleanupCallback& cleanup_callback);
+ void FinishedReadback(
+ const AsyncGetFramebufferPixelsCleanupCallback& cleanup_callback,
+ unsigned source_buffer,
+ unsigned query,
+ uint8_t* dest_pixels,
+ gfx::Size size);
+ void PassOnSkBitmap(scoped_ptr<SkBitmap> bitmap,
+ scoped_ptr<SkAutoLockPixels> lock,
+ scoped_ptr<CopyOutputRequest> request,
+ bool success);
+
+ static void DeleteTextureReleaseCallback(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ base::WeakPtr<GLRenderer> gl_renderer,
+ unsigned texture_id,
+ unsigned sync_point,
+ bool lost_resource);
+ void DeleteTextureReleaseCallbackOnImplThread(unsigned texture_id,
+ unsigned sync_point,
+ bool lost_resource);
+
+ void ReinitializeGrCanvas();
+ void ReinitializeGLState();
+
+ virtual void DiscardBackbuffer() OVERRIDE;
+ virtual void EnsureBackbuffer() OVERRIDE;
+ void EnforceMemoryPolicy();
+
+ RendererCapabilities capabilities_;
+
+ unsigned offscreen_framebuffer_id_;
+
+ scoped_ptr<GeometryBinding> shared_geometry_;
+ gfx::QuadF shared_geometry_quad_;
+
+ // This block of bindings defines all of the programs used by the compositor
+ // itself. Add any new programs here to GLRendererShaderTest.
+
+ // Tiled layer shaders.
+ typedef ProgramBinding<VertexShaderTile, FragmentShaderRGBATexAlpha>
+ TileProgram;
+ typedef ProgramBinding<VertexShaderTileAA, FragmentShaderRGBATexClampAlphaAA>
+ TileProgramAA;
+ typedef ProgramBinding<VertexShaderTileAA,
+ FragmentShaderRGBATexClampSwizzleAlphaAA>
+ TileProgramSwizzleAA;
+ typedef ProgramBinding<VertexShaderTile, FragmentShaderRGBATexOpaque>
+ TileProgramOpaque;
+ typedef ProgramBinding<VertexShaderTile, FragmentShaderRGBATexSwizzleAlpha>
+ TileProgramSwizzle;
+ typedef ProgramBinding<VertexShaderTile, FragmentShaderRGBATexSwizzleOpaque>
+ TileProgramSwizzleOpaque;
+ typedef ProgramBinding<VertexShaderPosTex, FragmentShaderCheckerboard>
+ TileCheckerboardProgram;
+
+ // Texture shaders.
+ typedef ProgramBinding<VertexShaderPosTexTransform,
+ FragmentShaderRGBATexVaryingAlpha> TextureProgram;
+ typedef ProgramBinding<VertexShaderPosTexTransform,
+ FragmentShaderRGBATexPremultiplyAlpha>
+ NonPremultipliedTextureProgram;
+ typedef ProgramBinding<VertexShaderPosTexTransform,
+ FragmentShaderTexBackgroundVaryingAlpha>
+ TextureBackgroundProgram;
+ typedef ProgramBinding<VertexShaderPosTexTransform,
+ FragmentShaderTexBackgroundPremultiplyAlpha>
+ NonPremultipliedTextureBackgroundProgram;
+ typedef ProgramBinding<VertexShaderPosTexTransform,
+ FragmentShaderRGBATexRectVaryingAlpha>
+ TextureIOSurfaceProgram;
+
+ // Render surface shaders.
+ typedef ProgramBinding<VertexShaderPosTexTransform,
+ FragmentShaderRGBATexAlpha> RenderPassProgram;
+ typedef ProgramBinding<VertexShaderPosTexTransform,
+ FragmentShaderRGBATexAlphaMask> RenderPassMaskProgram;
+ typedef ProgramBinding<VertexShaderQuadTexTransformAA,
+ FragmentShaderRGBATexAlphaAA> RenderPassProgramAA;
+ typedef ProgramBinding<VertexShaderQuadTexTransformAA,
+ FragmentShaderRGBATexAlphaMaskAA>
+ RenderPassMaskProgramAA;
+ typedef ProgramBinding<VertexShaderPosTexTransform,
+ FragmentShaderRGBATexColorMatrixAlpha>
+ RenderPassColorMatrixProgram;
+ typedef ProgramBinding<VertexShaderQuadTexTransformAA,
+ FragmentShaderRGBATexAlphaMaskColorMatrixAA>
+ RenderPassMaskColorMatrixProgramAA;
+ typedef ProgramBinding<VertexShaderQuadTexTransformAA,
+ FragmentShaderRGBATexAlphaColorMatrixAA>
+ RenderPassColorMatrixProgramAA;
+ typedef ProgramBinding<VertexShaderPosTexTransform,
+ FragmentShaderRGBATexAlphaMaskColorMatrix>
+ RenderPassMaskColorMatrixProgram;
+
+ // Video shaders.
+ typedef ProgramBinding<VertexShaderVideoTransform,
+ FragmentShaderOESImageExternal>
+ VideoStreamTextureProgram;
+ typedef ProgramBinding<VertexShaderPosTexYUVStretch, FragmentShaderYUVVideo>
+ VideoYUVProgram;
+ typedef ProgramBinding<VertexShaderPosTexYUVStretch, FragmentShaderYUVAVideo>
+ VideoYUVAProgram;
+
+ // Special purpose / effects shaders.
+ typedef ProgramBinding<VertexShaderPos, FragmentShaderColor>
+ DebugBorderProgram;
+ typedef ProgramBinding<VertexShaderQuad, FragmentShaderColor>
+ SolidColorProgram;
+ typedef ProgramBinding<VertexShaderQuadAA, FragmentShaderColorAA>
+ SolidColorProgramAA;
+
+ const TileProgram* GetTileProgram(TexCoordPrecision precision);
+ const TileProgramOpaque* GetTileProgramOpaque(TexCoordPrecision precision);
+ const TileProgramAA* GetTileProgramAA(TexCoordPrecision precision);
+ const TileProgramSwizzle* GetTileProgramSwizzle(TexCoordPrecision precision);
+ const TileProgramSwizzleOpaque* GetTileProgramSwizzleOpaque(
+ TexCoordPrecision precision);
+ const TileProgramSwizzleAA* GetTileProgramSwizzleAA(
+ TexCoordPrecision precision);
+ const TileCheckerboardProgram* GetTileCheckerboardProgram();
+
+ const RenderPassProgram* GetRenderPassProgram(
+ TexCoordPrecision precision);
+ const RenderPassProgramAA* GetRenderPassProgramAA(
+ TexCoordPrecision precision);
+ const RenderPassMaskProgram* GetRenderPassMaskProgram(
+ TexCoordPrecision precision);
+ const RenderPassMaskProgramAA* GetRenderPassMaskProgramAA(
+ TexCoordPrecision precision);
+ const RenderPassColorMatrixProgram* GetRenderPassColorMatrixProgram(
+ TexCoordPrecision precision);
+ const RenderPassColorMatrixProgramAA* GetRenderPassColorMatrixProgramAA(
+ TexCoordPrecision precision);
+ const RenderPassMaskColorMatrixProgram* GetRenderPassMaskColorMatrixProgram(
+ TexCoordPrecision precision);
+ const RenderPassMaskColorMatrixProgramAA*
+ GetRenderPassMaskColorMatrixProgramAA(TexCoordPrecision precision);
+
+ const TextureProgram* GetTextureProgram(
+ TexCoordPrecision precision);
+ const NonPremultipliedTextureProgram* GetNonPremultipliedTextureProgram(
+ TexCoordPrecision precision);
+ const TextureBackgroundProgram* GetTextureBackgroundProgram(
+ TexCoordPrecision precision);
+ const NonPremultipliedTextureBackgroundProgram*
+ GetNonPremultipliedTextureBackgroundProgram(TexCoordPrecision precision);
+ const TextureIOSurfaceProgram* GetTextureIOSurfaceProgram(
+ TexCoordPrecision precision);
+
+ const VideoYUVProgram* GetVideoYUVProgram(
+ TexCoordPrecision precision);
+ const VideoYUVAProgram* GetVideoYUVAProgram(
+ TexCoordPrecision precision);
+ const VideoStreamTextureProgram* GetVideoStreamTextureProgram(
+ TexCoordPrecision precision);
+
+ const DebugBorderProgram* GetDebugBorderProgram();
+ const SolidColorProgram* GetSolidColorProgram();
+ const SolidColorProgramAA* GetSolidColorProgramAA();
+
+ scoped_ptr<TileProgram> tile_program_;
+ scoped_ptr<TileProgramOpaque> tile_program_opaque_;
+ scoped_ptr<TileProgramAA> tile_program_aa_;
+ scoped_ptr<TileProgramSwizzle> tile_program_swizzle_;
+ scoped_ptr<TileProgramSwizzleOpaque> tile_program_swizzle_opaque_;
+ scoped_ptr<TileProgramSwizzleAA> tile_program_swizzle_aa_;
+ scoped_ptr<TileCheckerboardProgram> tile_checkerboard_program_;
+
+ scoped_ptr<TileProgram> tile_program_highp_;
+ scoped_ptr<TileProgramOpaque> tile_program_opaque_highp_;
+ scoped_ptr<TileProgramAA> tile_program_aa_highp_;
+ scoped_ptr<TileProgramSwizzle> tile_program_swizzle_highp_;
+ scoped_ptr<TileProgramSwizzleOpaque> tile_program_swizzle_opaque_highp_;
+ scoped_ptr<TileProgramSwizzleAA> tile_program_swizzle_aa_highp_;
+
+ scoped_ptr<TextureProgram> texture_program_;
+ scoped_ptr<NonPremultipliedTextureProgram> nonpremultiplied_texture_program_;
+ scoped_ptr<TextureBackgroundProgram> texture_background_program_;
+ scoped_ptr<NonPremultipliedTextureBackgroundProgram>
+ nonpremultiplied_texture_background_program_;
+ scoped_ptr<TextureIOSurfaceProgram> texture_io_surface_program_;
+
+ scoped_ptr<TextureProgram> texture_program_highp_;
+ scoped_ptr<NonPremultipliedTextureProgram>
+ nonpremultiplied_texture_program_highp_;
+ scoped_ptr<TextureBackgroundProgram> texture_background_program_highp_;
+ scoped_ptr<NonPremultipliedTextureBackgroundProgram>
+ nonpremultiplied_texture_background_program_highp_;
+ scoped_ptr<TextureIOSurfaceProgram> texture_io_surface_program_highp_;
+
+ scoped_ptr<RenderPassProgram> render_pass_program_;
+ scoped_ptr<RenderPassProgramAA> render_pass_program_aa_;
+ scoped_ptr<RenderPassMaskProgram> render_pass_mask_program_;
+ scoped_ptr<RenderPassMaskProgramAA> render_pass_mask_program_aa_;
+ scoped_ptr<RenderPassColorMatrixProgram> render_pass_color_matrix_program_;
+ scoped_ptr<RenderPassColorMatrixProgramAA>
+ render_pass_color_matrix_program_aa_;
+ scoped_ptr<RenderPassMaskColorMatrixProgram>
+ render_pass_mask_color_matrix_program_;
+ scoped_ptr<RenderPassMaskColorMatrixProgramAA>
+ render_pass_mask_color_matrix_program_aa_;
+
+ scoped_ptr<RenderPassProgram> render_pass_program_highp_;
+ scoped_ptr<RenderPassProgramAA> render_pass_program_aa_highp_;
+ scoped_ptr<RenderPassMaskProgram> render_pass_mask_program_highp_;
+ scoped_ptr<RenderPassMaskProgramAA> render_pass_mask_program_aa_highp_;
+ scoped_ptr<RenderPassColorMatrixProgram>
+ render_pass_color_matrix_program_highp_;
+ scoped_ptr<RenderPassColorMatrixProgramAA>
+ render_pass_color_matrix_program_aa_highp_;
+ scoped_ptr<RenderPassMaskColorMatrixProgram>
+ render_pass_mask_color_matrix_program_highp_;
+ scoped_ptr<RenderPassMaskColorMatrixProgramAA>
+ render_pass_mask_color_matrix_program_aa_highp_;
+
+ scoped_ptr<VideoYUVProgram> video_yuv_program_;
+ scoped_ptr<VideoYUVAProgram> video_yuva_program_;
+ scoped_ptr<VideoStreamTextureProgram> video_stream_texture_program_;
+
+ scoped_ptr<VideoYUVProgram> video_yuv_program_highp_;
+ scoped_ptr<VideoYUVAProgram> video_yuva_program_highp_;
+ scoped_ptr<VideoStreamTextureProgram> video_stream_texture_program_highp_;
+
+ scoped_ptr<DebugBorderProgram> debug_border_program_;
+ scoped_ptr<SolidColorProgram> solid_color_program_;
+ scoped_ptr<SolidColorProgramAA> solid_color_program_aa_;
+
+ WebKit::WebGraphicsContext3D* context_;
+
+ skia::RefPtr<GrContext> gr_context_;
+ skia::RefPtr<SkCanvas> sk_canvas_;
+
+ gfx::Rect swap_buffer_rect_;
+ gfx::Rect scissor_rect_;
+ gfx::Rect viewport_;
+ bool is_backbuffer_discarded_;
+ bool discard_backbuffer_when_not_visible_;
+ bool is_using_bind_uniform_;
+ bool visible_;
+ bool is_scissor_enabled_;
+ bool stencil_shadow_;
+ bool blend_shadow_;
+ unsigned program_shadow_;
+ TexturedQuadDrawCache draw_cache_;
+ int highp_threshold_min_;
+ int highp_threshold_cache_;
+ bool offscreen_context_labelled_;
+
+ struct PendingAsyncReadPixels;
+ ScopedPtrVector<PendingAsyncReadPixels> pending_async_read_pixels_;
+
+ scoped_ptr<ResourceProvider::ScopedWriteLockGL> current_framebuffer_lock_;
+
+ scoped_refptr<ResourceProvider::Fence> last_swap_fence_;
+
+ SkBitmap on_demand_tile_raster_bitmap_;
+ ResourceProvider::ResourceId on_demand_tile_raster_resource_id_;
+
+ base::WeakPtrFactory<GLRenderer> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(GLRenderer);
+};
+
+// Setting DEBUG_GL_CALLS to 1 will call glGetError() after almost every GL
+// call made by the compositor. Useful for debugging rendering issues but
+// will significantly degrade performance.
+#define DEBUG_GL_CALLS 0
+
+#if DEBUG_GL_CALLS && !defined(NDEBUG)
+#define GLC(context, x) \
+ (x, GLRenderer::DebugGLCall(&* context, #x, __FILE__, __LINE__))
+#else
+#define GLC(context, x) (x)
+#endif
+
+} // namespace cc
+
+#endif // CC_OUTPUT_GL_RENDERER_H_
diff --git a/chromium/cc/output/gl_renderer_draw_cache.cc b/chromium/cc/output/gl_renderer_draw_cache.cc
new file mode 100644
index 00000000000..51834c4602c
--- /dev/null
+++ b/chromium/cc/output/gl_renderer_draw_cache.cc
@@ -0,0 +1,14 @@
+// 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 "cc/output/gl_renderer_draw_cache.h"
+
+namespace cc {
+
+TexturedQuadDrawCache::TexturedQuadDrawCache()
+ : program_id(0) {}
+
+TexturedQuadDrawCache::~TexturedQuadDrawCache() {}
+
+} // namespace cc
diff --git a/chromium/cc/output/gl_renderer_draw_cache.h b/chromium/cc/output/gl_renderer_draw_cache.h
new file mode 100644
index 00000000000..f8284184f0c
--- /dev/null
+++ b/chromium/cc/output/gl_renderer_draw_cache.h
@@ -0,0 +1,56 @@
+// 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.
+
+#ifndef CC_OUTPUT_GL_RENDERER_DRAW_CACHE_H_
+#define CC_OUTPUT_GL_RENDERER_DRAW_CACHE_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "third_party/skia/include/core/SkColor.h"
+
+namespace cc {
+
+// Collects 4 floats at a time for easy upload to GL.
+struct Float4 {
+ float data[4];
+};
+
+// Collects 16 floats at a time for easy upload to GL.
+struct Float16 {
+ float data[16];
+};
+
+// A cache for storing textured quads to be drawn. Stores the minimum required
+// data to tell if two back to back draws only differ in their transform. Quads
+// that only differ by transform may be coalesced into a single draw call.
+struct TexturedQuadDrawCache {
+ TexturedQuadDrawCache();
+ ~TexturedQuadDrawCache();
+
+ // Values tracked to determine if textured quads may be coalesced.
+ int program_id;
+ int resource_id;
+ bool needs_blending;
+ SkColor background_color;
+
+ // Information about the program binding that is required to draw.
+ int uv_xform_location;
+ int background_color_location;
+ int vertex_opacity_location;
+ int matrix_location;
+ int sampler_location;
+
+ // A cache for the coalesced quad data.
+ std::vector<Float4> uv_xform_data;
+ std::vector<float> vertex_opacity_data;
+ std::vector<Float16> matrix_data;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TexturedQuadDrawCache);
+};
+
+} // namespace cc
+
+#endif // CC_OUTPUT_GL_RENDERER_DRAW_CACHE_H_
diff --git a/chromium/cc/output/gl_renderer_unittest.cc b/chromium/cc/output/gl_renderer_unittest.cc
new file mode 100644
index 00000000000..9e930ccd023
--- /dev/null
+++ b/chromium/cc/output/gl_renderer_unittest.cc
@@ -0,0 +1,1588 @@
+// 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 "cc/output/gl_renderer.h"
+
+#include <set>
+
+#include "cc/base/math_util.h"
+#include "cc/debug/test_web_graphics_context_3d.h"
+#include "cc/output/compositor_frame_metadata.h"
+#include "cc/resources/prioritized_resource_manager.h"
+#include "cc/resources/resource_provider.h"
+#include "cc/resources/sync_point_helper.h"
+#include "cc/test/fake_impl_proxy.h"
+#include "cc/test/fake_layer_tree_host_impl.h"
+#include "cc/test/fake_output_surface.h"
+#include "cc/test/mock_quad_culler.h"
+#include "cc/test/pixel_test.h"
+#include "cc/test/render_pass_test_common.h"
+#include "cc/test/render_pass_test_utils.h"
+#include "gpu/GLES2/gl2extchromium.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "third_party/skia/include/core/SkImageFilter.h"
+#include "third_party/skia/include/core/SkMatrix.h"
+#include "third_party/skia/include/effects/SkColorFilterImageFilter.h"
+#include "third_party/skia/include/effects/SkColorMatrixFilter.h"
+#include "ui/gfx/transform.h"
+
+using testing::_;
+using testing::AnyNumber;
+using testing::AtLeast;
+using testing::Expectation;
+using testing::InSequence;
+using testing::Mock;
+using testing::Return;
+using testing::StrictMock;
+using WebKit::WebGLId;
+using WebKit::WebString;
+using WebKit::WGC3Dbitfield;
+using WebKit::WGC3Dboolean;
+using WebKit::WGC3Dchar;
+using WebKit::WGC3Denum;
+using WebKit::WGC3Dfloat;
+using WebKit::WGC3Dint;
+using WebKit::WGC3Dintptr;
+using WebKit::WGC3Dsizei;
+using WebKit::WGC3Dsizeiptr;
+using WebKit::WGC3Duint;
+
+namespace cc {
+
+#define EXPECT_PROGRAM_VALID(program_binding) \
+ do { \
+ EXPECT_TRUE(program_binding->program()); \
+ EXPECT_TRUE(program_binding->initialized()); \
+ } while (false)
+
+// Explicitly named to be a friend in GLRenderer for shader access.
+class GLRendererShaderPixelTest : public GLRendererPixelTest {
+ public:
+ void TestShaders() {
+ ASSERT_FALSE(renderer()->IsContextLost());
+ EXPECT_PROGRAM_VALID(renderer()->GetTileCheckerboardProgram());
+ EXPECT_PROGRAM_VALID(renderer()->GetDebugBorderProgram());
+ EXPECT_PROGRAM_VALID(renderer()->GetSolidColorProgram());
+ EXPECT_PROGRAM_VALID(renderer()->GetSolidColorProgramAA());
+ TestShadersWithTexCoordPrecision(TexCoordPrecisionMedium);
+ TestShadersWithTexCoordPrecision(TexCoordPrecisionHigh);
+ ASSERT_FALSE(renderer()->IsContextLost());
+ }
+
+ void TestShadersWithTexCoordPrecision(TexCoordPrecision precision) {
+ EXPECT_PROGRAM_VALID(renderer()->GetTileProgram(precision));
+ EXPECT_PROGRAM_VALID(renderer()->GetTileProgramOpaque(precision));
+ EXPECT_PROGRAM_VALID(renderer()->GetTileProgramAA(precision));
+ EXPECT_PROGRAM_VALID(renderer()->GetTileProgramSwizzle(precision));
+ EXPECT_PROGRAM_VALID(renderer()->GetTileProgramSwizzleOpaque(precision));
+ EXPECT_PROGRAM_VALID(renderer()->GetTileProgramSwizzleAA(precision));
+ EXPECT_PROGRAM_VALID(renderer()->GetRenderPassProgram(precision));
+ EXPECT_PROGRAM_VALID(renderer()->GetRenderPassProgramAA(precision));
+ EXPECT_PROGRAM_VALID(renderer()->GetRenderPassMaskProgram(precision));
+ EXPECT_PROGRAM_VALID(renderer()->GetRenderPassMaskProgramAA(precision));
+ EXPECT_PROGRAM_VALID(
+ renderer()->GetRenderPassColorMatrixProgram(precision));
+ EXPECT_PROGRAM_VALID(
+ renderer()->GetRenderPassMaskColorMatrixProgramAA(precision));
+ EXPECT_PROGRAM_VALID(
+ renderer()->GetRenderPassColorMatrixProgramAA(precision));
+ EXPECT_PROGRAM_VALID(
+ renderer()->GetRenderPassMaskColorMatrixProgram(precision));
+ EXPECT_PROGRAM_VALID(renderer()->GetTextureProgram(precision));
+ EXPECT_PROGRAM_VALID(
+ renderer()->GetNonPremultipliedTextureProgram(precision));
+ EXPECT_PROGRAM_VALID(renderer()->GetTextureBackgroundProgram(precision));
+ EXPECT_PROGRAM_VALID(
+ renderer()->GetNonPremultipliedTextureBackgroundProgram(precision));
+ EXPECT_PROGRAM_VALID(renderer()->GetTextureIOSurfaceProgram(precision));
+ EXPECT_PROGRAM_VALID(renderer()->GetVideoYUVProgram(precision));
+ EXPECT_PROGRAM_VALID(renderer()->GetVideoYUVAProgram(precision));
+ // This is unlikely to be ever true in tests due to usage of osmesa.
+ if (renderer()->Capabilities().using_egl_image)
+ EXPECT_PROGRAM_VALID(renderer()->GetVideoStreamTextureProgram(precision));
+ else
+ EXPECT_FALSE(renderer()->GetVideoStreamTextureProgram(precision));
+ }
+};
+
+namespace {
+
+#if !defined(OS_ANDROID)
+TEST_F(GLRendererShaderPixelTest, AllShadersCompile) { TestShaders(); }
+#endif
+
+class FrameCountingContext : public TestWebGraphicsContext3D {
+ public:
+ FrameCountingContext() : frame_(0) {}
+
+ // WebGraphicsContext3D methods.
+
+ // This method would normally do a glSwapBuffers under the hood.
+ virtual void prepareTexture() { frame_++; }
+ virtual WebString getString(WebKit::WGC3Denum name) {
+ if (name == GL_EXTENSIONS)
+ return WebString(
+ "GL_CHROMIUM_set_visibility GL_CHROMIUM_gpu_memory_manager "
+ "GL_CHROMIUM_discard_backbuffer");
+ return WebString();
+ }
+
+ // Methods added for test.
+ int frame_count() { return frame_; }
+
+ private:
+ int frame_;
+};
+
+class FakeRendererClient : public RendererClient {
+ public:
+ FakeRendererClient()
+ : host_impl_(&proxy_),
+ set_full_root_layer_damage_count_(0),
+ root_layer_(LayerImpl::Create(host_impl_.active_tree(), 1)),
+ viewport_size_(gfx::Size(1, 1)),
+ scale_factor_(1.f),
+ external_stencil_test_enabled_(false) {
+ root_layer_->CreateRenderSurface();
+ RenderPass::Id render_pass_id =
+ root_layer_->render_surface()->RenderPassId();
+ scoped_ptr<RenderPass> root_render_pass = RenderPass::Create();
+ root_render_pass->SetNew(
+ render_pass_id, gfx::Rect(), gfx::Rect(), gfx::Transform());
+ render_passes_in_draw_order_.push_back(root_render_pass.Pass());
+ }
+
+ // RendererClient methods.
+ virtual gfx::Rect DeviceViewport() const OVERRIDE {
+ static gfx::Size fake_size(1, 1);
+ return gfx::Rect(fake_size);
+ }
+ virtual float DeviceScaleFactor() const OVERRIDE {
+ return scale_factor_;
+ }
+ virtual const LayerTreeSettings& Settings() const OVERRIDE {
+ static LayerTreeSettings fake_settings;
+ return fake_settings;
+ }
+ virtual void SetFullRootLayerDamage() OVERRIDE {
+ set_full_root_layer_damage_count_++;
+ }
+ virtual bool HasImplThread() const OVERRIDE { return false; }
+ virtual bool ShouldClearRootRenderPass() const OVERRIDE { return true; }
+ virtual CompositorFrameMetadata MakeCompositorFrameMetadata() const OVERRIDE {
+ return CompositorFrameMetadata();
+ }
+ virtual bool AllowPartialSwap() const OVERRIDE {
+ return true;
+ }
+ virtual bool ExternalStencilTestEnabled() const OVERRIDE {
+ return external_stencil_test_enabled_;
+ }
+
+ void EnableExternalStencilTest() {
+ external_stencil_test_enabled_ = true;
+ }
+
+ // Methods added for test.
+ int set_full_root_layer_damage_count() const {
+ return set_full_root_layer_damage_count_;
+ }
+ void set_viewport_and_scale(
+ gfx::Size viewport_size, float scale_factor) {
+ viewport_size_ = viewport_size;
+ scale_factor_ = scale_factor;
+ }
+
+ RenderPass* root_render_pass() { return render_passes_in_draw_order_.back(); }
+ RenderPassList* render_passes_in_draw_order() {
+ return &render_passes_in_draw_order_;
+ }
+
+ private:
+ FakeImplProxy proxy_;
+ FakeLayerTreeHostImpl host_impl_;
+ int set_full_root_layer_damage_count_;
+ scoped_ptr<LayerImpl> root_layer_;
+ RenderPassList render_passes_in_draw_order_;
+ gfx::Size viewport_size_;
+ float scale_factor_;
+ bool external_stencil_test_enabled_;
+};
+
+class FakeRendererGL : public GLRenderer {
+ public:
+ FakeRendererGL(RendererClient* client,
+ OutputSurface* output_surface,
+ ResourceProvider* resource_provider)
+ : GLRenderer(client, output_surface, resource_provider, 0) {}
+
+ // GLRenderer methods.
+
+ // Changing visibility to public.
+ using GLRenderer::Initialize;
+ using GLRenderer::IsBackbufferDiscarded;
+ using GLRenderer::DoDrawQuad;
+ using GLRenderer::BeginDrawingFrame;
+ using GLRenderer::FinishDrawingQuadList;
+ using GLRenderer::stencil_enabled;
+};
+
+class GLRendererTest : public testing::Test {
+ protected:
+ GLRendererTest()
+ : output_surface_(FakeOutputSurface::Create3d(
+ scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new FrameCountingContext()))),
+ resource_provider_(ResourceProvider::Create(output_surface_.get(), 0)),
+ renderer_(&mock_client_,
+ output_surface_.get(),
+ resource_provider_.get()) {}
+
+ virtual void SetUp() { renderer_.Initialize(); }
+
+ void SwapBuffers() { renderer_.SwapBuffers(); }
+
+ FrameCountingContext* Context() {
+ return static_cast<FrameCountingContext*>(output_surface_->context3d());
+ }
+
+ scoped_ptr<OutputSurface> output_surface_;
+ FakeRendererClient mock_client_;
+ scoped_ptr<ResourceProvider> resource_provider_;
+ 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
+
+
+// Gives unique shader ids and unique program ids for tests that need them.
+class ShaderCreatorMockGraphicsContext : public TestWebGraphicsContext3D {
+ public:
+ ShaderCreatorMockGraphicsContext()
+ : next_program_id_number_(10000),
+ next_shader_id_number_(1) {}
+
+ bool hasShader(WebGLId shader) {
+ return shader_set_.find(shader) != shader_set_.end();
+ }
+
+ bool hasProgram(WebGLId program) {
+ return program_set_.find(program) != program_set_.end();
+ }
+
+ virtual WebGLId createProgram() {
+ unsigned program = next_program_id_number_;
+ program_set_.insert(program);
+ next_program_id_number_++;
+ return program;
+ }
+
+ virtual void deleteProgram(WebGLId program) {
+ ASSERT_TRUE(hasProgram(program));
+ program_set_.erase(program);
+ }
+
+ virtual void useProgram(WebGLId program) {
+ if (!program)
+ return;
+ ASSERT_TRUE(hasProgram(program));
+ }
+
+ virtual WebKit::WebGLId createShader(WebKit::WGC3Denum) {
+ unsigned shader = next_shader_id_number_;
+ shader_set_.insert(shader);
+ next_shader_id_number_++;
+ return shader;
+ }
+
+ virtual void deleteShader(WebKit::WebGLId shader) {
+ ASSERT_TRUE(hasShader(shader));
+ shader_set_.erase(shader);
+ }
+
+ virtual void attachShader(WebGLId program, WebGLId shader) {
+ ASSERT_TRUE(hasProgram(program));
+ ASSERT_TRUE(hasShader(shader));
+ }
+
+ protected:
+ unsigned next_program_id_number_;
+ unsigned next_shader_id_number_;
+ std::set<unsigned> program_set_;
+ std::set<unsigned> shader_set_;
+};
+
+class GLRendererShaderTest : public testing::Test {
+ protected:
+ GLRendererShaderTest()
+ : output_surface_(FakeOutputSurface::Create3d(
+ scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new ShaderCreatorMockGraphicsContext()))),
+ resource_provider_(ResourceProvider::Create(output_surface_.get(), 0)),
+ renderer_(scoped_ptr<FakeRendererGL>(
+ new FakeRendererGL(&mock_client_,
+ output_surface_.get(),
+ resource_provider_.get()))) {
+ renderer_->Initialize();
+ }
+
+ void TestRenderPassProgram() {
+ EXPECT_PROGRAM_VALID(renderer_->render_pass_program_);
+ EXPECT_EQ(renderer_->render_pass_program_->program(),
+ renderer_->program_shadow_);
+ }
+
+ void TestRenderPassColorMatrixProgram() {
+ EXPECT_PROGRAM_VALID(renderer_->render_pass_color_matrix_program_);
+ EXPECT_EQ(renderer_->render_pass_color_matrix_program_->program(),
+ renderer_->program_shadow_);
+ }
+
+ void TestRenderPassMaskProgram() {
+ EXPECT_PROGRAM_VALID(renderer_->render_pass_mask_program_);
+ EXPECT_EQ(renderer_->render_pass_mask_program_->program(),
+ renderer_->program_shadow_);
+ }
+
+ void TestRenderPassMaskColorMatrixProgram() {
+ EXPECT_PROGRAM_VALID(renderer_->render_pass_mask_color_matrix_program_);
+ EXPECT_EQ(renderer_->render_pass_mask_color_matrix_program_->program(),
+ renderer_->program_shadow_);
+ }
+
+ void TestRenderPassProgramAA() {
+ EXPECT_PROGRAM_VALID(renderer_->render_pass_program_aa_);
+ EXPECT_EQ(renderer_->render_pass_program_aa_->program(),
+ renderer_->program_shadow_);
+ }
+
+ void TestRenderPassColorMatrixProgramAA() {
+ EXPECT_PROGRAM_VALID(renderer_->render_pass_color_matrix_program_aa_);
+ EXPECT_EQ(renderer_->render_pass_color_matrix_program_aa_->program(),
+ renderer_->program_shadow_);
+ }
+
+ void TestRenderPassMaskProgramAA() {
+ EXPECT_PROGRAM_VALID(renderer_->render_pass_mask_program_aa_);
+ EXPECT_EQ(renderer_->render_pass_mask_program_aa_->program(),
+ renderer_->program_shadow_);
+ }
+
+ void TestRenderPassMaskColorMatrixProgramAA() {
+ EXPECT_PROGRAM_VALID(renderer_->render_pass_mask_color_matrix_program_aa_);
+ EXPECT_EQ(renderer_->render_pass_mask_color_matrix_program_aa_->program(),
+ renderer_->program_shadow_);
+ }
+
+ void TestSolidColorProgramAA() {
+ EXPECT_PROGRAM_VALID(renderer_->solid_color_program_aa_);
+ EXPECT_EQ(renderer_->solid_color_program_aa_->program(),
+ renderer_->program_shadow_);
+ }
+
+ scoped_ptr<OutputSurface> output_surface_;
+ FakeRendererClient mock_client_;
+ scoped_ptr<ResourceProvider> resource_provider_;
+ scoped_ptr<FakeRendererGL> renderer_;
+};
+
+namespace {
+
+// Test GLRenderer discardBackbuffer functionality:
+// Suggest recreating framebuffer when one already exists.
+// Expected: it does nothing.
+TEST_F(GLRendererTest, SuggestBackbufferYesWhenItAlreadyExistsShouldDoNothing) {
+ renderer_.SetDiscardBackBufferWhenNotVisible(false);
+ EXPECT_EQ(0, mock_client_.set_full_root_layer_damage_count());
+ EXPECT_FALSE(renderer_.IsBackbufferDiscarded());
+
+ SwapBuffers();
+ EXPECT_EQ(1, Context()->frame_count());
+}
+
+// Test GLRenderer DiscardBackbuffer functionality:
+// Suggest discarding framebuffer when one exists and the renderer is not
+// visible.
+// Expected: it is discarded and damage tracker is reset.
+TEST_F(
+ GLRendererTest,
+ SuggestBackbufferNoShouldDiscardBackbufferAndDamageRootLayerIfNotVisible) {
+ renderer_.SetVisible(false);
+ renderer_.SetDiscardBackBufferWhenNotVisible(true);
+ EXPECT_EQ(1, mock_client_.set_full_root_layer_damage_count());
+ EXPECT_TRUE(renderer_.IsBackbufferDiscarded());
+}
+
+// Test GLRenderer DiscardBackbuffer functionality:
+// Suggest discarding framebuffer when one exists and the renderer is visible.
+// Expected: the allocation is ignored.
+TEST_F(GLRendererTest, SuggestBackbufferNoDoNothingWhenVisible) {
+ renderer_.SetVisible(true);
+ renderer_.SetDiscardBackBufferWhenNotVisible(true);
+ EXPECT_EQ(0, mock_client_.set_full_root_layer_damage_count());
+ EXPECT_FALSE(renderer_.IsBackbufferDiscarded());
+}
+
+// Test GLRenderer DiscardBackbuffer functionality:
+// Suggest discarding framebuffer when one does not exist.
+// Expected: it does nothing.
+TEST_F(GLRendererTest, SuggestBackbufferNoWhenItDoesntExistShouldDoNothing) {
+ renderer_.SetVisible(false);
+ renderer_.SetDiscardBackBufferWhenNotVisible(true);
+ EXPECT_EQ(1, mock_client_.set_full_root_layer_damage_count());
+ EXPECT_TRUE(renderer_.IsBackbufferDiscarded());
+
+ renderer_.SetDiscardBackBufferWhenNotVisible(true);
+ EXPECT_EQ(1, mock_client_.set_full_root_layer_damage_count());
+ EXPECT_TRUE(renderer_.IsBackbufferDiscarded());
+}
+
+// Test GLRenderer DiscardBackbuffer functionality:
+// Begin drawing a frame while a framebuffer is discarded.
+// Expected: will recreate framebuffer.
+TEST_F(GLRendererTest, DiscardedBackbufferIsRecreatedForScopeDuration) {
+ renderer_.SetVisible(false);
+ renderer_.SetDiscardBackBufferWhenNotVisible(true);
+ EXPECT_TRUE(renderer_.IsBackbufferDiscarded());
+ EXPECT_EQ(1, mock_client_.set_full_root_layer_damage_count());
+
+ renderer_.SetVisible(true);
+ renderer_.DrawFrame(mock_client_.render_passes_in_draw_order());
+ EXPECT_FALSE(renderer_.IsBackbufferDiscarded());
+
+ SwapBuffers();
+ EXPECT_EQ(1, Context()->frame_count());
+}
+
+TEST_F(GLRendererTest, FramebufferDiscardedAfterReadbackWhenNotVisible) {
+ renderer_.SetVisible(false);
+ renderer_.SetDiscardBackBufferWhenNotVisible(true);
+ EXPECT_TRUE(renderer_.IsBackbufferDiscarded());
+ EXPECT_EQ(1, mock_client_.set_full_root_layer_damage_count());
+
+ char pixels[4];
+ renderer_.DrawFrame(mock_client_.render_passes_in_draw_order());
+ EXPECT_FALSE(renderer_.IsBackbufferDiscarded());
+
+ renderer_.GetFramebufferPixels(pixels, gfx::Rect(0, 0, 1, 1));
+ EXPECT_TRUE(renderer_.IsBackbufferDiscarded());
+ EXPECT_EQ(2, mock_client_.set_full_root_layer_damage_count());
+}
+
+TEST_F(GLRendererTest, ExternalStencil) {
+ EXPECT_FALSE(renderer_.stencil_enabled());
+
+ mock_client_.EnableExternalStencilTest();
+ mock_client_.root_render_pass()->has_transparent_background = false;
+
+ renderer_.DrawFrame(mock_client_.render_passes_in_draw_order());
+ EXPECT_TRUE(renderer_.stencil_enabled());
+}
+
+class ForbidSynchronousCallContext : public TestWebGraphicsContext3D {
+ public:
+ ForbidSynchronousCallContext() {}
+
+ virtual bool getActiveAttrib(WebGLId program,
+ WGC3Duint index,
+ ActiveInfo& info) {
+ ADD_FAILURE();
+ return false;
+ }
+ virtual bool getActiveUniform(WebGLId program,
+ WGC3Duint index,
+ ActiveInfo& info) {
+ ADD_FAILURE();
+ return false;
+ }
+ virtual void getAttachedShaders(WebGLId program,
+ WGC3Dsizei max_count,
+ WGC3Dsizei* count,
+ WebGLId* shaders) {
+ ADD_FAILURE();
+ }
+ virtual WGC3Dint getAttribLocation(WebGLId program, const WGC3Dchar* name) {
+ ADD_FAILURE();
+ return 0;
+ }
+ virtual void getBooleanv(WGC3Denum pname, WGC3Dboolean* value) {
+ ADD_FAILURE();
+ }
+ virtual void getBufferParameteriv(WGC3Denum target,
+ WGC3Denum pname,
+ WGC3Dint* value) {
+ ADD_FAILURE();
+ }
+ virtual Attributes getContextAttributes() {
+ ADD_FAILURE();
+ return attributes_;
+ }
+ virtual WGC3Denum getError() {
+ ADD_FAILURE();
+ return 0;
+ }
+ virtual void getFloatv(WGC3Denum pname, WGC3Dfloat* value) { ADD_FAILURE(); }
+ virtual void getFramebufferAttachmentParameteriv(WGC3Denum target,
+ WGC3Denum attachment,
+ WGC3Denum pname,
+ WGC3Dint* value) {
+ ADD_FAILURE();
+ }
+ virtual void getIntegerv(WGC3Denum pname, WGC3Dint* value) {
+ 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.
+ virtual void getProgramiv(WebGLId program, WGC3Denum pname, WGC3Dint* value) {
+#ifndef NDEBUG
+ *value = 1;
+#else
+ ADD_FAILURE();
+#endif
+ }
+
+ virtual void getShaderiv(WebGLId shader, WGC3Denum pname, WGC3Dint* value) {
+#ifndef NDEBUG
+ *value = 1;
+#else
+ ADD_FAILURE();
+#endif
+ }
+
+ virtual WebString getString(WGC3Denum name) {
+ // We allow querying the extension string.
+ // TODO(enne): It'd be better to check that we only do this before starting
+ // any other expensive work (like starting a compilation)
+ if (name != GL_EXTENSIONS)
+ ADD_FAILURE();
+ return WebString();
+ }
+
+ virtual WebString getProgramInfoLog(WebGLId program) {
+ ADD_FAILURE();
+ return WebString();
+ }
+ virtual void getRenderbufferParameteriv(WGC3Denum target,
+ WGC3Denum pname,
+ WGC3Dint* value) {
+ ADD_FAILURE();
+ }
+
+ virtual WebString getShaderInfoLog(WebGLId shader) {
+ ADD_FAILURE();
+ return WebString();
+ }
+ virtual void getShaderPrecisionFormat(WGC3Denum shadertype,
+ WGC3Denum precisiontype,
+ WGC3Dint* range,
+ WGC3Dint* precision) {
+ ADD_FAILURE();
+ }
+ virtual WebString getShaderSource(WebGLId shader) {
+ ADD_FAILURE();
+ return WebString();
+ }
+ virtual void getTexParameterfv(WGC3Denum target,
+ WGC3Denum pname,
+ WGC3Dfloat* value) {
+ ADD_FAILURE();
+ }
+ virtual void getTexParameteriv(WGC3Denum target,
+ WGC3Denum pname,
+ WGC3Dint* value) {
+ ADD_FAILURE();
+ }
+ virtual void getUniformfv(WebGLId program,
+ WGC3Dint location,
+ WGC3Dfloat* value) {
+ ADD_FAILURE();
+ }
+ virtual void getUniformiv(WebGLId program,
+ WGC3Dint location,
+ WGC3Dint* value) {
+ ADD_FAILURE();
+ }
+ virtual WGC3Dint getUniformLocation(WebGLId program, const WGC3Dchar* name) {
+ ADD_FAILURE();
+ return 0;
+ }
+ virtual void getVertexAttribfv(WGC3Duint index,
+ WGC3Denum pname,
+ WGC3Dfloat* value) {
+ ADD_FAILURE();
+ }
+ virtual void getVertexAttribiv(WGC3Duint index,
+ WGC3Denum pname,
+ WGC3Dint* value) {
+ ADD_FAILURE();
+ }
+ virtual WGC3Dsizeiptr getVertexAttribOffset(WGC3Duint index,
+ WGC3Denum pname) {
+ ADD_FAILURE();
+ return 0;
+ }
+};
+
+// This test isn't using the same fixture as GLRendererTest, and you can't mix
+// TEST() and TEST_F() with the same name, Hence LRC2.
+TEST(GLRendererTest2, InitializationDoesNotMakeSynchronousCalls) {
+ FakeRendererClient mock_client;
+ scoped_ptr<OutputSurface> output_surface(
+ FakeOutputSurface::Create3d(scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new ForbidSynchronousCallContext)));
+ scoped_ptr<ResourceProvider> resource_provider(
+ ResourceProvider::Create(output_surface.get(), 0));
+ FakeRendererGL renderer(
+ &mock_client, output_surface.get(), resource_provider.get());
+
+ EXPECT_TRUE(renderer.Initialize());
+}
+
+class LoseContextOnFirstGetContext : public TestWebGraphicsContext3D {
+ public:
+ LoseContextOnFirstGetContext() : context_lost_(false) {}
+
+ virtual bool makeContextCurrent() OVERRIDE { return !context_lost_; }
+
+ virtual void getProgramiv(WebGLId program, WGC3Denum pname, WGC3Dint* value)
+ OVERRIDE {
+ context_lost_ = true;
+ *value = 0;
+ }
+
+ virtual void getShaderiv(WebGLId shader, WGC3Denum pname, WGC3Dint* value)
+ OVERRIDE {
+ context_lost_ = true;
+ *value = 0;
+ }
+
+ virtual WGC3Denum getGraphicsResetStatusARB() OVERRIDE {
+ return context_lost_ ? 1 : 0;
+ }
+
+ private:
+ bool context_lost_;
+};
+
+TEST(GLRendererTest2, InitializationWithQuicklyLostContextDoesNotAssert) {
+ FakeRendererClient mock_client;
+ scoped_ptr<OutputSurface> output_surface(
+ FakeOutputSurface::Create3d(scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new LoseContextOnFirstGetContext)));
+ scoped_ptr<ResourceProvider> resource_provider(
+ ResourceProvider::Create(output_surface.get(), 0));
+ FakeRendererGL renderer(
+ &mock_client, output_surface.get(), resource_provider.get());
+
+ renderer.Initialize();
+}
+
+class ClearCountingContext : public TestWebGraphicsContext3D {
+ public:
+ ClearCountingContext() : clear_(0) {}
+
+ virtual void clear(WGC3Dbitfield) { clear_++; }
+
+ int clear_count() const { return clear_; }
+
+ private:
+ int clear_;
+};
+
+TEST(GLRendererTest2, OpaqueBackground) {
+ FakeRendererClient mock_client;
+ scoped_ptr<OutputSurface> output_surface(FakeOutputSurface::Create3d(
+ scoped_ptr<WebKit::WebGraphicsContext3D>(new ClearCountingContext)));
+ ClearCountingContext* context =
+ static_cast<ClearCountingContext*>(output_surface->context3d());
+ scoped_ptr<ResourceProvider> resource_provider(
+ ResourceProvider::Create(output_surface.get(), 0));
+ FakeRendererGL renderer(
+ &mock_client, output_surface.get(), resource_provider.get());
+
+ mock_client.root_render_pass()->has_transparent_background = false;
+
+ EXPECT_TRUE(renderer.Initialize());
+
+ renderer.DrawFrame(mock_client.render_passes_in_draw_order());
+
+// On DEBUG builds, render passes with opaque background clear to blue to
+// easily see regions that were not drawn on the screen.
+#ifdef NDEBUG
+ EXPECT_EQ(0, context->clear_count());
+#else
+ EXPECT_EQ(1, context->clear_count());
+#endif
+}
+
+TEST(GLRendererTest2, TransparentBackground) {
+ FakeRendererClient mock_client;
+ scoped_ptr<OutputSurface> output_surface(FakeOutputSurface::Create3d(
+ scoped_ptr<WebKit::WebGraphicsContext3D>(new ClearCountingContext)));
+ ClearCountingContext* context =
+ static_cast<ClearCountingContext*>(output_surface->context3d());
+ scoped_ptr<ResourceProvider> resource_provider(
+ ResourceProvider::Create(output_surface.get(), 0));
+ FakeRendererGL renderer(
+ &mock_client, output_surface.get(), resource_provider.get());
+
+ mock_client.root_render_pass()->has_transparent_background = true;
+
+ EXPECT_TRUE(renderer.Initialize());
+
+ renderer.DrawFrame(mock_client.render_passes_in_draw_order());
+
+ EXPECT_EQ(1, context->clear_count());
+}
+
+class VisibilityChangeIsLastCallTrackingContext
+ : public TestWebGraphicsContext3D {
+ public:
+ VisibilityChangeIsLastCallTrackingContext()
+ : last_call_was_set_visibility_(false) {}
+
+ // WebGraphicsContext3D methods.
+ virtual void setVisibilityCHROMIUM(bool visible) {
+ DCHECK(last_call_was_set_visibility_ == false);
+ last_call_was_set_visibility_ = true;
+ }
+ virtual void flush() {
+ last_call_was_set_visibility_ = false;
+ }
+ virtual void deleteTexture(WebGLId) {
+ last_call_was_set_visibility_ = false;
+ }
+ virtual void deleteFramebuffer(WebGLId) {
+ last_call_was_set_visibility_ = false;
+ }
+ virtual void deleteQueryEXT(WebGLId) {
+ last_call_was_set_visibility_ = false;
+ }
+ virtual void deleteRenderbuffer(WebGLId) {
+ last_call_was_set_visibility_ = false;
+ }
+ virtual void discardBackbufferCHROMIUM() {
+ last_call_was_set_visibility_ = false;
+ }
+ virtual void ensureBackbufferCHROMIUM() {
+ last_call_was_set_visibility_ = false;
+ }
+
+ // This method would normally do a glSwapBuffers under the hood.
+ virtual WebString getString(WebKit::WGC3Denum name) {
+ if (name == GL_EXTENSIONS)
+ return WebString(
+ "GL_CHROMIUM_set_visibility GL_CHROMIUM_gpu_memory_manager "
+ "GL_CHROMIUM_discard_backbuffer");
+ return WebString();
+ }
+
+ // Methods added for test.
+ bool last_call_was_set_visibility() const {
+ return last_call_was_set_visibility_;
+ }
+
+ private:
+ bool last_call_was_set_visibility_;
+};
+
+TEST(GLRendererTest2, VisibilityChangeIsLastCall) {
+ FakeRendererClient mock_client;
+ scoped_ptr<OutputSurface> output_surface(
+ FakeOutputSurface::Create3d(scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new VisibilityChangeIsLastCallTrackingContext)));
+ VisibilityChangeIsLastCallTrackingContext* context =
+ static_cast<VisibilityChangeIsLastCallTrackingContext*>(
+ output_surface->context3d());
+ scoped_ptr<ResourceProvider> resource_provider(
+ ResourceProvider::Create(output_surface.get(), 0));
+ FakeRendererGL renderer(
+ &mock_client, output_surface.get(), resource_provider.get());
+
+ EXPECT_TRUE(renderer.Initialize());
+
+ // Ensure that the call to setVisibilityCHROMIUM is the last call issue to the
+ // GPU process, after glFlush is called, and after the RendererClient's
+ // SetManagedMemoryPolicy is called. Plumb this tracking between both the
+ // RenderClient and the Context by giving them both a pointer to a variable on
+ // the stack.
+ renderer.SetVisible(true);
+ renderer.DrawFrame(mock_client.render_passes_in_draw_order());
+ renderer.SetVisible(false);
+ EXPECT_TRUE(context->last_call_was_set_visibility());
+}
+
+class TextureStateTrackingContext : public TestWebGraphicsContext3D {
+ public:
+ TextureStateTrackingContext() : active_texture_(GL_INVALID_ENUM) {}
+
+ virtual WebString getString(WGC3Denum name) {
+ if (name == GL_EXTENSIONS)
+ return WebString("GL_OES_EGL_image_external");
+ return WebString();
+ }
+
+ MOCK_METHOD3(texParameteri,
+ void(WGC3Denum target, WGC3Denum pname, WGC3Dint param));
+ MOCK_METHOD4(drawElements,
+ void(WGC3Denum mode,
+ WGC3Dsizei count,
+ WGC3Denum type,
+ WGC3Dintptr offset));
+
+ virtual void activeTexture(WGC3Denum texture) {
+ EXPECT_NE(texture, active_texture_);
+ active_texture_ = texture;
+ }
+
+ WGC3Denum active_texture() const { return active_texture_; }
+
+ private:
+ WGC3Denum active_texture_;
+};
+
+TEST(GLRendererTest2, ActiveTextureState) {
+ FakeRendererClient fake_client;
+ scoped_ptr<OutputSurface> output_surface(
+ FakeOutputSurface::Create3d(scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new TextureStateTrackingContext)));
+ TextureStateTrackingContext* context =
+ static_cast<TextureStateTrackingContext*>(output_surface->context3d());
+ scoped_ptr<ResourceProvider> resource_provider(
+ ResourceProvider::Create(output_surface.get(), 0));
+ FakeRendererGL renderer(
+ &fake_client, output_surface.get(), resource_provider.get());
+
+ // During initialization we are allowed to set any texture parameters.
+ EXPECT_CALL(*context, texParameteri(_, _, _)).Times(AnyNumber());
+ EXPECT_TRUE(renderer.Initialize());
+
+ cc::RenderPass::Id id(1, 1);
+ scoped_ptr<TestRenderPass> pass = TestRenderPass::Create();
+ pass->SetNew(id,
+ gfx::Rect(0, 0, 100, 100),
+ gfx::Rect(0, 0, 100, 100),
+ gfx::Transform());
+ pass->AppendOneOfEveryQuadType(resource_provider.get(), RenderPass::Id(2, 1));
+
+ // Set up expected texture filter state transitions that match the quads
+ // created in AppendOneOfEveryQuadType().
+ Mock::VerifyAndClearExpectations(context);
+ {
+ InSequence sequence;
+
+ // yuv_quad is drawn with the default linear filter.
+ EXPECT_CALL(*context, drawElements(_, _, _, _));
+
+ // tile_quad is drawn with GL_NEAREST because it is not transformed or
+ // scaled.
+ EXPECT_CALL(
+ *context,
+ texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
+ EXPECT_CALL(
+ *context,
+ texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
+ EXPECT_CALL(*context, drawElements(_, _, _, _));
+
+ // transformed_tile_quad uses GL_LINEAR.
+ EXPECT_CALL(*context, drawElements(_, _, _, _));
+
+ // scaled_tile_quad also uses GL_LINEAR.
+ EXPECT_CALL(*context, drawElements(_, _, _, _));
+
+ // The remaining quads also use GL_LINEAR because nearest neighbor
+ // filtering is currently only used with tile quads.
+ EXPECT_CALL(*context, drawElements(_, _, _, _)).Times(6);
+ }
+
+ cc::DirectRenderer::DrawingFrame drawing_frame;
+ renderer.BeginDrawingFrame(&drawing_frame);
+ EXPECT_EQ(static_cast<unsigned>(GL_TEXTURE0), context->active_texture());
+
+ for (cc::QuadList::BackToFrontIterator
+ it = pass->quad_list.BackToFrontBegin();
+ it != pass->quad_list.BackToFrontEnd();
+ ++it) {
+ renderer.DoDrawQuad(&drawing_frame, *it);
+ }
+ renderer.FinishDrawingQuadList();
+ EXPECT_EQ(static_cast<unsigned>(GL_TEXTURE0), context->active_texture());
+ Mock::VerifyAndClearExpectations(context);
+}
+
+class NoClearRootRenderPassFakeClient : public FakeRendererClient {
+ public:
+ virtual bool ShouldClearRootRenderPass() const OVERRIDE { return false; }
+};
+
+class NoClearRootRenderPassMockContext : public TestWebGraphicsContext3D {
+ public:
+ MOCK_METHOD1(clear, void(WGC3Dbitfield mask));
+ MOCK_METHOD4(drawElements,
+ void(WGC3Denum mode,
+ WGC3Dsizei count,
+ WGC3Denum type,
+ WGC3Dintptr offset));
+};
+
+TEST(GLRendererTest2, ShouldClearRootRenderPass) {
+ NoClearRootRenderPassFakeClient mock_client;
+ scoped_ptr<OutputSurface> output_surface(
+ FakeOutputSurface::Create3d(scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new NoClearRootRenderPassMockContext)));
+ NoClearRootRenderPassMockContext* mock_context =
+ static_cast<NoClearRootRenderPassMockContext*>(
+ output_surface->context3d());
+ scoped_ptr<ResourceProvider> resource_provider(
+ ResourceProvider::Create(output_surface.get(), 0));
+ FakeRendererGL renderer(
+ &mock_client, output_surface.get(), resource_provider.get());
+ EXPECT_TRUE(renderer.Initialize());
+
+ gfx::Rect viewport_rect(mock_client.DeviceViewport());
+ ScopedPtrVector<RenderPass>& render_passes =
+ *mock_client.render_passes_in_draw_order();
+ render_passes.clear();
+
+ RenderPass::Id root_pass_id(1, 0);
+ TestRenderPass* root_pass = AddRenderPass(
+ &render_passes, root_pass_id, viewport_rect, gfx::Transform());
+ AddQuad(root_pass, viewport_rect, SK_ColorGREEN);
+
+ RenderPass::Id child_pass_id(2, 0);
+ TestRenderPass* child_pass = AddRenderPass(
+ &render_passes, child_pass_id, viewport_rect, gfx::Transform());
+ AddQuad(child_pass, viewport_rect, SK_ColorBLUE);
+
+ 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_context, clear(clear_bits)).Times(AtLeast(1));
+
+ Expectation first_render_pass =
+ EXPECT_CALL(*mock_context, drawElements(_, _, _, _)).Times(1);
+
+ // The second render pass is the root one, clearing should be prevented.
+ EXPECT_CALL(*mock_context, clear(clear_bits)).Times(0)
+ .After(first_render_pass);
+
+ EXPECT_CALL(*mock_context, drawElements(_, _, _, _)).Times(AnyNumber())
+ .After(first_render_pass);
+
+ renderer.DecideRenderPassAllocationsForFrame(
+ *mock_client.render_passes_in_draw_order());
+ renderer.DrawFrame(mock_client.render_passes_in_draw_order());
+
+ // In multiple render passes all but the root pass should clear the
+ // framebuffer.
+ Mock::VerifyAndClearExpectations(&mock_context);
+}
+
+class ScissorTestOnClearCheckingContext : public TestWebGraphicsContext3D {
+ public:
+ ScissorTestOnClearCheckingContext() : scissor_enabled_(false) {}
+
+ virtual void clear(WGC3Dbitfield) { EXPECT_FALSE(scissor_enabled_); }
+
+ virtual void enable(WGC3Denum cap) {
+ if (cap == GL_SCISSOR_TEST)
+ scissor_enabled_ = true;
+ }
+
+ virtual void disable(WGC3Denum cap) {
+ if (cap == GL_SCISSOR_TEST)
+ scissor_enabled_ = false;
+ }
+
+ private:
+ bool scissor_enabled_;
+};
+
+TEST(GLRendererTest2, ScissorTestWhenClearing) {
+ FakeRendererClient mock_client;
+ scoped_ptr<OutputSurface> output_surface(
+ FakeOutputSurface::Create3d(scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new ScissorTestOnClearCheckingContext)));
+ scoped_ptr<ResourceProvider> resource_provider(
+ ResourceProvider::Create(output_surface.get(), 0));
+ FakeRendererGL renderer(
+ &mock_client, output_surface.get(), resource_provider.get());
+ EXPECT_TRUE(renderer.Initialize());
+ EXPECT_FALSE(renderer.Capabilities().using_partial_swap);
+
+ gfx::Rect viewport_rect(mock_client.DeviceViewport());
+ ScopedPtrVector<RenderPass>& render_passes =
+ *mock_client.render_passes_in_draw_order();
+ render_passes.clear();
+
+ gfx::Rect grand_child_rect(25, 25);
+ RenderPass::Id grand_child_pass_id(3, 0);
+ TestRenderPass* grand_child_pass = AddRenderPass(
+ &render_passes, grand_child_pass_id, grand_child_rect, gfx::Transform());
+ AddClippedQuad(grand_child_pass, grand_child_rect, SK_ColorYELLOW);
+
+ gfx::Rect child_rect(50, 50);
+ RenderPass::Id child_pass_id(2, 0);
+ TestRenderPass* child_pass = AddRenderPass(
+ &render_passes, child_pass_id, child_rect, gfx::Transform());
+ AddQuad(child_pass, child_rect, SK_ColorBLUE);
+
+ RenderPass::Id root_pass_id(1, 0);
+ TestRenderPass* root_pass = AddRenderPass(
+ &render_passes, root_pass_id, viewport_rect, gfx::Transform());
+ AddQuad(root_pass, viewport_rect, SK_ColorGREEN);
+
+ AddRenderPassQuad(root_pass, child_pass);
+ AddRenderPassQuad(child_pass, grand_child_pass);
+
+ renderer.DecideRenderPassAllocationsForFrame(
+ *mock_client.render_passes_in_draw_order());
+ renderer.DrawFrame(mock_client.render_passes_in_draw_order());
+}
+
+class NonReshapableOutputSurface : public FakeOutputSurface {
+ public:
+ explicit NonReshapableOutputSurface(
+ scoped_ptr<WebKit::WebGraphicsContext3D> context3d)
+ : FakeOutputSurface(context3d.Pass(), false) {}
+ virtual gfx::Size SurfaceSize() const OVERRIDE { return gfx::Size(500, 500); }
+};
+
+class OffsetViewportRendererClient : public FakeRendererClient {
+ public:
+ virtual gfx::Rect DeviceViewport() const OVERRIDE {
+ return gfx::Rect(10, 10, 100, 100);
+ }
+};
+
+class FlippedScissorAndViewportContext : public TestWebGraphicsContext3D {
+ public:
+ FlippedScissorAndViewportContext()
+ : did_call_viewport_(false), did_call_scissor_(false) {}
+ virtual ~FlippedScissorAndViewportContext() {
+ EXPECT_TRUE(did_call_viewport_);
+ EXPECT_TRUE(did_call_scissor_);
+ }
+
+ virtual void viewport(GLint x, GLint y, GLsizei width, GLsizei height) {
+ EXPECT_EQ(10, x);
+ EXPECT_EQ(390, y);
+ EXPECT_EQ(100, width);
+ EXPECT_EQ(100, height);
+ did_call_viewport_ = true;
+ }
+
+ virtual void scissor(GLint x, GLint y, GLsizei width, GLsizei height) {
+ EXPECT_EQ(30, x);
+ EXPECT_EQ(450, y);
+ EXPECT_EQ(20, width);
+ EXPECT_EQ(20, height);
+ did_call_scissor_ = true;
+ }
+
+ private:
+ bool did_call_viewport_;
+ bool did_call_scissor_;
+};
+
+TEST(GLRendererTest2, ScissorAndViewportWithinNonreshapableSurface) {
+ // In Android WebView, the OutputSurface is unable to respect reshape() calls
+ // and maintains a fixed size. This test verifies that glViewport and
+ // glScissor's Y coordinate is flipped correctly in this environment, and that
+ // the glViewport can be at a nonzero origin within the surface.
+ OffsetViewportRendererClient mock_client;
+ scoped_ptr<OutputSurface> output_surface(make_scoped_ptr(
+ new NonReshapableOutputSurface(scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new FlippedScissorAndViewportContext))));
+ scoped_ptr<ResourceProvider> resource_provider(
+ ResourceProvider::Create(output_surface.get(), 0));
+ FakeRendererGL renderer(
+ &mock_client, output_surface.get(), resource_provider.get());
+ EXPECT_TRUE(renderer.Initialize());
+ EXPECT_FALSE(renderer.Capabilities().using_partial_swap);
+
+ gfx::Rect viewport_rect(mock_client.DeviceViewport().size());
+ gfx::Rect quad_rect = gfx::Rect(20, 20, 20, 20);
+ ScopedPtrVector<RenderPass>& render_passes =
+ *mock_client.render_passes_in_draw_order();
+ render_passes.clear();
+
+ RenderPass::Id root_pass_id(1, 0);
+ TestRenderPass* root_pass = AddRenderPass(
+ &render_passes, root_pass_id, viewport_rect, gfx::Transform());
+ AddClippedQuad(root_pass, quad_rect, SK_ColorGREEN);
+
+ renderer.DecideRenderPassAllocationsForFrame(
+ *mock_client.render_passes_in_draw_order());
+ renderer.DrawFrame(mock_client.render_passes_in_draw_order());
+}
+
+TEST_F(GLRendererShaderTest, DrawRenderPassQuadShaderPermutations) {
+ gfx::Rect viewport_rect(mock_client_.DeviceViewport());
+ ScopedPtrVector<RenderPass>* render_passes =
+ mock_client_.render_passes_in_draw_order();
+
+ gfx::Rect child_rect(50, 50);
+ RenderPass::Id child_pass_id(2, 0);
+ TestRenderPass* child_pass;
+
+ RenderPass::Id root_pass_id(1, 0);
+ TestRenderPass* root_pass;
+
+ cc::ResourceProvider::ResourceId mask =
+ resource_provider_->CreateResource(gfx::Size(20, 12),
+ resource_provider_->best_texture_format(),
+ ResourceProvider::TextureUsageAny);
+ resource_provider_->AllocateForTesting(mask);
+
+ SkScalar 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;
+ skia::RefPtr<SkColorFilter> color_filter(
+ skia::AdoptRef(new SkColorMatrixFilter(matrix)));
+ skia::RefPtr<SkImageFilter> filter = skia::AdoptRef(
+ SkColorFilterImageFilter::Create(color_filter.get(), NULL));
+
+ gfx::Transform transform_causing_aa;
+ transform_causing_aa.Rotate(20.0);
+
+ // RenderPassProgram
+ render_passes->clear();
+
+ child_pass = AddRenderPass(
+ render_passes, child_pass_id, child_rect, gfx::Transform());
+
+ root_pass = AddRenderPass(
+ render_passes, root_pass_id, viewport_rect, gfx::Transform());
+
+ AddRenderPassQuad(root_pass,
+ child_pass,
+ 0,
+ skia::RefPtr<SkImageFilter>(),
+ gfx::Transform());
+
+ renderer_->DecideRenderPassAllocationsForFrame(
+ *mock_client_.render_passes_in_draw_order());
+ renderer_->DrawFrame(mock_client_.render_passes_in_draw_order());
+ TestRenderPassProgram();
+
+ // RenderPassColorMatrixProgram
+ render_passes->clear();
+
+ child_pass = AddRenderPass(
+ render_passes, child_pass_id, child_rect, transform_causing_aa);
+
+ root_pass = AddRenderPass(
+ render_passes, root_pass_id, viewport_rect, gfx::Transform());
+
+ AddRenderPassQuad(root_pass, child_pass, 0, filter, gfx::Transform());
+
+ renderer_->DecideRenderPassAllocationsForFrame(
+ *mock_client_.render_passes_in_draw_order());
+ renderer_->DrawFrame(mock_client_.render_passes_in_draw_order());
+ TestRenderPassColorMatrixProgram();
+
+ // RenderPassMaskProgram
+ render_passes->clear();
+
+ child_pass = AddRenderPass(
+ render_passes, child_pass_id, child_rect, gfx::Transform());
+
+ root_pass = AddRenderPass(
+ render_passes, root_pass_id, viewport_rect, gfx::Transform());
+
+ AddRenderPassQuad(root_pass,
+ child_pass,
+ mask,
+ skia::RefPtr<SkImageFilter>(),
+ gfx::Transform());
+
+ renderer_->DecideRenderPassAllocationsForFrame(
+ *mock_client_.render_passes_in_draw_order());
+ renderer_->DrawFrame(mock_client_.render_passes_in_draw_order());
+ TestRenderPassMaskProgram();
+
+ // RenderPassMaskColorMatrixProgram
+ render_passes->clear();
+
+ child_pass = AddRenderPass(
+ render_passes, child_pass_id, child_rect, gfx::Transform());
+
+ root_pass = AddRenderPass(
+ render_passes, root_pass_id, viewport_rect, gfx::Transform());
+
+ AddRenderPassQuad(root_pass, child_pass, mask, filter, gfx::Transform());
+
+ renderer_->DecideRenderPassAllocationsForFrame(
+ *mock_client_.render_passes_in_draw_order());
+ renderer_->DrawFrame(mock_client_.render_passes_in_draw_order());
+ TestRenderPassMaskColorMatrixProgram();
+
+ // RenderPassProgramAA
+ render_passes->clear();
+
+ child_pass = AddRenderPass(
+ render_passes, child_pass_id, child_rect, transform_causing_aa);
+
+ root_pass = AddRenderPass(
+ render_passes, root_pass_id, viewport_rect, gfx::Transform());
+
+ AddRenderPassQuad(root_pass,
+ child_pass,
+ 0,
+ skia::RefPtr<SkImageFilter>(),
+ transform_causing_aa);
+
+ renderer_->DecideRenderPassAllocationsForFrame(
+ *mock_client_.render_passes_in_draw_order());
+ renderer_->DrawFrame(mock_client_.render_passes_in_draw_order());
+ TestRenderPassProgramAA();
+
+ // RenderPassColorMatrixProgramAA
+ render_passes->clear();
+
+ child_pass = AddRenderPass(
+ render_passes, child_pass_id, child_rect, transform_causing_aa);
+
+ root_pass = AddRenderPass(
+ render_passes, root_pass_id, viewport_rect, gfx::Transform());
+
+ AddRenderPassQuad(root_pass, child_pass, 0, filter, transform_causing_aa);
+
+ renderer_->DecideRenderPassAllocationsForFrame(
+ *mock_client_.render_passes_in_draw_order());
+ renderer_->DrawFrame(mock_client_.render_passes_in_draw_order());
+ TestRenderPassColorMatrixProgramAA();
+
+ // RenderPassMaskProgramAA
+ render_passes->clear();
+
+ child_pass = AddRenderPass(render_passes, child_pass_id, child_rect,
+ transform_causing_aa);
+
+ root_pass = AddRenderPass(render_passes, root_pass_id, viewport_rect,
+ gfx::Transform());
+
+ AddRenderPassQuad(root_pass, child_pass, mask, skia::RefPtr<SkImageFilter>(),
+ transform_causing_aa);
+
+ renderer_->DecideRenderPassAllocationsForFrame(
+ *mock_client_.render_passes_in_draw_order());
+ renderer_->DrawFrame(mock_client_.render_passes_in_draw_order());
+ TestRenderPassMaskProgramAA();
+
+ // RenderPassMaskColorMatrixProgramAA
+ render_passes->clear();
+
+ child_pass = AddRenderPass(render_passes, child_pass_id, child_rect,
+ transform_causing_aa);
+
+ root_pass = AddRenderPass(render_passes, root_pass_id, viewport_rect,
+ transform_causing_aa);
+
+ AddRenderPassQuad(root_pass, child_pass, mask, filter, transform_causing_aa);
+
+ renderer_->DecideRenderPassAllocationsForFrame(
+ *mock_client_.render_passes_in_draw_order());
+ renderer_->DrawFrame(mock_client_.render_passes_in_draw_order());
+ TestRenderPassMaskColorMatrixProgramAA();
+}
+
+// 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);
+ RenderPass::Id child_pass_id(2, 0);
+ TestRenderPass* child_pass;
+
+ gfx::Rect viewport_rect(mock_client_.DeviceViewport());
+ RenderPass::Id root_pass_id(1, 0);
+ TestRenderPass* 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;
+ MathUtil::MapQuad(transform_preventing_aa,
+ gfx::QuadF(child_rect),
+ &clipped);
+ ASSERT_TRUE(clipped);
+
+ // Set up the render pass quad to be drawn
+ ScopedPtrVector<RenderPass>* render_passes =
+ mock_client_.render_passes_in_draw_order();
+
+ render_passes->clear();
+
+ child_pass = AddRenderPass(
+ render_passes, child_pass_id, child_rect, transform_preventing_aa);
+
+ root_pass = AddRenderPass(
+ render_passes, root_pass_id, viewport_rect, gfx::Transform());
+
+ AddRenderPassQuad(root_pass,
+ child_pass,
+ 0,
+ skia::RefPtr<SkImageFilter>(),
+ transform_preventing_aa);
+
+ renderer_->DecideRenderPassAllocationsForFrame(
+ *mock_client_.render_passes_in_draw_order());
+ renderer_->DrawFrame(mock_client_.render_passes_in_draw_order());
+
+ // If use_aa incorrectly ignores clipping, it will use the
+ // RenderPassProgramAA shader instead of the RenderPassProgram.
+ TestRenderPassProgram();
+}
+
+TEST_F(GLRendererShaderTest, DrawSolidColorShader) {
+ gfx::Rect viewport_rect(mock_client_.DeviceViewport());
+ ScopedPtrVector<RenderPass>* render_passes =
+ mock_client_.render_passes_in_draw_order();
+
+ RenderPass::Id root_pass_id(1, 0);
+ TestRenderPass* 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);
+
+ render_passes->clear();
+
+ root_pass = AddRenderPass(
+ render_passes, root_pass_id, viewport_rect, gfx::Transform());
+ AddTransformedQuad(root_pass,
+ viewport_rect,
+ SK_ColorYELLOW,
+ pixel_aligned_transform_causing_aa);
+
+ renderer_->DecideRenderPassAllocationsForFrame(
+ *mock_client_.render_passes_in_draw_order());
+ renderer_->DrawFrame(mock_client_.render_passes_in_draw_order());
+
+ TestSolidColorProgramAA();
+}
+
+class OutputSurfaceMockContext : public TestWebGraphicsContext3D {
+ public:
+ // 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 GL calls directly. Instead these are supposed to go
+ // through the OutputSurface abstraction.
+ MOCK_METHOD0(ensureBackbufferCHROMIUM, void());
+ MOCK_METHOD0(discardBackbufferCHROMIUM, void());
+ MOCK_METHOD2(bindFramebuffer, void(WGC3Denum target, WebGLId framebuffer));
+ MOCK_METHOD0(prepareTexture, void());
+ MOCK_METHOD3(reshapeWithScaleFactor,
+ void(int width, int height, float scale_factor));
+ MOCK_METHOD4(drawElements,
+ void(WGC3Denum mode,
+ WGC3Dsizei count,
+ WGC3Denum type,
+ WGC3Dintptr offset));
+
+ virtual WebString getString(WebKit::WGC3Denum name) {
+ if (name == GL_EXTENSIONS)
+ return WebString(
+ "GL_CHROMIUM_post_sub_buffer GL_CHROMIUM_discard_backbuffer");
+ return WebString();
+ }
+};
+
+class MockOutputSurface : public OutputSurface {
+ public:
+ MockOutputSurface()
+ : OutputSurface(scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new StrictMock<OutputSurfaceMockContext>)) {
+ surface_size_ = gfx::Size(100, 100);
+ }
+ virtual ~MockOutputSurface() {}
+
+ MOCK_METHOD0(EnsureBackbuffer, void());
+ MOCK_METHOD0(DiscardBackbuffer, void());
+ MOCK_METHOD2(Reshape, void(gfx::Size size, float scale_factor));
+ MOCK_METHOD0(BindFramebuffer, void());
+ MOCK_METHOD1(SwapBuffers, void(CompositorFrame* frame));
+};
+
+class MockOutputSurfaceTest : public testing::Test, public FakeRendererClient {
+ protected:
+ MockOutputSurfaceTest()
+ : resource_provider_(ResourceProvider::Create(&output_surface_, 0)),
+ renderer_(this, &output_surface_, resource_provider_.get()) {}
+
+ virtual void SetUp() { EXPECT_TRUE(renderer_.Initialize()); }
+
+ void SwapBuffers() { renderer_.SwapBuffers(); }
+
+ void DrawFrame() {
+ gfx::Rect viewport_rect(DeviceViewport());
+ ScopedPtrVector<RenderPass>* render_passes = render_passes_in_draw_order();
+ render_passes->clear();
+
+ RenderPass::Id render_pass_id(1, 0);
+ TestRenderPass* render_pass = AddRenderPass(
+ render_passes, render_pass_id, viewport_rect, gfx::Transform());
+ AddQuad(render_pass, viewport_rect, SK_ColorGREEN);
+
+ EXPECT_CALL(output_surface_, EnsureBackbuffer()).WillRepeatedly(Return());
+
+ EXPECT_CALL(output_surface_,
+ Reshape(DeviceViewport().size(), DeviceScaleFactor())).Times(1);
+
+ EXPECT_CALL(output_surface_, BindFramebuffer()).Times(1);
+
+ EXPECT_CALL(*Context(), drawElements(_, _, _, _)).Times(1);
+
+ renderer_.DecideRenderPassAllocationsForFrame(
+ *render_passes_in_draw_order());
+ renderer_.DrawFrame(render_passes_in_draw_order());
+ }
+
+ OutputSurfaceMockContext* Context() {
+ return static_cast<OutputSurfaceMockContext*>(output_surface_.context3d());
+ }
+
+ StrictMock<MockOutputSurface> output_surface_;
+ scoped_ptr<ResourceProvider> resource_provider_;
+ FakeRendererGL renderer_;
+};
+
+TEST_F(MockOutputSurfaceTest, DrawFrameAndSwap) {
+ DrawFrame();
+
+ EXPECT_CALL(output_surface_, SwapBuffers(_)).Times(1);
+ renderer_.SwapBuffers();
+}
+
+TEST_F(MockOutputSurfaceTest, DrawFrameAndResizeAndSwap) {
+ DrawFrame();
+ EXPECT_CALL(output_surface_, SwapBuffers(_)).Times(1);
+ renderer_.SwapBuffers();
+
+ set_viewport_and_scale(gfx::Size(2, 2), 2.f);
+ renderer_.ViewportChanged();
+
+ DrawFrame();
+ EXPECT_CALL(output_surface_, SwapBuffers(_)).Times(1);
+ renderer_.SwapBuffers();
+
+ DrawFrame();
+ EXPECT_CALL(output_surface_, SwapBuffers(_)).Times(1);
+ renderer_.SwapBuffers();
+
+ set_viewport_and_scale(gfx::Size(1, 1), 1.f);
+ renderer_.ViewportChanged();
+
+ DrawFrame();
+ EXPECT_CALL(output_surface_, SwapBuffers(_)).Times(1);
+ renderer_.SwapBuffers();
+}
+
+class GLRendererTestSyncPoint : public GLRendererPixelTest {
+ protected:
+ static void SyncPointCallback(int* callback_count) {
+ ++(*callback_count);
+ base::MessageLoop::current()->QuitWhenIdle();
+ }
+
+ static void OtherCallback(int* callback_count) {
+ ++(*callback_count);
+ base::MessageLoop::current()->QuitWhenIdle();
+ }
+};
+
+#if !defined(OS_ANDROID)
+TEST_F(GLRendererTestSyncPoint, SignalSyncPointOnLostContext) {
+ int sync_point_callback_count = 0;
+ int other_callback_count = 0;
+ unsigned sync_point = output_surface_->context3d()->insertSyncPoint();
+
+ output_surface_->context3d()->loseContextCHROMIUM(
+ GL_GUILTY_CONTEXT_RESET_ARB, GL_INNOCENT_CONTEXT_RESET_ARB);
+
+ SyncPointHelper::SignalSyncPoint(
+ output_surface_->context3d(),
+ sync_point,
+ base::Bind(&SyncPointCallback, &sync_point_callback_count));
+ EXPECT_EQ(0, sync_point_callback_count);
+ EXPECT_EQ(0, other_callback_count);
+
+ // Make the sync point happen.
+ output_surface_->context3d()->finish();
+ // Post a task after the sync point.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&OtherCallback, &other_callback_count));
+
+ base::MessageLoop::current()->Run();
+
+ // The sync point shouldn't have happened since the context was lost.
+ EXPECT_EQ(0, sync_point_callback_count);
+ EXPECT_EQ(1, other_callback_count);
+}
+
+TEST_F(GLRendererTestSyncPoint, SignalSyncPoint) {
+ int sync_point_callback_count = 0;
+ int other_callback_count = 0;
+ unsigned sync_point = output_surface_->context3d()->insertSyncPoint();
+
+ SyncPointHelper::SignalSyncPoint(
+ output_surface_->context3d(),
+ sync_point,
+ base::Bind(&SyncPointCallback, &sync_point_callback_count));
+ EXPECT_EQ(0, sync_point_callback_count);
+ EXPECT_EQ(0, other_callback_count);
+
+ // Make the sync point happen.
+ output_surface_->context3d()->finish();
+ // Post a task after the sync point.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&OtherCallback, &other_callback_count));
+
+ base::MessageLoop::current()->Run();
+
+ // The sync point should have happened.
+ EXPECT_EQ(1, sync_point_callback_count);
+ EXPECT_EQ(1, other_callback_count);
+}
+#endif // OS_ANDROID
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/output/managed_memory_policy.cc b/chromium/cc/output/managed_memory_policy.cc
new file mode 100644
index 00000000000..7e837662c29
--- /dev/null
+++ b/chromium/cc/output/managed_memory_policy.cc
@@ -0,0 +1,80 @@
+// Copyright 2013 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 "cc/output/managed_memory_policy.h"
+
+#include "base/logging.h"
+#include "cc/resources/priority_calculator.h"
+
+namespace cc {
+
+const size_t ManagedMemoryPolicy::kDefaultNumResourcesLimit = 10 * 1000 * 1000;
+
+ManagedMemoryPolicy::ManagedMemoryPolicy(size_t bytes_limit_when_visible)
+ : bytes_limit_when_visible(bytes_limit_when_visible),
+ priority_cutoff_when_visible(CUTOFF_ALLOW_EVERYTHING),
+ bytes_limit_when_not_visible(0),
+ priority_cutoff_when_not_visible(CUTOFF_ALLOW_NOTHING),
+ num_resources_limit(kDefaultNumResourcesLimit) {}
+
+ManagedMemoryPolicy::ManagedMemoryPolicy(
+ size_t bytes_limit_when_visible,
+ PriorityCutoff priority_cutoff_when_visible,
+ size_t bytes_limit_when_not_visible,
+ PriorityCutoff priority_cutoff_when_not_visible,
+ size_t num_resources_limit)
+ : bytes_limit_when_visible(bytes_limit_when_visible),
+ priority_cutoff_when_visible(priority_cutoff_when_visible),
+ bytes_limit_when_not_visible(bytes_limit_when_not_visible),
+ priority_cutoff_when_not_visible(priority_cutoff_when_not_visible),
+ num_resources_limit(num_resources_limit) {}
+
+bool ManagedMemoryPolicy::operator==(const ManagedMemoryPolicy& other) const {
+ return bytes_limit_when_visible == other.bytes_limit_when_visible &&
+ priority_cutoff_when_visible == other.priority_cutoff_when_visible &&
+ bytes_limit_when_not_visible == other.bytes_limit_when_not_visible &&
+ priority_cutoff_when_not_visible ==
+ other.priority_cutoff_when_not_visible &&
+ num_resources_limit == other.num_resources_limit;
+}
+
+bool ManagedMemoryPolicy::operator!=(const ManagedMemoryPolicy& other) const {
+ return !(*this == other);
+}
+
+// static
+int ManagedMemoryPolicy::PriorityCutoffToValue(PriorityCutoff priority_cutoff) {
+ switch (priority_cutoff) {
+ case CUTOFF_ALLOW_NOTHING:
+ return PriorityCalculator::AllowNothingCutoff();
+ case CUTOFF_ALLOW_REQUIRED_ONLY:
+ return PriorityCalculator::AllowVisibleOnlyCutoff();
+ case CUTOFF_ALLOW_NICE_TO_HAVE:
+ return PriorityCalculator::AllowVisibleAndNearbyCutoff();
+ case CUTOFF_ALLOW_EVERYTHING:
+ return PriorityCalculator::AllowEverythingCutoff();
+ }
+ NOTREACHED();
+ return PriorityCalculator::AllowNothingCutoff();
+}
+
+// static
+TileMemoryLimitPolicy
+ManagedMemoryPolicy::PriorityCutoffToTileMemoryLimitPolicy(
+ PriorityCutoff priority_cutoff) {
+ switch (priority_cutoff) {
+ case CUTOFF_ALLOW_NOTHING:
+ return ALLOW_NOTHING;
+ case CUTOFF_ALLOW_REQUIRED_ONLY:
+ return ALLOW_ABSOLUTE_MINIMUM;
+ case CUTOFF_ALLOW_NICE_TO_HAVE:
+ return ALLOW_PREPAINT_ONLY;
+ case CUTOFF_ALLOW_EVERYTHING:
+ return ALLOW_ANYTHING;
+ }
+ NOTREACHED();
+ return ALLOW_NOTHING;
+}
+
+} // namespace cc
diff --git a/chromium/cc/output/managed_memory_policy.h b/chromium/cc/output/managed_memory_policy.h
new file mode 100644
index 00000000000..5a607a7a1f2
--- /dev/null
+++ b/chromium/cc/output/managed_memory_policy.h
@@ -0,0 +1,45 @@
+// Copyright 2013 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.
+
+#ifndef CC_OUTPUT_MANAGED_MEMORY_POLICY_H_
+#define CC_OUTPUT_MANAGED_MEMORY_POLICY_H_
+
+#include "base/basictypes.h"
+#include "cc/base/cc_export.h"
+#include "cc/resources/tile_priority.h"
+
+namespace cc {
+
+struct CC_EXPORT ManagedMemoryPolicy {
+ enum PriorityCutoff {
+ CUTOFF_ALLOW_NOTHING,
+ CUTOFF_ALLOW_REQUIRED_ONLY,
+ CUTOFF_ALLOW_NICE_TO_HAVE,
+ CUTOFF_ALLOW_EVERYTHING,
+ };
+ static const size_t kDefaultNumResourcesLimit;
+
+ explicit ManagedMemoryPolicy(size_t bytes_limit_when_visible);
+ ManagedMemoryPolicy(size_t bytes_limit_when_visible,
+ PriorityCutoff priority_cutoff_when_visible,
+ size_t bytes_limit_when_not_visible,
+ PriorityCutoff priority_cutoff_when_not_visible,
+ size_t num_resources_limit);
+ bool operator==(const ManagedMemoryPolicy&) const;
+ bool operator!=(const ManagedMemoryPolicy&) const;
+
+ size_t bytes_limit_when_visible;
+ PriorityCutoff priority_cutoff_when_visible;
+ size_t bytes_limit_when_not_visible;
+ PriorityCutoff priority_cutoff_when_not_visible;
+ size_t num_resources_limit;
+
+ static int PriorityCutoffToValue(PriorityCutoff priority_cutoff);
+ static TileMemoryLimitPolicy PriorityCutoffToTileMemoryLimitPolicy(
+ PriorityCutoff priority_cutoff);
+};
+
+} // namespace cc
+
+#endif // CC_OUTPUT_MANAGED_MEMORY_POLICY_H_
diff --git a/chromium/cc/output/output_surface.cc b/chromium/cc/output/output_surface.cc
new file mode 100644
index 00000000000..5d2c6bb6e9f
--- /dev/null
+++ b/chromium/cc/output/output_surface.cc
@@ -0,0 +1,461 @@
+// Copyright (c) 2013 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 "cc/output/output_surface.h"
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/output/managed_memory_policy.h"
+#include "cc/output/output_surface_client.h"
+#include "cc/scheduler/delay_based_time_source.h"
+#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h"
+#include "third_party/WebKit/public/platform/WebGraphicsMemoryAllocation.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "third_party/khronos/GLES2/gl2ext.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size.h"
+
+using std::set;
+using std::string;
+using std::vector;
+
+namespace cc {
+namespace {
+
+ManagedMemoryPolicy::PriorityCutoff ConvertPriorityCutoff(
+ WebKit::WebGraphicsMemoryAllocation::PriorityCutoff priority_cutoff) {
+ // This is simple a 1:1 map, the names differ only because the WebKit names
+ // should be to match the cc names.
+ switch (priority_cutoff) {
+ case WebKit::WebGraphicsMemoryAllocation::PriorityCutoffAllowNothing:
+ return ManagedMemoryPolicy::CUTOFF_ALLOW_NOTHING;
+ case WebKit::WebGraphicsMemoryAllocation::PriorityCutoffAllowVisibleOnly:
+ return ManagedMemoryPolicy::CUTOFF_ALLOW_REQUIRED_ONLY;
+ case WebKit::WebGraphicsMemoryAllocation::
+ PriorityCutoffAllowVisibleAndNearby:
+ return ManagedMemoryPolicy::CUTOFF_ALLOW_NICE_TO_HAVE;
+ case WebKit::WebGraphicsMemoryAllocation::PriorityCutoffAllowEverything:
+ return ManagedMemoryPolicy::CUTOFF_ALLOW_EVERYTHING;
+ }
+ NOTREACHED();
+ return ManagedMemoryPolicy::CUTOFF_ALLOW_NOTHING;
+}
+
+} // anonymous namespace
+
+class OutputSurfaceCallbacks
+ : public WebKit::WebGraphicsContext3D::
+ WebGraphicsSwapBuffersCompleteCallbackCHROMIUM,
+ public WebKit::WebGraphicsContext3D::WebGraphicsContextLostCallback,
+ public WebKit::WebGraphicsContext3D::
+ WebGraphicsMemoryAllocationChangedCallbackCHROMIUM {
+ public:
+ explicit OutputSurfaceCallbacks(OutputSurface* client)
+ : client_(client) {
+ DCHECK(client_);
+ }
+
+ // WK:WGC3D::WGSwapBuffersCompleteCallbackCHROMIUM implementation.
+ virtual void onSwapBuffersComplete() { client_->OnSwapBuffersComplete(NULL); }
+
+ // WK:WGC3D::WGContextLostCallback implementation.
+ virtual void onContextLost() { client_->DidLoseOutputSurface(); }
+
+ // WK:WGC3D::WGMemoryAllocationChangedCallbackCHROMIUM implementation.
+ virtual void onMemoryAllocationChanged(
+ WebKit::WebGraphicsMemoryAllocation allocation) {
+ ManagedMemoryPolicy policy(
+ allocation.bytesLimitWhenVisible,
+ ConvertPriorityCutoff(allocation.priorityCutoffWhenVisible),
+ allocation.bytesLimitWhenNotVisible,
+ ConvertPriorityCutoff(allocation.priorityCutoffWhenNotVisible),
+ ManagedMemoryPolicy::kDefaultNumResourcesLimit);
+ bool discard_backbuffer = !allocation.suggestHaveBackbuffer;
+ client_->SetMemoryPolicy(policy, discard_backbuffer);
+ }
+
+ private:
+ OutputSurface* client_;
+};
+
+OutputSurface::OutputSurface(
+ scoped_ptr<WebKit::WebGraphicsContext3D> context3d)
+ : context3d_(context3d.Pass()),
+ has_gl_discard_backbuffer_(false),
+ has_swap_buffers_complete_callback_(false),
+ device_scale_factor_(-1),
+ weak_ptr_factory_(this),
+ max_frames_pending_(0),
+ pending_swap_buffers_(0),
+ needs_begin_frame_(false),
+ begin_frame_pending_(false),
+ client_(NULL),
+ check_for_retroactive_begin_frame_pending_(false) {
+}
+
+OutputSurface::OutputSurface(
+ scoped_ptr<cc::SoftwareOutputDevice> software_device)
+ : software_device_(software_device.Pass()),
+ has_gl_discard_backbuffer_(false),
+ has_swap_buffers_complete_callback_(false),
+ device_scale_factor_(-1),
+ weak_ptr_factory_(this),
+ max_frames_pending_(0),
+ pending_swap_buffers_(0),
+ needs_begin_frame_(false),
+ begin_frame_pending_(false),
+ client_(NULL),
+ check_for_retroactive_begin_frame_pending_(false) {
+}
+
+OutputSurface::OutputSurface(
+ scoped_ptr<WebKit::WebGraphicsContext3D> context3d,
+ scoped_ptr<cc::SoftwareOutputDevice> software_device)
+ : context3d_(context3d.Pass()),
+ software_device_(software_device.Pass()),
+ has_gl_discard_backbuffer_(false),
+ has_swap_buffers_complete_callback_(false),
+ device_scale_factor_(-1),
+ weak_ptr_factory_(this),
+ max_frames_pending_(0),
+ pending_swap_buffers_(0),
+ needs_begin_frame_(false),
+ begin_frame_pending_(false),
+ client_(NULL),
+ check_for_retroactive_begin_frame_pending_(false) {
+}
+
+void OutputSurface::InitializeBeginFrameEmulation(
+ base::SingleThreadTaskRunner* task_runner,
+ bool throttle_frame_production,
+ base::TimeDelta interval) {
+ if (throttle_frame_production) {
+ frame_rate_controller_.reset(
+ new FrameRateController(
+ DelayBasedTimeSource::Create(interval, task_runner)));
+ } else {
+ frame_rate_controller_.reset(new FrameRateController(task_runner));
+ }
+
+ frame_rate_controller_->SetClient(this);
+ frame_rate_controller_->SetMaxSwapsPending(max_frames_pending_);
+ frame_rate_controller_->SetDeadlineAdjustment(
+ capabilities_.adjust_deadline_for_parent ?
+ BeginFrameArgs::DefaultDeadlineAdjustment() : base::TimeDelta());
+
+ // The new frame rate controller will consume the swap acks of the old
+ // frame rate controller, so we set that expectation up here.
+ for (int i = 0; i < pending_swap_buffers_; i++)
+ frame_rate_controller_->DidSwapBuffers();
+}
+
+void OutputSurface::SetMaxFramesPending(int max_frames_pending) {
+ if (frame_rate_controller_)
+ frame_rate_controller_->SetMaxSwapsPending(max_frames_pending);
+ max_frames_pending_ = max_frames_pending;
+}
+
+void OutputSurface::OnVSyncParametersChanged(base::TimeTicks timebase,
+ base::TimeDelta interval) {
+ TRACE_EVENT2("cc", "OutputSurface::OnVSyncParametersChanged",
+ "timebase", (timebase - base::TimeTicks()).InSecondsF(),
+ "interval", interval.InSecondsF());
+ if (frame_rate_controller_)
+ frame_rate_controller_->SetTimebaseAndInterval(timebase, interval);
+}
+
+void OutputSurface::FrameRateControllerTick(bool throttled,
+ const BeginFrameArgs& args) {
+ DCHECK(frame_rate_controller_);
+ if (throttled)
+ skipped_begin_frame_args_ = args;
+ else
+ BeginFrame(args);
+}
+
+// Forwarded to OutputSurfaceClient
+void OutputSurface::SetNeedsRedrawRect(gfx::Rect damage_rect) {
+ TRACE_EVENT0("cc", "OutputSurface::SetNeedsRedrawRect");
+ client_->SetNeedsRedrawRect(damage_rect);
+}
+
+void OutputSurface::SetNeedsBeginFrame(bool enable) {
+ TRACE_EVENT1("cc", "OutputSurface::SetNeedsBeginFrame", "enable", enable);
+ needs_begin_frame_ = enable;
+ begin_frame_pending_ = false;
+ if (frame_rate_controller_) {
+ BeginFrameArgs skipped = frame_rate_controller_->SetActive(enable);
+ if (skipped.IsValid())
+ skipped_begin_frame_args_ = skipped;
+ }
+ if (needs_begin_frame_)
+ PostCheckForRetroactiveBeginFrame();
+}
+
+void OutputSurface::BeginFrame(const BeginFrameArgs& args) {
+ TRACE_EVENT2("cc", "OutputSurface::BeginFrame",
+ "begin_frame_pending_", begin_frame_pending_,
+ "pending_swap_buffers_", pending_swap_buffers_);
+ if (!needs_begin_frame_ || begin_frame_pending_ ||
+ (pending_swap_buffers_ >= max_frames_pending_ &&
+ max_frames_pending_ > 0)) {
+ skipped_begin_frame_args_ = args;
+ } else {
+ begin_frame_pending_ = true;
+ client_->BeginFrame(args);
+ // args might be an alias for skipped_begin_frame_args_.
+ // Do not reset it before calling BeginFrame!
+ skipped_begin_frame_args_ = BeginFrameArgs();
+ }
+}
+
+base::TimeDelta OutputSurface::AlternateRetroactiveBeginFramePeriod() {
+ return BeginFrameArgs::DefaultRetroactiveBeginFramePeriod();
+}
+
+void OutputSurface::PostCheckForRetroactiveBeginFrame() {
+ if (!skipped_begin_frame_args_.IsValid() ||
+ check_for_retroactive_begin_frame_pending_)
+ return;
+
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&OutputSurface::CheckForRetroactiveBeginFrame,
+ weak_ptr_factory_.GetWeakPtr()));
+ check_for_retroactive_begin_frame_pending_ = true;
+}
+
+void OutputSurface::CheckForRetroactiveBeginFrame() {
+ TRACE_EVENT0("cc", "OutputSurface::CheckForRetroactiveBeginFrame");
+ check_for_retroactive_begin_frame_pending_ = false;
+ base::TimeTicks now = base::TimeTicks::Now();
+ // TODO(brianderson): Remove the alternative deadline once we have better
+ // deadline estimations.
+ base::TimeTicks alternative_deadline =
+ skipped_begin_frame_args_.frame_time +
+ AlternateRetroactiveBeginFramePeriod();
+ if (now < skipped_begin_frame_args_.deadline ||
+ now < alternative_deadline) {
+ BeginFrame(skipped_begin_frame_args_);
+ }
+}
+
+void OutputSurface::DidSwapBuffers() {
+ begin_frame_pending_ = false;
+ pending_swap_buffers_++;
+ TRACE_EVENT1("cc", "OutputSurface::DidSwapBuffers",
+ "pending_swap_buffers_", pending_swap_buffers_);
+ if (frame_rate_controller_)
+ frame_rate_controller_->DidSwapBuffers();
+ PostCheckForRetroactiveBeginFrame();
+}
+
+void OutputSurface::OnSwapBuffersComplete(const CompositorFrameAck* ack) {
+ pending_swap_buffers_--;
+ TRACE_EVENT1("cc", "OutputSurface::OnSwapBuffersComplete",
+ "pending_swap_buffers_", pending_swap_buffers_);
+ client_->OnSwapBuffersComplete(ack);
+ if (frame_rate_controller_)
+ frame_rate_controller_->DidSwapBuffersComplete();
+ PostCheckForRetroactiveBeginFrame();
+}
+
+void OutputSurface::DidLoseOutputSurface() {
+ TRACE_EVENT0("cc", "OutputSurface::DidLoseOutputSurface");
+ begin_frame_pending_ = false;
+ pending_swap_buffers_ = 0;
+ client_->DidLoseOutputSurface();
+}
+
+void OutputSurface::SetExternalStencilTest(bool enabled) {
+ client_->SetExternalStencilTest(enabled);
+}
+
+void OutputSurface::SetExternalDrawConstraints(const gfx::Transform& transform,
+ gfx::Rect viewport) {
+ client_->SetExternalDrawConstraints(transform, viewport);
+}
+
+OutputSurface::~OutputSurface() {
+ if (frame_rate_controller_)
+ frame_rate_controller_->SetActive(false);
+
+ if (context3d_) {
+ context3d_->setSwapBuffersCompleteCallbackCHROMIUM(NULL);
+ context3d_->setContextLostCallback(NULL);
+ context3d_->setMemoryAllocationChangedCallbackCHROMIUM(NULL);
+ }
+}
+
+bool OutputSurface::ForcedDrawToSoftwareDevice() const {
+ return false;
+}
+
+bool OutputSurface::BindToClient(cc::OutputSurfaceClient* client) {
+ DCHECK(client);
+ client_ = client;
+ bool success = true;
+
+ if (context3d_) {
+ success = context3d_->makeContextCurrent();
+ if (success)
+ SetContext3D(context3d_.Pass());
+ }
+
+ if (!success)
+ client_ = NULL;
+
+ return success;
+}
+
+bool OutputSurface::InitializeAndSetContext3D(
+ scoped_ptr<WebKit::WebGraphicsContext3D> context3d,
+ scoped_refptr<ContextProvider> offscreen_context_provider) {
+ DCHECK(!context3d_);
+ DCHECK(context3d);
+ DCHECK(client_);
+
+ bool success = false;
+ if (context3d->makeContextCurrent()) {
+ SetContext3D(context3d.Pass());
+ if (client_->DeferredInitialize(offscreen_context_provider))
+ success = true;
+ }
+
+ if (!success)
+ ResetContext3D();
+
+ return success;
+}
+
+void OutputSurface::ReleaseGL() {
+ DCHECK(client_);
+ DCHECK(context3d_);
+ client_->ReleaseGL();
+ ResetContext3D();
+}
+
+void OutputSurface::SetContext3D(
+ scoped_ptr<WebKit::WebGraphicsContext3D> context3d) {
+ DCHECK(!context3d_);
+ DCHECK(context3d);
+ DCHECK(client_);
+
+ string extensions_string = UTF16ToASCII(context3d->getString(GL_EXTENSIONS));
+ vector<string> extensions_list;
+ base::SplitString(extensions_string, ' ', &extensions_list);
+ set<string> extensions(extensions_list.begin(), extensions_list.end());
+ has_gl_discard_backbuffer_ =
+ extensions.count("GL_CHROMIUM_discard_backbuffer") > 0;
+ has_swap_buffers_complete_callback_ =
+ extensions.count("GL_CHROMIUM_swapbuffers_complete_callback") > 0;
+
+
+ context3d_ = context3d.Pass();
+ callbacks_.reset(new OutputSurfaceCallbacks(this));
+ context3d_->setSwapBuffersCompleteCallbackCHROMIUM(callbacks_.get());
+ context3d_->setContextLostCallback(callbacks_.get());
+ context3d_->setMemoryAllocationChangedCallbackCHROMIUM(callbacks_.get());
+}
+
+void OutputSurface::ResetContext3D() {
+ context3d_.reset();
+ callbacks_.reset();
+}
+
+void OutputSurface::EnsureBackbuffer() {
+ DCHECK(context3d_);
+ if (has_gl_discard_backbuffer_)
+ context3d_->ensureBackbufferCHROMIUM();
+}
+
+void OutputSurface::DiscardBackbuffer() {
+ DCHECK(context3d_);
+ if (has_gl_discard_backbuffer_)
+ context3d_->discardBackbufferCHROMIUM();
+}
+
+void OutputSurface::Reshape(gfx::Size size, float scale_factor) {
+ if (size == surface_size_ && scale_factor == device_scale_factor_)
+ return;
+
+ surface_size_ = size;
+ device_scale_factor_ = scale_factor;
+ if (context3d_) {
+ context3d_->reshapeWithScaleFactor(
+ size.width(), size.height(), scale_factor);
+ }
+ if (software_device_)
+ software_device_->Resize(size);
+}
+
+gfx::Size OutputSurface::SurfaceSize() const {
+ return surface_size_;
+}
+
+void OutputSurface::BindFramebuffer() {
+ DCHECK(context3d_);
+ context3d_->bindFramebuffer(GL_FRAMEBUFFER, 0);
+}
+
+void OutputSurface::SwapBuffers(cc::CompositorFrame* frame) {
+ if (frame->software_frame_data) {
+ PostSwapBuffersComplete();
+ DidSwapBuffers();
+ return;
+ }
+
+ DCHECK(context3d_);
+ DCHECK(frame->gl_frame_data);
+
+ if (frame->gl_frame_data->sub_buffer_rect ==
+ gfx::Rect(frame->gl_frame_data->size)) {
+ // Note that currently this has the same effect as SwapBuffers; we should
+ // consider exposing a different entry point on WebGraphicsContext3D.
+ context3d()->prepareTexture();
+ } else {
+ gfx::Rect sub_buffer_rect = frame->gl_frame_data->sub_buffer_rect;
+ context3d()->postSubBufferCHROMIUM(sub_buffer_rect.x(),
+ sub_buffer_rect.y(),
+ sub_buffer_rect.width(),
+ sub_buffer_rect.height());
+ }
+
+ if (!has_swap_buffers_complete_callback_)
+ PostSwapBuffersComplete();
+
+ DidSwapBuffers();
+}
+
+void OutputSurface::PostSwapBuffersComplete() {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&OutputSurface::OnSwapBuffersComplete,
+ weak_ptr_factory_.GetWeakPtr(),
+ static_cast<CompositorFrameAck*>(NULL)));
+}
+
+void OutputSurface::SetMemoryPolicy(const ManagedMemoryPolicy& policy,
+ bool discard_backbuffer) {
+ TRACE_EVENT2("cc", "OutputSurface::SetMemoryPolicy",
+ "bytes_limit_when_visible", policy.bytes_limit_when_visible,
+ "discard_backbuffer", discard_backbuffer);
+ // Just ignore the memory manager when it says to set the limit to zero
+ // bytes. This will happen when the memory manager thinks that the renderer
+ // is not visible (which the renderer knows better).
+ if (policy.bytes_limit_when_visible)
+ client_->SetMemoryPolicy(policy);
+ client_->SetDiscardBackBufferWhenNotVisible(discard_backbuffer);
+}
+
+} // namespace cc
diff --git a/chromium/cc/output/output_surface.h b/chromium/cc/output/output_surface.h
new file mode 100644
index 00000000000..73f1c750851
--- /dev/null
+++ b/chromium/cc/output/output_surface.h
@@ -0,0 +1,203 @@
+// 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.
+
+#ifndef CC_OUTPUT_OUTPUT_SURFACE_H_
+#define CC_OUTPUT_OUTPUT_SURFACE_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/output/context_provider.h"
+#include "cc/output/software_output_device.h"
+#include "cc/scheduler/frame_rate_controller.h"
+#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h"
+
+namespace base { class SingleThreadTaskRunner; }
+
+namespace ui { struct LatencyInfo; }
+
+namespace gfx {
+class Rect;
+class Size;
+class Transform;
+}
+
+namespace cc {
+
+class CompositorFrame;
+class CompositorFrameAck;
+struct ManagedMemoryPolicy;
+class OutputSurfaceClient;
+class OutputSurfaceCallbacks;
+
+// Represents the output surface for a compositor. The compositor owns
+// and manages its destruction. Its lifetime is:
+// 1. Created on the main thread by the LayerTreeHost through its client.
+// 2. Passed to the compositor thread and bound to a client via BindToClient.
+// From here on, it will only be used on the compositor thread.
+// 3. If the 3D context is lost, then the compositor will delete the output
+// surface (on the compositor thread) and go back to step 1.
+class CC_EXPORT OutputSurface : public FrameRateControllerClient {
+ public:
+ enum {
+ DEFAULT_MAX_FRAMES_PENDING = 2
+ };
+
+ explicit OutputSurface(scoped_ptr<WebKit::WebGraphicsContext3D> context3d);
+
+ explicit OutputSurface(scoped_ptr<cc::SoftwareOutputDevice> software_device);
+
+ OutputSurface(scoped_ptr<WebKit::WebGraphicsContext3D> context3d,
+ scoped_ptr<cc::SoftwareOutputDevice> software_device);
+
+ virtual ~OutputSurface();
+
+ struct Capabilities {
+ Capabilities()
+ : delegated_rendering(false),
+ max_frames_pending(0),
+ deferred_gl_initialization(false),
+ draw_and_swap_full_viewport_every_frame(false),
+ adjust_deadline_for_parent(true) {}
+ bool delegated_rendering;
+ int max_frames_pending;
+ bool deferred_gl_initialization;
+ bool draw_and_swap_full_viewport_every_frame;
+ // This doesn't handle the <webview> case, but once BeginFrame is
+ // supported natively, we shouldn't need adjust_deadline_for_parent.
+ bool adjust_deadline_for_parent;
+ };
+
+ const Capabilities& capabilities() const {
+ return capabilities_;
+ }
+
+ // Obtain the 3d context or the software device associated with this output
+ // surface. Either of these may return a null pointer, but not both.
+ // In the event of a lost context, the entire output surface should be
+ // recreated.
+ WebKit::WebGraphicsContext3D* context3d() const {
+ return context3d_.get();
+ }
+
+ SoftwareOutputDevice* software_device() const {
+ return software_device_.get();
+ }
+
+ // In the case where both the context3d and software_device are present
+ // (namely Android WebView), this is called to determine whether the software
+ // device should be used on the current frame.
+ virtual bool ForcedDrawToSoftwareDevice() const;
+
+ // Called by the compositor on the compositor thread. This is a place where
+ // thread-specific data for the output surface can be initialized, since from
+ // this point on the output surface will only be used on the compositor
+ // thread.
+ virtual bool BindToClient(OutputSurfaceClient* client);
+
+ void InitializeBeginFrameEmulation(
+ base::SingleThreadTaskRunner* task_runner,
+ bool throttle_frame_production,
+ base::TimeDelta interval);
+
+ void SetMaxFramesPending(int max_frames_pending);
+
+ virtual void EnsureBackbuffer();
+ virtual void DiscardBackbuffer();
+
+ virtual void Reshape(gfx::Size size, float scale_factor);
+ virtual gfx::Size SurfaceSize() const;
+
+ virtual void BindFramebuffer();
+
+ // The implementation may destroy or steal the contents of the CompositorFrame
+ // passed in (though it will not take ownership of the CompositorFrame
+ // itself).
+ virtual void SwapBuffers(CompositorFrame* frame);
+
+ // Notifies frame-rate smoothness preference. If true, all non-critical
+ // processing should be stopped, or lowered in priority.
+ virtual void UpdateSmoothnessTakesPriority(bool prefer_smoothness) {}
+
+ // Requests a BeginFrame notification from the output surface. The
+ // notification will be delivered by calling
+ // OutputSurfaceClient::BeginFrame until the callback is disabled.
+ virtual void SetNeedsBeginFrame(bool enable);
+
+ protected:
+ // Synchronously initialize context3d and enter hardware mode.
+ // This can only supported in threaded compositing mode.
+ // |offscreen_context_provider| should match what is returned by
+ // LayerTreeClient::OffscreenContextProviderForCompositorThread.
+ bool InitializeAndSetContext3D(
+ scoped_ptr<WebKit::WebGraphicsContext3D> context3d,
+ scoped_refptr<ContextProvider> offscreen_context_provider);
+ void ReleaseGL();
+
+ void PostSwapBuffersComplete();
+
+ struct cc::OutputSurface::Capabilities capabilities_;
+ scoped_ptr<OutputSurfaceCallbacks> callbacks_;
+ scoped_ptr<WebKit::WebGraphicsContext3D> context3d_;
+ scoped_ptr<cc::SoftwareOutputDevice> software_device_;
+ bool has_gl_discard_backbuffer_;
+ bool has_swap_buffers_complete_callback_;
+ gfx::Size surface_size_;
+ float device_scale_factor_;
+ base::WeakPtrFactory<OutputSurface> weak_ptr_factory_;
+
+ // The FrameRateController is deprecated.
+ // Platforms should move to native BeginFrames instead.
+ void OnVSyncParametersChanged(base::TimeTicks timebase,
+ base::TimeDelta interval);
+ virtual void FrameRateControllerTick(bool throttled,
+ const BeginFrameArgs& args) OVERRIDE;
+ scoped_ptr<FrameRateController> frame_rate_controller_;
+ int max_frames_pending_;
+ int pending_swap_buffers_;
+ bool needs_begin_frame_;
+ bool begin_frame_pending_;
+
+ // Forwarded to OutputSurfaceClient but threaded through OutputSurface
+ // first so OutputSurface has a chance to update the FrameRateController
+ bool HasClient() { return !!client_; }
+ void SetNeedsRedrawRect(gfx::Rect damage_rect);
+ void BeginFrame(const BeginFrameArgs& args);
+ void DidSwapBuffers();
+ void OnSwapBuffersComplete(const CompositorFrameAck* ack);
+ void DidLoseOutputSurface();
+ void SetExternalStencilTest(bool enabled);
+ void SetExternalDrawConstraints(const gfx::Transform& transform,
+ gfx::Rect viewport);
+
+ // virtual for testing.
+ virtual base::TimeDelta AlternateRetroactiveBeginFramePeriod();
+ virtual void PostCheckForRetroactiveBeginFrame();
+ void CheckForRetroactiveBeginFrame();
+
+ private:
+ OutputSurfaceClient* client_;
+ friend class OutputSurfaceCallbacks;
+
+ void SetContext3D(scoped_ptr<WebKit::WebGraphicsContext3D> context3d);
+ void ResetContext3D();
+ void SetMemoryPolicy(const ManagedMemoryPolicy& policy,
+ bool discard_backbuffer_when_not_visible);
+
+ // This stores a BeginFrame that we couldn't process immediately, but might
+ // process retroactively in the near future.
+ BeginFrameArgs skipped_begin_frame_args_;
+
+ // check_for_retroactive_begin_frame_pending_ is used to avoid posting
+ // redundant checks for a retroactive BeginFrame.
+ bool check_for_retroactive_begin_frame_pending_;
+
+ DISALLOW_COPY_AND_ASSIGN(OutputSurface);
+};
+
+} // namespace cc
+
+#endif // CC_OUTPUT_OUTPUT_SURFACE_H_
diff --git a/chromium/cc/output/output_surface_client.h b/chromium/cc/output/output_surface_client.h
new file mode 100644
index 00000000000..4d1762211bb
--- /dev/null
+++ b/chromium/cc/output/output_surface_client.h
@@ -0,0 +1,54 @@
+// 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.
+
+#ifndef CC_OUTPUT_OUTPUT_SURFACE_CLIENT_H_
+#define CC_OUTPUT_OUTPUT_SURFACE_CLIENT_H_
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/time/time.h"
+#include "cc/base/cc_export.h"
+#include "cc/output/begin_frame_args.h"
+#include "cc/output/context_provider.h"
+#include "ui/gfx/rect.h"
+
+namespace gfx {
+class Transform;
+}
+
+namespace cc {
+
+class CompositorFrameAck;
+struct ManagedMemoryPolicy;
+
+class CC_EXPORT OutputSurfaceClient {
+ public:
+ // Called to synchronously re-initialize using the Context3D. Upon returning
+ // the compositor should be able to draw using GL what was previously
+ // committed.
+ virtual bool DeferredInitialize(
+ scoped_refptr<ContextProvider> offscreen_context_provider) = 0;
+ virtual void ReleaseGL() = 0;
+ virtual void SetNeedsRedrawRect(gfx::Rect damage_rect) = 0;
+ virtual void BeginFrame(const BeginFrameArgs& args) = 0;
+ virtual void OnSwapBuffersComplete(const CompositorFrameAck* ack) = 0;
+ virtual void DidLoseOutputSurface() = 0;
+ virtual void SetExternalStencilTest(bool enabled) = 0;
+ virtual void SetExternalDrawConstraints(const gfx::Transform& transform,
+ gfx::Rect viewport) = 0;
+ virtual void SetDiscardBackBufferWhenNotVisible(bool discard) = 0;
+ virtual void SetMemoryPolicy(const ManagedMemoryPolicy& policy) = 0;
+ // If set, |callback| will be called subsequent to each new tree activation,
+ // regardless of the compositor visibility or damage. |callback| must remain
+ // valid for the lifetime of the OutputSurfaceClient or until unregisted --
+ // use SetTreeActivationCallback(base::Closure()) to unregister it.
+ virtual void SetTreeActivationCallback(const base::Closure& callback) = 0;
+
+ protected:
+ virtual ~OutputSurfaceClient() {}
+};
+
+} // namespace cc
+
+#endif // CC_OUTPUT_OUTPUT_SURFACE_CLIENT_H_
diff --git a/chromium/cc/output/output_surface_unittest.cc b/chromium/cc/output/output_surface_unittest.cc
new file mode 100644
index 00000000000..4f9e370e1f0
--- /dev/null
+++ b/chromium/cc/output/output_surface_unittest.cc
@@ -0,0 +1,428 @@
+// Copyright 2013 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 "cc/output/output_surface.h"
+
+#include "base/test/test_simple_task_runner.h"
+#include "cc/debug/test_web_graphics_context_3d.h"
+#include "cc/output/managed_memory_policy.h"
+#include "cc/output/output_surface_client.h"
+#include "cc/output/software_output_device.h"
+#include "cc/test/fake_output_surface.h"
+#include "cc/test/fake_output_surface_client.h"
+#include "cc/test/scheduler_test_common.h"
+#include "gpu/GLES2/gl2extchromium.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/WebKit/public/platform/WebGraphicsMemoryAllocation.h"
+
+using WebKit::WebGraphicsMemoryAllocation;
+
+namespace cc {
+namespace {
+
+class TestOutputSurface : public OutputSurface {
+ public:
+ explicit TestOutputSurface(scoped_ptr<WebKit::WebGraphicsContext3D> context3d)
+ : OutputSurface(context3d.Pass()) {}
+
+ explicit TestOutputSurface(
+ scoped_ptr<cc::SoftwareOutputDevice> software_device)
+ : OutputSurface(software_device.Pass()) {}
+
+ TestOutputSurface(scoped_ptr<WebKit::WebGraphicsContext3D> context3d,
+ scoped_ptr<cc::SoftwareOutputDevice> software_device)
+ : OutputSurface(context3d.Pass(), software_device.Pass()) {}
+
+ bool InitializeNewContext3D(
+ scoped_ptr<WebKit::WebGraphicsContext3D> new_context3d) {
+ return InitializeAndSetContext3D(new_context3d.Pass(),
+ scoped_refptr<ContextProvider>());
+ }
+
+ using OutputSurface::ReleaseGL;
+
+ bool HasClientForTesting() {
+ return HasClient();
+ }
+
+ void OnVSyncParametersChangedForTesting(base::TimeTicks timebase,
+ base::TimeDelta interval) {
+ OnVSyncParametersChanged(timebase, interval);
+ }
+
+ void BeginFrameForTesting() {
+ OutputSurface::BeginFrame(BeginFrameArgs::CreateExpiredForTesting());
+ }
+
+ void DidSwapBuffersForTesting() {
+ DidSwapBuffers();
+ }
+
+ int pending_swap_buffers() {
+ return pending_swap_buffers_;
+ }
+
+ void OnSwapBuffersCompleteForTesting() {
+ OnSwapBuffersComplete(NULL);
+ }
+
+ void SetAlternateRetroactiveBeginFramePeriod(base::TimeDelta period) {
+ alternate_retroactive_begin_frame_period_ = period;
+ }
+
+ protected:
+ virtual void PostCheckForRetroactiveBeginFrame() OVERRIDE {
+ // For testing purposes, we check immediately rather than posting a task.
+ CheckForRetroactiveBeginFrame();
+ }
+
+ virtual base::TimeDelta AlternateRetroactiveBeginFramePeriod() OVERRIDE {
+ return alternate_retroactive_begin_frame_period_;
+ }
+
+ base::TimeDelta alternate_retroactive_begin_frame_period_;
+};
+
+TEST(OutputSurfaceTest, ClientPointerIndicatesBindToClientSuccess) {
+ scoped_ptr<TestWebGraphicsContext3D> context3d =
+ TestWebGraphicsContext3D::Create();
+
+ TestOutputSurface output_surface(
+ context3d.PassAs<WebKit::WebGraphicsContext3D>());
+ EXPECT_FALSE(output_surface.HasClientForTesting());
+
+ FakeOutputSurfaceClient client;
+ EXPECT_TRUE(output_surface.BindToClient(&client));
+ EXPECT_TRUE(output_surface.HasClientForTesting());
+ EXPECT_FALSE(client.deferred_initialize_called());
+
+ // Verify DidLoseOutputSurface callback is hooked up correctly.
+ EXPECT_FALSE(client.did_lose_output_surface_called());
+ output_surface.context3d()->loseContextCHROMIUM(
+ GL_GUILTY_CONTEXT_RESET_ARB, GL_INNOCENT_CONTEXT_RESET_ARB);
+ EXPECT_TRUE(client.did_lose_output_surface_called());
+}
+
+TEST(OutputSurfaceTest, ClientPointerIndicatesBindToClientFailure) {
+ scoped_ptr<TestWebGraphicsContext3D> context3d =
+ TestWebGraphicsContext3D::Create();
+
+ // Lose the context so BindToClient fails.
+ context3d->set_times_make_current_succeeds(0);
+
+ TestOutputSurface output_surface(
+ context3d.PassAs<WebKit::WebGraphicsContext3D>());
+ EXPECT_FALSE(output_surface.HasClientForTesting());
+
+ FakeOutputSurfaceClient client;
+ EXPECT_FALSE(output_surface.BindToClient(&client));
+ EXPECT_FALSE(output_surface.HasClientForTesting());
+}
+
+class InitializeNewContext3D : public ::testing::Test {
+ public:
+ InitializeNewContext3D()
+ : context3d_(TestWebGraphicsContext3D::Create()),
+ output_surface_(
+ scoped_ptr<SoftwareOutputDevice>(new SoftwareOutputDevice)) {}
+
+ protected:
+ void BindOutputSurface() {
+ EXPECT_TRUE(output_surface_.BindToClient(&client_));
+ EXPECT_TRUE(output_surface_.HasClientForTesting());
+ }
+
+ void InitializeNewContextExpectFail() {
+ EXPECT_FALSE(output_surface_.InitializeNewContext3D(
+ context3d_.PassAs<WebKit::WebGraphicsContext3D>()));
+ EXPECT_TRUE(output_surface_.HasClientForTesting());
+
+ EXPECT_FALSE(output_surface_.context3d());
+ EXPECT_TRUE(output_surface_.software_device());
+ }
+
+ scoped_ptr<TestWebGraphicsContext3D> context3d_;
+ TestOutputSurface output_surface_;
+ FakeOutputSurfaceClient client_;
+};
+
+TEST_F(InitializeNewContext3D, Success) {
+ BindOutputSurface();
+ EXPECT_FALSE(client_.deferred_initialize_called());
+
+ EXPECT_TRUE(output_surface_.InitializeNewContext3D(
+ context3d_.PassAs<WebKit::WebGraphicsContext3D>()));
+ EXPECT_TRUE(client_.deferred_initialize_called());
+
+ EXPECT_FALSE(client_.did_lose_output_surface_called());
+ output_surface_.context3d()->loseContextCHROMIUM(
+ GL_GUILTY_CONTEXT_RESET_ARB, GL_INNOCENT_CONTEXT_RESET_ARB);
+ EXPECT_TRUE(client_.did_lose_output_surface_called());
+
+ output_surface_.ReleaseGL();
+ EXPECT_FALSE(output_surface_.context3d());
+}
+
+TEST_F(InitializeNewContext3D, Context3dMakeCurrentFails) {
+ BindOutputSurface();
+ context3d_->set_times_make_current_succeeds(0);
+ InitializeNewContextExpectFail();
+}
+
+TEST_F(InitializeNewContext3D, ClientDeferredInitializeFails) {
+ BindOutputSurface();
+ client_.set_deferred_initialize_result(false);
+ InitializeNewContextExpectFail();
+}
+
+TEST(OutputSurfaceTest, BeginFrameEmulation) {
+ scoped_ptr<TestWebGraphicsContext3D> context3d =
+ TestWebGraphicsContext3D::Create();
+
+ TestOutputSurface output_surface(
+ context3d.PassAs<WebKit::WebGraphicsContext3D>());
+ EXPECT_FALSE(output_surface.HasClientForTesting());
+
+ FakeOutputSurfaceClient client;
+ EXPECT_TRUE(output_surface.BindToClient(&client));
+ EXPECT_TRUE(output_surface.HasClientForTesting());
+ EXPECT_FALSE(client.deferred_initialize_called());
+
+ // Initialize BeginFrame emulation
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner =
+ new base::TestSimpleTaskRunner;
+ bool throttle_frame_production = true;
+ const base::TimeDelta display_refresh_interval =
+ BeginFrameArgs::DefaultInterval();
+
+ output_surface.InitializeBeginFrameEmulation(
+ task_runner.get(),
+ throttle_frame_production,
+ display_refresh_interval);
+
+ output_surface.SetMaxFramesPending(2);
+ output_surface.SetAlternateRetroactiveBeginFramePeriod(
+ base::TimeDelta::FromSeconds(-1));
+
+ // We should start off with 0 BeginFrames
+ EXPECT_EQ(client.begin_frame_count(), 0);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 0);
+
+ // We should not have a pending task until a BeginFrame has been requested.
+ EXPECT_FALSE(task_runner->HasPendingTask());
+ output_surface.SetNeedsBeginFrame(true);
+ EXPECT_TRUE(task_runner->HasPendingTask());
+
+ // BeginFrame should be called on the first tick.
+ task_runner->RunPendingTasks();
+ EXPECT_EQ(client.begin_frame_count(), 1);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 0);
+
+ // BeginFrame should not be called when there is a pending BeginFrame.
+ task_runner->RunPendingTasks();
+ EXPECT_EQ(client.begin_frame_count(), 1);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 0);
+
+ // DidSwapBuffers should clear the pending BeginFrame.
+ output_surface.DidSwapBuffersForTesting();
+ EXPECT_EQ(client.begin_frame_count(), 1);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 1);
+ task_runner->RunPendingTasks();
+ EXPECT_EQ(client.begin_frame_count(), 2);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 1);
+
+ // BeginFrame should be throttled by pending swap buffers.
+ output_surface.DidSwapBuffersForTesting();
+ EXPECT_EQ(client.begin_frame_count(), 2);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 2);
+ task_runner->RunPendingTasks();
+ EXPECT_EQ(client.begin_frame_count(), 2);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 2);
+
+ // SwapAck should decrement pending swap buffers and unblock BeginFrame again.
+ output_surface.OnSwapBuffersCompleteForTesting();
+ EXPECT_EQ(client.begin_frame_count(), 2);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 1);
+ task_runner->RunPendingTasks();
+ EXPECT_EQ(client.begin_frame_count(), 3);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 1);
+
+ // Calling SetNeedsBeginFrame again indicates a swap did not occur but
+ // the client still wants another BeginFrame.
+ output_surface.SetNeedsBeginFrame(true);
+ task_runner->RunPendingTasks();
+ EXPECT_EQ(client.begin_frame_count(), 4);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 1);
+
+ // Disabling SetNeedsBeginFrame should prevent further BeginFrames.
+ output_surface.SetNeedsBeginFrame(false);
+ task_runner->RunPendingTasks();
+ EXPECT_FALSE(task_runner->HasPendingTask());
+ EXPECT_EQ(client.begin_frame_count(), 4);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 1);
+}
+
+TEST(OutputSurfaceTest, OptimisticAndRetroactiveBeginFrames) {
+ scoped_ptr<TestWebGraphicsContext3D> context3d =
+ TestWebGraphicsContext3D::Create();
+
+ TestOutputSurface output_surface(
+ context3d.PassAs<WebKit::WebGraphicsContext3D>());
+ EXPECT_FALSE(output_surface.HasClientForTesting());
+
+ FakeOutputSurfaceClient client;
+ EXPECT_TRUE(output_surface.BindToClient(&client));
+ EXPECT_TRUE(output_surface.HasClientForTesting());
+ EXPECT_FALSE(client.deferred_initialize_called());
+
+ output_surface.SetMaxFramesPending(2);
+
+ // Enable retroactive BeginFrames.
+ output_surface.SetAlternateRetroactiveBeginFramePeriod(
+ base::TimeDelta::FromSeconds(100000));
+
+ // Optimistically injected BeginFrames should be throttled if
+ // SetNeedsBeginFrame is false...
+ output_surface.SetNeedsBeginFrame(false);
+ output_surface.BeginFrameForTesting();
+ EXPECT_EQ(client.begin_frame_count(), 0);
+ // ...and retroactively triggered by a SetNeedsBeginFrame.
+ output_surface.SetNeedsBeginFrame(true);
+ EXPECT_EQ(client.begin_frame_count(), 1);
+
+ // Optimistically injected BeginFrames should be throttled by pending
+ // BeginFrames...
+ output_surface.BeginFrameForTesting();
+ EXPECT_EQ(client.begin_frame_count(), 1);
+ // ...and retroactively triggered by a SetNeedsBeginFrame.
+ output_surface.SetNeedsBeginFrame(true);
+ EXPECT_EQ(client.begin_frame_count(), 2);
+ // ...or retroactively triggered by a Swap.
+ output_surface.BeginFrameForTesting();
+ EXPECT_EQ(client.begin_frame_count(), 2);
+ output_surface.DidSwapBuffersForTesting();
+ EXPECT_EQ(client.begin_frame_count(), 3);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 1);
+
+ // Optimistically injected BeginFrames should be by throttled by pending
+ // swap buffers...
+ output_surface.DidSwapBuffersForTesting();
+ EXPECT_EQ(client.begin_frame_count(), 3);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 2);
+ output_surface.BeginFrameForTesting();
+ EXPECT_EQ(client.begin_frame_count(), 3);
+ // ...and retroactively triggered by OnSwapBuffersComplete
+ output_surface.OnSwapBuffersCompleteForTesting();
+ EXPECT_EQ(client.begin_frame_count(), 4);
+}
+
+TEST(OutputSurfaceTest, RetroactiveBeginFrameDoesNotDoubleTickWhenEmulating) {
+ scoped_ptr<TestWebGraphicsContext3D> context3d =
+ TestWebGraphicsContext3D::Create();
+
+ TestOutputSurface output_surface(
+ context3d.PassAs<WebKit::WebGraphicsContext3D>());
+ EXPECT_FALSE(output_surface.HasClientForTesting());
+
+ FakeOutputSurfaceClient client;
+ EXPECT_TRUE(output_surface.BindToClient(&client));
+ EXPECT_TRUE(output_surface.HasClientForTesting());
+ EXPECT_FALSE(client.deferred_initialize_called());
+
+ base::TimeDelta big_interval = base::TimeDelta::FromSeconds(1000);
+
+ // Initialize BeginFrame emulation
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner =
+ new base::TestSimpleTaskRunner;
+ bool throttle_frame_production = true;
+ const base::TimeDelta display_refresh_interval = big_interval;
+
+ output_surface.InitializeBeginFrameEmulation(
+ task_runner.get(),
+ throttle_frame_production,
+ display_refresh_interval);
+
+ // We need to subtract an epsilon from Now() because some platforms have
+ // a slow clock.
+ output_surface.OnVSyncParametersChangedForTesting(
+ base::TimeTicks::Now() - base::TimeDelta::FromMilliseconds(1),
+ display_refresh_interval);
+
+ output_surface.SetMaxFramesPending(2);
+ output_surface.SetAlternateRetroactiveBeginFramePeriod(
+ base::TimeDelta::FromSeconds(-1));
+
+ // We should start off with 0 BeginFrames
+ EXPECT_EQ(client.begin_frame_count(), 0);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 0);
+
+ // The first SetNeedsBeginFrame(true) should start a retroactive BeginFrame.
+ output_surface.SetNeedsBeginFrame(true);
+ EXPECT_TRUE(task_runner->HasPendingTask());
+ EXPECT_GT(task_runner->NextPendingTaskDelay(), big_interval / 2);
+ EXPECT_EQ(client.begin_frame_count(), 1);
+
+ output_surface.SetNeedsBeginFrame(false);
+ EXPECT_TRUE(task_runner->HasPendingTask());
+ EXPECT_EQ(client.begin_frame_count(), 1);
+
+ // The second SetNeedBeginFrame(true) should not retroactively start a
+ // BeginFrame if the timestamp would be the same as the previous BeginFrame.
+ output_surface.SetNeedsBeginFrame(true);
+ EXPECT_TRUE(task_runner->HasPendingTask());
+ EXPECT_EQ(client.begin_frame_count(), 1);
+}
+
+TEST(OutputSurfaceTest, MemoryAllocation) {
+ scoped_ptr<TestWebGraphicsContext3D> scoped_context =
+ TestWebGraphicsContext3D::Create();
+ TestWebGraphicsContext3D* context = scoped_context.get();
+
+ TestOutputSurface output_surface(
+ scoped_context.PassAs<WebKit::WebGraphicsContext3D>());
+
+ FakeOutputSurfaceClient client;
+ EXPECT_TRUE(output_surface.BindToClient(&client));
+
+ WebGraphicsMemoryAllocation allocation;
+ allocation.suggestHaveBackbuffer = true;
+ allocation.bytesLimitWhenVisible = 1234;
+ allocation.priorityCutoffWhenVisible =
+ WebGraphicsMemoryAllocation::PriorityCutoffAllowVisibleOnly;
+ allocation.bytesLimitWhenNotVisible = 4567;
+ allocation.priorityCutoffWhenNotVisible =
+ WebGraphicsMemoryAllocation::PriorityCutoffAllowNothing;
+
+ context->SetMemoryAllocation(allocation);
+
+ EXPECT_EQ(1234u, client.memory_policy().bytes_limit_when_visible);
+ EXPECT_EQ(ManagedMemoryPolicy::CUTOFF_ALLOW_REQUIRED_ONLY,
+ client.memory_policy().priority_cutoff_when_visible);
+ EXPECT_EQ(4567u, client.memory_policy().bytes_limit_when_not_visible);
+ EXPECT_EQ(ManagedMemoryPolicy::CUTOFF_ALLOW_NOTHING,
+ client.memory_policy().priority_cutoff_when_not_visible);
+ EXPECT_FALSE(client.discard_backbuffer_when_not_visible());
+
+ allocation.suggestHaveBackbuffer = false;
+ context->SetMemoryAllocation(allocation);
+ EXPECT_TRUE(client.discard_backbuffer_when_not_visible());
+
+ allocation.priorityCutoffWhenVisible =
+ WebGraphicsMemoryAllocation::PriorityCutoffAllowEverything;
+ allocation.priorityCutoffWhenNotVisible =
+ WebGraphicsMemoryAllocation::PriorityCutoffAllowVisibleAndNearby;
+ context->SetMemoryAllocation(allocation);
+ EXPECT_EQ(ManagedMemoryPolicy::CUTOFF_ALLOW_EVERYTHING,
+ client.memory_policy().priority_cutoff_when_visible);
+ EXPECT_EQ(ManagedMemoryPolicy::CUTOFF_ALLOW_NICE_TO_HAVE,
+ client.memory_policy().priority_cutoff_when_not_visible);
+
+ // 0 bytes limit should be ignored.
+ allocation.bytesLimitWhenVisible = 0;
+ context->SetMemoryAllocation(allocation);
+ EXPECT_EQ(1234u, client.memory_policy().bytes_limit_when_visible);
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/output/program_binding.cc b/chromium/cc/output/program_binding.cc
new file mode 100644
index 00000000000..5b7b13ee06d
--- /dev/null
+++ b/chromium/cc/output/program_binding.cc
@@ -0,0 +1,150 @@
+// Copyright 2011 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 "cc/output/program_binding.h"
+
+#include "base/debug/trace_event.h"
+#include "cc/output/geometry_binding.h"
+#include "cc/output/gl_renderer.h" // For the GLC() macro.
+#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h"
+#include "third_party/khronos/GLES2/gl2.h"
+
+using WebKit::WebGraphicsContext3D;
+
+namespace cc {
+
+ProgramBindingBase::ProgramBindingBase()
+ : program_(0),
+ vertex_shader_id_(0),
+ fragment_shader_id_(0),
+ initialized_(false) {}
+
+ProgramBindingBase::~ProgramBindingBase() {
+ // If you hit these asserts, you initialized but forgot to call Cleanup().
+ DCHECK(!program_);
+ DCHECK(!vertex_shader_id_);
+ DCHECK(!fragment_shader_id_);
+ DCHECK(!initialized_);
+}
+
+void ProgramBindingBase::Init(WebGraphicsContext3D* context,
+ const std::string& vertex_shader,
+ const std::string& fragment_shader) {
+ TRACE_EVENT0("cc", "ProgramBindingBase::init");
+ vertex_shader_id_ = LoadShader(context, GL_VERTEX_SHADER, vertex_shader);
+ if (!vertex_shader_id_) {
+ if (!IsContextLost(context))
+ LOG(ERROR) << "Failed to create vertex shader";
+ return;
+ }
+
+ fragment_shader_id_ =
+ LoadShader(context, GL_FRAGMENT_SHADER, fragment_shader);
+ if (!fragment_shader_id_) {
+ GLC(context, context->deleteShader(vertex_shader_id_));
+ vertex_shader_id_ = 0;
+ if (!IsContextLost(context))
+ LOG(ERROR) << "Failed to create fragment shader";
+ return;
+ }
+
+ program_ =
+ CreateShaderProgram(context, vertex_shader_id_, fragment_shader_id_);
+ DCHECK(program_ || IsContextLost(context));
+}
+
+void ProgramBindingBase::Link(WebGraphicsContext3D* context) {
+ GLC(context, context->linkProgram(program_));
+ CleanupShaders(context);
+ if (!program_)
+ return;
+#ifndef NDEBUG
+ int linked = 0;
+ GLC(context, context->getProgramiv(program_, GL_LINK_STATUS, &linked));
+ if (!linked) {
+ if (!IsContextLost(context))
+ LOG(ERROR) << "Failed to link shader program";
+ GLC(context, context->deleteProgram(program_));
+ }
+#endif
+}
+
+void ProgramBindingBase::Cleanup(WebGraphicsContext3D* context) {
+ initialized_ = false;
+ if (!program_)
+ return;
+
+ DCHECK(context);
+ GLC(context, context->deleteProgram(program_));
+ program_ = 0;
+
+ CleanupShaders(context);
+}
+
+unsigned ProgramBindingBase::LoadShader(WebGraphicsContext3D* context,
+ unsigned type,
+ const std::string& shader_source) {
+ unsigned shader = context->createShader(type);
+ if (!shader)
+ return 0;
+ GLC(context, context->shaderSource(shader, shader_source.data()));
+ GLC(context, context->compileShader(shader));
+#ifndef NDEBUG
+ int compiled = 0;
+ GLC(context, context->getShaderiv(shader, GL_COMPILE_STATUS, &compiled));
+ if (!compiled) {
+ GLC(context, context->deleteShader(shader));
+ return 0;
+ }
+#endif
+ return shader;
+}
+
+unsigned ProgramBindingBase::CreateShaderProgram(WebGraphicsContext3D* context,
+ unsigned vertex_shader,
+ unsigned fragment_shader) {
+ unsigned program_object = context->createProgram();
+ if (!program_object) {
+ if (!IsContextLost(context))
+ LOG(ERROR) << "Failed to create shader program";
+ return 0;
+ }
+
+ GLC(context, context->attachShader(program_object, vertex_shader));
+ GLC(context, context->attachShader(program_object, fragment_shader));
+
+ // Bind the common attrib locations.
+ GLC(context,
+ context->bindAttribLocation(program_object,
+ GeometryBinding::PositionAttribLocation(),
+ "a_position"));
+ GLC(context,
+ context->bindAttribLocation(program_object,
+ GeometryBinding::TexCoordAttribLocation(),
+ "a_texCoord"));
+ GLC(context,
+ context->bindAttribLocation(
+ program_object,
+ GeometryBinding::TriangleIndexAttribLocation(),
+ "a_index"));
+
+ return program_object;
+}
+
+void ProgramBindingBase::CleanupShaders(WebGraphicsContext3D* context) {
+ if (vertex_shader_id_) {
+ GLC(context, context->deleteShader(vertex_shader_id_));
+ vertex_shader_id_ = 0;
+ }
+ if (fragment_shader_id_) {
+ GLC(context, context->deleteShader(fragment_shader_id_));
+ fragment_shader_id_ = 0;
+ }
+}
+
+bool ProgramBindingBase::IsContextLost(WebGraphicsContext3D* context) {
+ return (context->getGraphicsResetStatusARB() != GL_NO_ERROR);
+}
+
+} // namespace cc
diff --git a/chromium/cc/output/program_binding.h b/chromium/cc/output/program_binding.h
new file mode 100644
index 00000000000..ee9d284fa7f
--- /dev/null
+++ b/chromium/cc/output/program_binding.h
@@ -0,0 +1,98 @@
+// Copyright 2011 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.
+
+#ifndef CC_OUTPUT_PROGRAM_BINDING_H_
+#define CC_OUTPUT_PROGRAM_BINDING_H_
+
+#include <string>
+
+#include "base/logging.h"
+#include "cc/output/shader.h"
+
+namespace WebKit { class WebGraphicsContext3D; }
+
+namespace cc {
+
+class ProgramBindingBase {
+ public:
+ ProgramBindingBase();
+ ~ProgramBindingBase();
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ const std::string& vertex_shader,
+ const std::string& fragment_shader);
+ void Link(WebKit::WebGraphicsContext3D* context);
+ void Cleanup(WebKit::WebGraphicsContext3D* context);
+
+ unsigned program() const { return program_; }
+ bool initialized() const { return initialized_; }
+
+ protected:
+ unsigned LoadShader(WebKit::WebGraphicsContext3D* context,
+ unsigned type,
+ const std::string& shader_source);
+ unsigned CreateShaderProgram(WebKit::WebGraphicsContext3D* context,
+ unsigned vertex_shader,
+ unsigned fragment_shader);
+ void CleanupShaders(WebKit::WebGraphicsContext3D* context);
+ bool IsContextLost(WebKit::WebGraphicsContext3D* context);
+
+ unsigned program_;
+ unsigned vertex_shader_id_;
+ unsigned fragment_shader_id_;
+ bool initialized_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ProgramBindingBase);
+};
+
+template <class VertexShader, class FragmentShader>
+class ProgramBinding : public ProgramBindingBase {
+ public:
+ explicit ProgramBinding(WebKit::WebGraphicsContext3D* context,
+ TexCoordPrecision precision) {
+ ProgramBindingBase::Init(
+ context,
+ vertex_shader_.GetShaderString(),
+ fragment_shader_.GetShaderString(precision));
+ }
+
+ void Initialize(WebKit::WebGraphicsContext3D* context,
+ bool using_bind_uniform) {
+ DCHECK(context);
+ DCHECK(!initialized_);
+
+ if (IsContextLost(context))
+ return;
+
+ // Need to bind uniforms before linking
+ if (!using_bind_uniform)
+ Link(context);
+
+ int base_uniform_index = 0;
+ vertex_shader_.Init(
+ context, program_, using_bind_uniform, &base_uniform_index);
+ fragment_shader_.Init(
+ context, program_, using_bind_uniform, &base_uniform_index);
+
+ // Link after binding uniforms
+ if (using_bind_uniform)
+ Link(context);
+
+ initialized_ = true;
+ }
+
+ const VertexShader& vertex_shader() const { return vertex_shader_; }
+ const FragmentShader& fragment_shader() const { return fragment_shader_; }
+
+ private:
+ VertexShader vertex_shader_;
+ FragmentShader fragment_shader_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProgramBinding);
+};
+
+} // namespace cc
+
+#endif // CC_OUTPUT_PROGRAM_BINDING_H_
diff --git a/chromium/cc/output/render_surface_filters.cc b/chromium/cc/output/render_surface_filters.cc
new file mode 100644
index 00000000000..fa84e67f4d3
--- /dev/null
+++ b/chromium/cc/output/render_surface_filters.cc
@@ -0,0 +1,462 @@
+// 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 "cc/output/render_surface_filters.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "cc/output/filter_operation.h"
+#include "cc/output/filter_operations.h"
+#include "skia/ext/refptr.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/effects/SkBlurImageFilter.h"
+#include "third_party/skia/include/effects/SkColorMatrixFilter.h"
+#include "third_party/skia/include/effects/SkMagnifierImageFilter.h"
+#include "third_party/skia/include/gpu/SkGpuDevice.h"
+#include "third_party/skia/include/gpu/SkGrPixelRef.h"
+#include "ui/gfx/size_f.h"
+
+namespace cc {
+
+namespace {
+
+void GetBrightnessMatrix(float amount, SkScalar matrix[20]) {
+ // Spec implementation
+ // (http://dvcs.w3.org/hg/FXTF/raw-file/tip/filters/index.html#brightnessEquivalent)
+ // <feFunc[R|G|B] type="linear" slope="[amount]">
+ memset(matrix, 0, 20 * sizeof(SkScalar));
+ matrix[0] = matrix[6] = matrix[12] = amount;
+ matrix[18] = 1.f;
+}
+
+void GetSaturatingBrightnessMatrix(float amount, SkScalar matrix[20]) {
+ // Legacy implementation used by internal clients.
+ // <feFunc[R|G|B] type="linear" intercept="[amount]"/>
+ memset(matrix, 0, 20 * sizeof(SkScalar));
+ matrix[0] = matrix[6] = matrix[12] = matrix[18] = 1.f;
+ matrix[4] = matrix[9] = matrix[14] = amount * 255.f;
+}
+
+void GetContrastMatrix(float amount, SkScalar matrix[20]) {
+ memset(matrix, 0, 20 * sizeof(SkScalar));
+ matrix[0] = matrix[6] = matrix[12] = amount;
+ matrix[4] = matrix[9] = matrix[14] = (-0.5f * amount + 0.5f) * 255.f;
+ matrix[18] = 1.f;
+}
+
+void GetSaturateMatrix(float amount, SkScalar matrix[20]) {
+ // Note, these values are computed to ensure MatrixNeedsClamping is false
+ // for amount in [0..1]
+ 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.f;
+ 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.f;
+ 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.f;
+ matrix[15] = matrix[16] = matrix[17] = matrix[19] = 0.f;
+ matrix[18] = 1.f;
+}
+
+void GetHueRotateMatrix(float hue, SkScalar matrix[20]) {
+ const float kPi = 3.1415926535897932384626433832795f;
+
+ float cos_hue = cosf(hue * kPi / 180.f);
+ float sin_hue = sinf(hue * kPi / 180.f);
+ matrix[0] = 0.213f + cos_hue * 0.787f - sin_hue * 0.213f;
+ matrix[1] = 0.715f - cos_hue * 0.715f - sin_hue * 0.715f;
+ matrix[2] = 0.072f - cos_hue * 0.072f + sin_hue * 0.928f;
+ matrix[3] = matrix[4] = 0.f;
+ matrix[5] = 0.213f - cos_hue * 0.213f + sin_hue * 0.143f;
+ matrix[6] = 0.715f + cos_hue * 0.285f + sin_hue * 0.140f;
+ matrix[7] = 0.072f - cos_hue * 0.072f - sin_hue * 0.283f;
+ matrix[8] = matrix[9] = 0.f;
+ matrix[10] = 0.213f - cos_hue * 0.213f - sin_hue * 0.787f;
+ matrix[11] = 0.715f - cos_hue * 0.715f + sin_hue * 0.715f;
+ matrix[12] = 0.072f + cos_hue * 0.928f + sin_hue * 0.072f;
+ matrix[13] = matrix[14] = 0.f;
+ matrix[15] = matrix[16] = matrix[17] = 0.f;
+ matrix[18] = 1.f;
+ matrix[19] = 0.f;
+}
+
+void GetInvertMatrix(float amount, SkScalar matrix[20]) {
+ memset(matrix, 0, 20 * sizeof(SkScalar));
+ matrix[0] = matrix[6] = matrix[12] = 1.f - 2.f * amount;
+ matrix[4] = matrix[9] = matrix[14] = amount * 255.f;
+ matrix[18] = 1.f;
+}
+
+void GetOpacityMatrix(float amount, SkScalar matrix[20]) {
+ memset(matrix, 0, 20 * sizeof(SkScalar));
+ matrix[0] = matrix[6] = matrix[12] = 1.f;
+ matrix[18] = amount;
+}
+
+void GetGrayscaleMatrix(float amount, SkScalar matrix[20]) {
+ // Note, these values are computed to ensure MatrixNeedsClamping is false
+ // for amount in [0..1]
+ matrix[0] = 0.2126f + 0.7874f * amount;
+ matrix[1] = 0.7152f - 0.7152f * amount;
+ matrix[2] = 1.f - (matrix[0] + matrix[1]);
+ matrix[3] = matrix[4] = 0.f;
+
+ matrix[5] = 0.2126f - 0.2126f * amount;
+ matrix[6] = 0.7152f + 0.2848f * amount;
+ matrix[7] = 1.f - (matrix[5] + matrix[6]);
+ matrix[8] = matrix[9] = 0.f;
+
+ matrix[10] = 0.2126f - 0.2126f * amount;
+ matrix[11] = 0.7152f - 0.7152f * amount;
+ matrix[12] = 1.f - (matrix[10] + matrix[11]);
+ matrix[13] = matrix[14] = 0.f;
+
+ matrix[15] = matrix[16] = matrix[17] = matrix[19] = 0.f;
+ matrix[18] = 1.f;
+}
+
+void GetSepiaMatrix(float amount, SkScalar matrix[20]) {
+ matrix[0] = 0.393f + 0.607f * amount;
+ matrix[1] = 0.769f - 0.769f * amount;
+ matrix[2] = 0.189f - 0.189f * amount;
+ matrix[3] = matrix[4] = 0.f;
+
+ matrix[5] = 0.349f - 0.349f * amount;
+ matrix[6] = 0.686f + 0.314f * amount;
+ matrix[7] = 0.168f - 0.168f * amount;
+ matrix[8] = matrix[9] = 0.f;
+
+ matrix[10] = 0.272f - 0.272f * amount;
+ matrix[11] = 0.534f - 0.534f * amount;
+ matrix[12] = 0.131f + 0.869f * amount;
+ matrix[13] = matrix[14] = 0.f;
+
+ matrix[15] = matrix[16] = matrix[17] = matrix[19] = 0.f;
+ matrix[18] = 1.f;
+}
+
+// The 5x4 matrix is really a "compressed" version of a 5x5 matrix that'd have
+// (0 0 0 0 1) as a last row, and that would be applied to a 5-vector extended
+// from the 4-vector color with a 1.
+void MultColorMatrix(SkScalar a[20], SkScalar b[20], SkScalar out[20]) {
+ for (int j = 0; j < 4; ++j) {
+ for (int i = 0; i < 5; ++i) {
+ out[i+j*5] = i == 4 ? a[4+j*5] : 0.f;
+ for (int k = 0; k < 4; ++k)
+ out[i+j*5] += a[k+j*5] * b[i+k*5];
+ }
+ }
+}
+
+// To detect if we need to apply clamping after applying a matrix, we check if
+// any output component might go outside of [0, 255] for any combination of
+// input components in [0..255].
+// Each output component is an affine transformation of the input component, so
+// the minimum and maximum values are for any combination of minimum or maximum
+// values of input components (i.e. 0 or 255).
+// E.g. if R' = x*R + y*G + z*B + w*A + t
+// Then the maximum value will be for R=255 if x>0 or R=0 if x<0, and the
+// minimum value will be for R=0 if x>0 or R=255 if x<0.
+// Same goes for all components.
+bool ComponentNeedsClamping(SkScalar row[5]) {
+ SkScalar max_value = row[4] / 255.f;
+ SkScalar min_value = row[4] / 255.f;
+ for (int i = 0; i < 4; ++i) {
+ if (row[i] > 0)
+ max_value += row[i];
+ else
+ min_value += row[i];
+ }
+ return (max_value > 1.f) || (min_value < 0.f);
+}
+
+bool MatrixNeedsClamping(SkScalar matrix[20]) {
+ return ComponentNeedsClamping(matrix)
+ || ComponentNeedsClamping(matrix+5)
+ || ComponentNeedsClamping(matrix+10)
+ || ComponentNeedsClamping(matrix+15);
+}
+
+bool GetColorMatrix(const FilterOperation& op, SkScalar matrix[20]) {
+ switch (op.type()) {
+ case FilterOperation::BRIGHTNESS: {
+ GetBrightnessMatrix(op.amount(), matrix);
+ return true;
+ }
+ case FilterOperation::SATURATING_BRIGHTNESS: {
+ GetSaturatingBrightnessMatrix(op.amount(), matrix);
+ return true;
+ }
+ case FilterOperation::CONTRAST: {
+ GetContrastMatrix(op.amount(), matrix);
+ return true;
+ }
+ case FilterOperation::GRAYSCALE: {
+ GetGrayscaleMatrix(1.f - op.amount(), matrix);
+ return true;
+ }
+ case FilterOperation::SEPIA: {
+ GetSepiaMatrix(1.f - op.amount(), matrix);
+ return true;
+ }
+ case FilterOperation::SATURATE: {
+ GetSaturateMatrix(op.amount(), matrix);
+ return true;
+ }
+ case FilterOperation::HUE_ROTATE: {
+ GetHueRotateMatrix(op.amount(), matrix);
+ return true;
+ }
+ case FilterOperation::INVERT: {
+ GetInvertMatrix(op.amount(), matrix);
+ return true;
+ }
+ case FilterOperation::OPACITY: {
+ GetOpacityMatrix(op.amount(), matrix);
+ return true;
+ }
+ case FilterOperation::COLOR_MATRIX: {
+ memcpy(matrix, op.matrix(), sizeof(SkScalar[20]));
+ return true;
+ }
+ default:
+ return false;
+ }
+}
+
+class FilterBufferState {
+ public:
+ FilterBufferState(GrContext* gr_context,
+ gfx::SizeF size,
+ unsigned texture_id)
+ : gr_context_(gr_context),
+ current_texture_(0) {
+ // Wrap the source texture in a Ganesh platform texture.
+ GrBackendTextureDesc backend_texture_description;
+ backend_texture_description.fWidth = size.width();
+ backend_texture_description.fHeight = size.height();
+ backend_texture_description.fConfig = kSkia8888_GrPixelConfig;
+ backend_texture_description.fTextureHandle = texture_id;
+ skia::RefPtr<GrTexture> texture = skia::AdoptRef(
+ gr_context->wrapBackendTexture(backend_texture_description));
+ // Place the platform texture inside an SkBitmap.
+ source_.setConfig(SkBitmap::kARGB_8888_Config, size.width(), size.height());
+ skia::RefPtr<SkGrPixelRef> pixel_ref =
+ skia::AdoptRef(new SkGrPixelRef(texture.get()));
+ source_.setPixelRef(pixel_ref.get());
+ }
+
+ ~FilterBufferState() {}
+
+ bool Init(int filter_count) {
+ int scratch_count = std::min(2, filter_count);
+ GrTextureDesc desc;
+ desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
+ desc.fSampleCnt = 0;
+ desc.fWidth = source_.width();
+ desc.fHeight = source_.height();
+ desc.fConfig = kSkia8888_GrPixelConfig;
+ for (int i = 0; i < scratch_count; ++i) {
+ GrAutoScratchTexture scratch_texture(
+ gr_context_, desc, GrContext::kExact_ScratchTexMatch);
+ scratch_textures_[i] = skia::AdoptRef(scratch_texture.detach());
+ if (!scratch_textures_[i])
+ return false;
+ }
+ return true;
+ }
+
+ SkCanvas* Canvas() {
+ if (!canvas_)
+ CreateCanvas();
+ return canvas_.get();
+ }
+
+ const SkBitmap& Source() { return source_; }
+
+ void Swap() {
+ canvas_->flush();
+ canvas_.clear();
+ device_.clear();
+
+ skia::RefPtr<SkGrPixelRef> pixel_ref = skia::AdoptRef(
+ new SkGrPixelRef(scratch_textures_[current_texture_].get()));
+ source_.setPixelRef(pixel_ref.get());
+ current_texture_ = 1 - current_texture_;
+ }
+
+ private:
+ void CreateCanvas() {
+ DCHECK(scratch_textures_[current_texture_].get());
+ device_ = skia::AdoptRef(new SkGpuDevice(
+ gr_context_, scratch_textures_[current_texture_].get()));
+ canvas_ = skia::AdoptRef(new SkCanvas(device_.get()));
+ canvas_->clear(0x0);
+ }
+
+ GrContext* gr_context_;
+ SkBitmap source_;
+ skia::RefPtr<GrTexture> scratch_textures_[2];
+ int current_texture_;
+ skia::RefPtr<SkGpuDevice> device_;
+ skia::RefPtr<SkCanvas> canvas_;
+};
+
+} // namespace
+
+FilterOperations RenderSurfaceFilters::Optimize(
+ const FilterOperations& filters) {
+ FilterOperations new_list;
+
+ SkScalar accumulated_color_matrix[20];
+ bool have_accumulated_color_matrix = false;
+ for (unsigned i = 0; i < filters.size(); ++i) {
+ const FilterOperation& op = filters.at(i);
+
+ // If the filter is a color matrix, we may be able to combine it with
+ // following Filter(s) that also are color matrices.
+ SkScalar matrix[20];
+ if (GetColorMatrix(op, matrix)) {
+ if (have_accumulated_color_matrix) {
+ SkScalar new_matrix[20];
+ MultColorMatrix(matrix, accumulated_color_matrix, new_matrix);
+ memcpy(accumulated_color_matrix,
+ new_matrix,
+ sizeof(accumulated_color_matrix));
+ } else {
+ memcpy(accumulated_color_matrix,
+ matrix,
+ sizeof(accumulated_color_matrix));
+ have_accumulated_color_matrix = true;
+ }
+
+ // We can only combine matrices if clamping of color components
+ // would have no effect.
+ if (!MatrixNeedsClamping(accumulated_color_matrix))
+ continue;
+ }
+
+ if (have_accumulated_color_matrix) {
+ new_list.Append(FilterOperation::CreateColorMatrixFilter(
+ accumulated_color_matrix));
+ }
+ have_accumulated_color_matrix = false;
+
+ switch (op.type()) {
+ case FilterOperation::BLUR:
+ case FilterOperation::DROP_SHADOW:
+ case FilterOperation::ZOOM:
+ new_list.Append(op);
+ break;
+ case FilterOperation::BRIGHTNESS:
+ case FilterOperation::SATURATING_BRIGHTNESS:
+ case FilterOperation::CONTRAST:
+ case FilterOperation::GRAYSCALE:
+ case FilterOperation::SEPIA:
+ case FilterOperation::SATURATE:
+ case FilterOperation::HUE_ROTATE:
+ case FilterOperation::INVERT:
+ case FilterOperation::OPACITY:
+ case FilterOperation::COLOR_MATRIX:
+ break;
+ }
+ }
+ if (have_accumulated_color_matrix) {
+ new_list.Append(FilterOperation::CreateColorMatrixFilter(
+ accumulated_color_matrix));
+ }
+ return new_list;
+}
+
+SkBitmap RenderSurfaceFilters::Apply(const FilterOperations& filters,
+ unsigned texture_id,
+ gfx::SizeF size,
+ GrContext* gr_context) {
+ DCHECK(gr_context);
+
+ FilterBufferState state(gr_context, size, texture_id);
+ if (!state.Init(filters.size()))
+ return SkBitmap();
+
+ for (unsigned i = 0; i < filters.size(); ++i) {
+ const FilterOperation& op = filters.at(i);
+ SkCanvas* canvas = state.Canvas();
+ switch (op.type()) {
+ case FilterOperation::COLOR_MATRIX: {
+ SkPaint paint;
+ skia::RefPtr<SkColorMatrixFilter> filter =
+ skia::AdoptRef(new SkColorMatrixFilter(op.matrix()));
+ paint.setColorFilter(filter.get());
+ canvas->drawBitmap(state.Source(), 0, 0, &paint);
+ break;
+ }
+ case FilterOperation::BLUR: {
+ float std_deviation = op.amount();
+ skia::RefPtr<SkImageFilter> filter =
+ skia::AdoptRef(new SkBlurImageFilter(std_deviation, std_deviation));
+ SkPaint paint;
+ paint.setImageFilter(filter.get());
+ canvas->drawSprite(state.Source(), 0, 0, &paint);
+ break;
+ }
+ case FilterOperation::DROP_SHADOW: {
+ skia::RefPtr<SkImageFilter> blur_filter =
+ skia::AdoptRef(new SkBlurImageFilter(op.amount(), op.amount()));
+ skia::RefPtr<SkColorFilter> color_filter =
+ skia::AdoptRef(SkColorFilter::CreateModeFilter(
+ op.drop_shadow_color(), SkXfermode::kSrcIn_Mode));
+ SkPaint paint;
+ paint.setImageFilter(blur_filter.get());
+ paint.setColorFilter(color_filter.get());
+ paint.setXfermodeMode(SkXfermode::kSrcOver_Mode);
+ canvas->saveLayer(NULL, &paint);
+ canvas->drawBitmap(state.Source(),
+ op.drop_shadow_offset().x(),
+ op.drop_shadow_offset().y());
+ canvas->restore();
+ canvas->drawBitmap(state.Source(), 0, 0);
+ break;
+ }
+ case FilterOperation::ZOOM: {
+ SkPaint paint;
+ int width = state.Source().width();
+ int height = state.Source().height();
+ skia::RefPtr<SkImageFilter> zoom_filter = skia::AdoptRef(
+ new SkMagnifierImageFilter(
+ SkRect::MakeXYWH(
+ (width - (width / op.amount())) / 2.f,
+ (height - (height / op.amount())) / 2.f,
+ width / op.amount(),
+ height / op.amount()),
+ op.zoom_inset()));
+ paint.setImageFilter(zoom_filter.get());
+ canvas->saveLayer(NULL, &paint);
+ canvas->drawBitmap(state.Source(), 0, 0);
+ canvas->restore();
+ break;
+ }
+ case FilterOperation::BRIGHTNESS:
+ case FilterOperation::SATURATING_BRIGHTNESS:
+ case FilterOperation::CONTRAST:
+ case FilterOperation::GRAYSCALE:
+ case FilterOperation::SEPIA:
+ case FilterOperation::SATURATE:
+ case FilterOperation::HUE_ROTATE:
+ case FilterOperation::INVERT:
+ case FilterOperation::OPACITY:
+ NOTREACHED();
+ break;
+ }
+ state.Swap();
+ }
+ return state.Source();
+}
+
+} // namespace cc
diff --git a/chromium/cc/output/render_surface_filters.h b/chromium/cc/output/render_surface_filters.h
new file mode 100644
index 00000000000..5ab78dc77e7
--- /dev/null
+++ b/chromium/cc/output/render_surface_filters.h
@@ -0,0 +1,37 @@
+// 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.
+
+
+#ifndef CC_OUTPUT_RENDER_SURFACE_FILTERS_H_
+#define CC_OUTPUT_RENDER_SURFACE_FILTERS_H_
+
+#include "base/basictypes.h"
+#include "cc/base/cc_export.h"
+
+class GrContext;
+class SkBitmap;
+
+namespace gfx {
+class SizeF;
+}
+
+namespace cc {
+
+class FilterOperations;
+
+class CC_EXPORT RenderSurfaceFilters {
+ public:
+ static SkBitmap Apply(const FilterOperations& filters,
+ unsigned texture_id,
+ gfx::SizeF size,
+ GrContext* gr_context);
+ static FilterOperations Optimize(const FilterOperations& filters);
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(RenderSurfaceFilters);
+};
+
+} // namespace cc
+
+#endif // CC_OUTPUT_RENDER_SURFACE_FILTERS_H_
diff --git a/chromium/cc/output/render_surface_filters_unittest.cc b/chromium/cc/output/render_surface_filters_unittest.cc
new file mode 100644
index 00000000000..38c6cc343b1
--- /dev/null
+++ b/chromium/cc/output/render_surface_filters_unittest.cc
@@ -0,0 +1,140 @@
+// 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 "cc/output/render_surface_filters.h"
+
+#include "cc/output/filter_operation.h"
+#include "cc/output/filter_operations.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+// Checks whether op can be combined with a following color matrix.
+bool IsCombined(const FilterOperation& op) {
+ FilterOperations filters;
+ filters.Append(op);
+ // brightness(0.0f) is identity.
+ filters.Append(FilterOperation::CreateBrightnessFilter(0.0f));
+ FilterOperations optimized = RenderSurfaceFilters::Optimize(filters);
+ return optimized.size() == 1;
+}
+
+TEST(RenderSurfaceFiltersTest, TestColorMatrixFiltersCombined) {
+ // Several filters should always combine for any amount between 0 and 1:
+ // grayscale, saturate, invert, contrast, opacity.
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateGrayscaleFilter(0.0f)));
+ // Note that we use 0.3f to avoid "argument is truncated from 'double' to
+ // 'float'" warnings on Windows. 0.5f is exactly representable as a float, so
+ // there is no warning.
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateGrayscaleFilter(0.3f)));
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateGrayscaleFilter(0.5f)));
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateGrayscaleFilter(1.0f)));
+
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateSaturateFilter(0.0f)));
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateSaturateFilter(0.3f)));
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateSaturateFilter(0.5)));
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateSaturateFilter(1.0f)));
+
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateInvertFilter(0.0f)));
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateInvertFilter(0.3f)));
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateInvertFilter(0.5)));
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateInvertFilter(1.0f)));
+
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateContrastFilter(0.0f)));
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateContrastFilter(0.3f)));
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateContrastFilter(0.5)));
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateContrastFilter(1.0f)));
+
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateOpacityFilter(0.0f)));
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateOpacityFilter(0.3f)));
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateOpacityFilter(0.5)));
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateOpacityFilter(1.0f)));
+
+ // Brightness combines when amount <= 1
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateBrightnessFilter(0.5)));
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateBrightnessFilter(1.0f)));
+ EXPECT_FALSE(IsCombined(FilterOperation::CreateBrightnessFilter(1.5)));
+
+ // SaturatingBrightness combines only when amount == 0
+ EXPECT_TRUE(
+ IsCombined(FilterOperation::CreateSaturatingBrightnessFilter(0.0f)));
+ EXPECT_FALSE(
+ IsCombined(FilterOperation::CreateSaturatingBrightnessFilter(0.5)));
+ EXPECT_FALSE(
+ IsCombined(FilterOperation::CreateSaturatingBrightnessFilter(1.0f)));
+
+ // Several filters should never combine: blur, drop-shadow.
+ EXPECT_FALSE(IsCombined(FilterOperation::CreateBlurFilter(3.0f)));
+ EXPECT_FALSE(IsCombined(FilterOperation::CreateDropShadowFilter(
+ gfx::Point(2, 2), 3.0f, 0xffffffff)));
+
+ // sepia and hue may or may not combine depending on the value.
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateSepiaFilter(0.0f)));
+ EXPECT_FALSE(IsCombined(FilterOperation::CreateSepiaFilter(1.0f)));
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateHueRotateFilter(0.0f)));
+ EXPECT_FALSE(IsCombined(FilterOperation::CreateHueRotateFilter(180.0f)));
+
+ float matrix1[20] = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, };
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateColorMatrixFilter(matrix1)));
+
+ float matrix2[20] = { 1.0f, 1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, };
+ EXPECT_FALSE(
+ IsCombined(FilterOperation::CreateColorMatrixFilter(matrix2)));
+
+ float matrix3[20] = { 0.25f, 0.0f, 0.0f, 0.0f, 255.0f * 0.75f,
+ 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, };
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateColorMatrixFilter(matrix3)));
+
+ float matrix4[20] = { -0.25f, 0.75f, 0.0f, 0.0f, 255.0f * 0.25f,
+ 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, };
+ EXPECT_TRUE(IsCombined(FilterOperation::CreateColorMatrixFilter(matrix4)));
+}
+
+TEST(RenderSurfaceFiltersTest, TestOptimize) {
+ FilterOperation combines(FilterOperation::CreateBrightnessFilter(1.0f));
+ FilterOperation doesnt_combine(
+ FilterOperation::CreateBrightnessFilter(1.5f));
+
+ FilterOperations filters;
+ FilterOperations optimized = RenderSurfaceFilters::Optimize(filters);
+ EXPECT_EQ(0u, optimized.size());
+
+ filters.Append(combines);
+ optimized = RenderSurfaceFilters::Optimize(filters);
+ EXPECT_EQ(1u, optimized.size());
+
+ filters.Append(combines);
+ optimized = RenderSurfaceFilters::Optimize(filters);
+ EXPECT_EQ(1u, optimized.size());
+
+ filters.Append(doesnt_combine);
+ optimized = RenderSurfaceFilters::Optimize(filters);
+ EXPECT_EQ(1u, optimized.size());
+
+ filters.Append(combines);
+ optimized = RenderSurfaceFilters::Optimize(filters);
+ EXPECT_EQ(2u, optimized.size());
+
+ filters.Append(doesnt_combine);
+ optimized = RenderSurfaceFilters::Optimize(filters);
+ EXPECT_EQ(2u, optimized.size());
+
+ filters.Append(doesnt_combine);
+ optimized = RenderSurfaceFilters::Optimize(filters);
+ EXPECT_EQ(3u, optimized.size());
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/output/renderer.cc b/chromium/cc/output/renderer.cc
new file mode 100644
index 00000000000..7627db7ab0a
--- /dev/null
+++ b/chromium/cc/output/renderer.cc
@@ -0,0 +1,17 @@
+// 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 "cc/output/renderer.h"
+
+namespace cc {
+
+bool Renderer::HaveCachedResourcesForRenderPassId(RenderPass::Id id) const {
+ return false;
+}
+
+bool Renderer::IsContextLost() {
+ return false;
+}
+
+} // namespace cc
diff --git a/chromium/cc/output/renderer.h b/chromium/cc/output/renderer.h
new file mode 100644
index 00000000000..a9a800b6a0e
--- /dev/null
+++ b/chromium/cc/output/renderer.h
@@ -0,0 +1,92 @@
+// 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.
+
+#ifndef CC_OUTPUT_RENDERER_H_
+#define CC_OUTPUT_RENDERER_H_
+
+#include "base/basictypes.h"
+#include "cc/base/cc_export.h"
+#include "cc/quads/render_pass.h"
+#include "cc/trees/layer_tree_host.h"
+
+namespace cc {
+
+class CompositorFrameAck;
+class CompositorFrameMetadata;
+class ScopedResource;
+
+class CC_EXPORT RendererClient {
+ public:
+ // Draw viewport in non-y-flipped window space. Note that while a draw is in
+ // progress, this is guaranteed to be contained within the output surface
+ // size.
+ virtual gfx::Rect DeviceViewport() const = 0;
+
+ virtual float DeviceScaleFactor() const = 0;
+ virtual const LayerTreeSettings& Settings() const = 0;
+ virtual void SetFullRootLayerDamage() = 0;
+ virtual bool HasImplThread() const = 0;
+ virtual bool ShouldClearRootRenderPass() const = 0;
+ virtual CompositorFrameMetadata MakeCompositorFrameMetadata() const = 0;
+ virtual bool AllowPartialSwap() const = 0;
+ virtual bool ExternalStencilTestEnabled() const = 0;
+
+ protected:
+ virtual ~RendererClient() {}
+};
+
+class CC_EXPORT Renderer {
+ public:
+ virtual ~Renderer() {}
+
+ virtual const RendererCapabilities& Capabilities() const = 0;
+
+ const LayerTreeSettings& Settings() const { return client_->Settings(); }
+
+ virtual void ViewportChanged() {}
+
+ virtual bool CanReadPixels() const = 0;
+
+ virtual void DecideRenderPassAllocationsForFrame(
+ const RenderPassList& render_passes_in_draw_order) {}
+ virtual bool HaveCachedResourcesForRenderPassId(RenderPass::Id id) const;
+
+ // This passes ownership of the render passes to the renderer. It should
+ // consume them, and empty the list.
+ virtual void DrawFrame(RenderPassList* render_passes_in_draw_order) = 0;
+
+ // Waits for rendering to finish.
+ virtual void Finish() = 0;
+
+ virtual void DoNoOp() {}
+
+ // Puts backbuffer onscreen.
+ virtual void SwapBuffers() = 0;
+ virtual void ReceiveSwapBuffersAck(const CompositorFrameAck& ack) {}
+
+ virtual void GetFramebufferPixels(void* pixels, gfx::Rect rect) = 0;
+
+ virtual bool IsContextLost();
+
+ virtual void SetVisible(bool visible) = 0;
+
+ virtual void SendManagedMemoryStats(size_t bytes_visible,
+ size_t bytes_visible_and_nearby,
+ size_t bytes_allocated) = 0;
+
+ virtual void SetDiscardBackBufferWhenNotVisible(bool discard) = 0;
+
+ protected:
+ explicit Renderer(RendererClient* client)
+ : client_(client) {}
+
+ RendererClient* client_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Renderer);
+};
+
+} // namespace cc
+
+#endif // CC_OUTPUT_RENDERER_H_
diff --git a/chromium/cc/output/renderer_pixeltest.cc b/chromium/cc/output/renderer_pixeltest.cc
new file mode 100644
index 00000000000..9508aa29166
--- /dev/null
+++ b/chromium/cc/output/renderer_pixeltest.cc
@@ -0,0 +1,1548 @@
+// 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 "base/message_loop/message_loop.h"
+#include "cc/layers/append_quads_data.h"
+#include "cc/output/gl_renderer.h"
+#include "cc/quads/draw_quad.h"
+#include "cc/quads/picture_draw_quad.h"
+#include "cc/quads/texture_draw_quad.h"
+#include "cc/resources/platform_color.h"
+#include "cc/resources/sync_point_helper.h"
+#include "cc/test/fake_picture_pile_impl.h"
+#include "cc/test/pixel_test.h"
+#include "gpu/GLES2/gl2extchromium.h"
+#include "third_party/skia/include/core/SkImageFilter.h"
+#include "third_party/skia/include/core/SkMatrix.h"
+#include "third_party/skia/include/effects/SkColorFilterImageFilter.h"
+#include "third_party/skia/include/effects/SkColorMatrixFilter.h"
+#include "ui/gfx/rect_conversions.h"
+
+namespace cc {
+namespace {
+
+scoped_ptr<RenderPass> CreateTestRootRenderPass(RenderPass::Id id,
+ gfx::Rect rect) {
+ scoped_ptr<RenderPass> pass = RenderPass::Create();
+ const gfx::Rect output_rect = rect;
+ const gfx::RectF damage_rect = rect;
+ const gfx::Transform transform_to_root_target;
+ pass->SetNew(id, output_rect, damage_rect, transform_to_root_target);
+ return pass.Pass();
+}
+
+scoped_ptr<RenderPass> CreateTestRenderPass(
+ RenderPass::Id id,
+ gfx::Rect rect,
+ const gfx::Transform& transform_to_root_target) {
+ scoped_ptr<RenderPass> pass = RenderPass::Create();
+ const gfx::Rect output_rect = rect;
+ const gfx::RectF damage_rect = rect;
+ pass->SetNew(id, output_rect, damage_rect, transform_to_root_target);
+ return pass.Pass();
+}
+
+scoped_ptr<SharedQuadState> CreateTestSharedQuadState(
+ gfx::Transform content_to_target_transform, gfx::Rect rect) {
+ const gfx::Size content_bounds = rect.size();
+ const gfx::Rect visible_content_rect = rect;
+ const gfx::Rect clip_rect = rect;
+ const bool is_clipped = false;
+ const float opacity = 1.0f;
+ scoped_ptr<SharedQuadState> shared_state = SharedQuadState::Create();
+ shared_state->SetAll(content_to_target_transform,
+ content_bounds,
+ visible_content_rect,
+ clip_rect,
+ is_clipped,
+ opacity);
+ return shared_state.Pass();
+}
+
+scoped_ptr<SharedQuadState> CreateTestSharedQuadStateClipped(
+ gfx::Transform content_to_target_transform,
+ gfx::Rect rect,
+ gfx::Rect clip_rect) {
+ const gfx::Size content_bounds = rect.size();
+ const gfx::Rect visible_content_rect = clip_rect;
+ const bool is_clipped = true;
+ const float opacity = 1.0f;
+ scoped_ptr<SharedQuadState> shared_state = SharedQuadState::Create();
+ shared_state->SetAll(content_to_target_transform,
+ content_bounds,
+ visible_content_rect,
+ clip_rect,
+ is_clipped,
+ opacity);
+ return shared_state.Pass();
+}
+
+scoped_ptr<DrawQuad> CreateTestRenderPassDrawQuad(
+ SharedQuadState* shared_state, gfx::Rect rect, RenderPass::Id pass_id) {
+ scoped_ptr<RenderPassDrawQuad> quad = RenderPassDrawQuad::Create();
+ quad->SetNew(shared_state,
+ rect,
+ pass_id,
+ false, // is_replica
+ 0, // mask_resource_id
+ rect, // contents_changed_since_last_frame
+ gfx::RectF(), // mask_uv_rect
+ FilterOperations(), // foreground filters
+ skia::RefPtr<SkImageFilter>(), // foreground filter
+ FilterOperations()); // background filters
+
+ return quad.PassAs<DrawQuad>();
+}
+
+scoped_ptr<TextureDrawQuad> CreateTestTextureDrawQuad(
+ gfx::Rect rect,
+ SkColor texel_color,
+ SkColor background_color,
+ bool premultiplied_alpha,
+ SharedQuadState* shared_state,
+ ResourceProvider* resource_provider) {
+ SkPMColor pixel_color = premultiplied_alpha ?
+ SkPreMultiplyColor(texel_color) :
+ SkPackARGB32NoCheck(SkColorGetA(texel_color),
+ SkColorGetR(texel_color),
+ SkColorGetG(texel_color),
+ SkColorGetB(texel_color));
+ std::vector<uint32_t> pixels(rect.size().GetArea(), pixel_color);
+
+ ResourceProvider::ResourceId resource = resource_provider->CreateResource(
+ rect.size(), GL_RGBA, ResourceProvider::TextureUsageAny);
+ resource_provider->SetPixels(
+ resource,
+ reinterpret_cast<uint8_t*>(&pixels.front()),
+ rect,
+ rect,
+ gfx::Vector2d());
+
+ float vertex_opacity[4] = {1.0f, 1.0f, 1.0f, 1.0f};
+
+ scoped_ptr<TextureDrawQuad> quad = TextureDrawQuad::Create();
+ quad->SetNew(shared_state,
+ rect,
+ gfx::Rect(),
+ resource,
+ premultiplied_alpha,
+ gfx::PointF(0.0f, 0.0f), // uv_top_left
+ gfx::PointF(1.0f, 1.0f), // uv_bottom_right
+ background_color,
+ vertex_opacity,
+ false); // flipped
+ return quad.Pass();
+}
+
+typedef ::testing::Types<GLRenderer,
+ SoftwareRenderer,
+ GLRendererWithExpandedViewport,
+ SoftwareRendererWithExpandedViewport> RendererTypes;
+TYPED_TEST_CASE(RendererPixelTest, RendererTypes);
+
+typedef ::testing::Types<GLRenderer,
+ GLRendererWithSkiaGPUBackend,
+ SoftwareRenderer> RendererTypesWithSkiaGPUBackend;
+template <typename RendererType>
+class RendererPixelTestWithSkiaGPUBackend
+ : public RendererPixelTest<RendererType> {
+};
+TYPED_TEST_CASE(RendererPixelTestWithSkiaGPUBackend,
+ RendererTypesWithSkiaGPUBackend);
+
+// All pixels can be off by one, but any more than that is an error.
+class FuzzyPixelOffByOneComparator : public FuzzyPixelComparator {
+ public:
+ explicit FuzzyPixelOffByOneComparator(bool discard_alpha)
+ : FuzzyPixelComparator(discard_alpha, 100.f, 0.f, 1.f, 1, 0) {}
+};
+
+template <typename RendererType>
+class FuzzyForSoftwareOnlyPixelComparator : public PixelComparator {
+ public:
+ explicit FuzzyForSoftwareOnlyPixelComparator(bool discard_alpha)
+ : fuzzy_(discard_alpha), exact_(discard_alpha) {}
+
+ virtual bool Compare(const SkBitmap& actual_bmp,
+ const SkBitmap& expected_bmp) const;
+
+ private:
+ FuzzyPixelOffByOneComparator fuzzy_;
+ ExactPixelComparator exact_;
+};
+
+template<>
+bool FuzzyForSoftwareOnlyPixelComparator<SoftwareRenderer>::Compare(
+ const SkBitmap& actual_bmp,
+ const SkBitmap& expected_bmp) const {
+ return fuzzy_.Compare(actual_bmp, expected_bmp);
+}
+
+template <>
+bool FuzzyForSoftwareOnlyPixelComparator<
+ SoftwareRendererWithExpandedViewport>::Compare(
+ const SkBitmap& actual_bmp,
+ const SkBitmap& expected_bmp) const {
+ return fuzzy_.Compare(actual_bmp, expected_bmp);
+}
+
+template<typename RendererType>
+bool FuzzyForSoftwareOnlyPixelComparator<RendererType>::Compare(
+ const SkBitmap& actual_bmp,
+ const SkBitmap& expected_bmp) const {
+ return exact_.Compare(actual_bmp, expected_bmp);
+}
+
+#if !defined(OS_ANDROID)
+TYPED_TEST(RendererPixelTest, SimpleGreenRect) {
+ gfx::Rect rect(this->device_viewport_size_);
+
+ RenderPass::Id id(1, 1);
+ scoped_ptr<RenderPass> pass = CreateTestRootRenderPass(id, rect);
+
+ scoped_ptr<SharedQuadState> shared_state =
+ CreateTestSharedQuadState(gfx::Transform(), rect);
+
+ scoped_ptr<SolidColorDrawQuad> color_quad = SolidColorDrawQuad::Create();
+ color_quad->SetNew(shared_state.get(), rect, SK_ColorGREEN, false);
+
+ pass->quad_list.push_back(color_quad.PassAs<DrawQuad>());
+
+ RenderPassList pass_list;
+ pass_list.push_back(pass.Pass());
+
+ EXPECT_TRUE(this->RunPixelTest(
+ &pass_list,
+ base::FilePath(FILE_PATH_LITERAL("green.png")),
+ ExactPixelComparator(true)));
+}
+
+TYPED_TEST(RendererPixelTest, SimpleGreenRect_NonRootRenderPass) {
+ gfx::Rect rect(this->device_viewport_size_);
+ gfx::Rect small_rect(100, 100);
+
+ RenderPass::Id child_id(2, 1);
+ scoped_ptr<RenderPass> child_pass =
+ CreateTestRenderPass(child_id, small_rect, gfx::Transform());
+
+ scoped_ptr<SharedQuadState> child_shared_state =
+ CreateTestSharedQuadState(gfx::Transform(), small_rect);
+
+ scoped_ptr<SolidColorDrawQuad> color_quad = SolidColorDrawQuad::Create();
+ color_quad->SetNew(child_shared_state.get(), rect, SK_ColorGREEN, false);
+ child_pass->quad_list.push_back(color_quad.PassAs<DrawQuad>());
+
+ RenderPass::Id root_id(1, 1);
+ scoped_ptr<RenderPass> root_pass =
+ CreateTestRenderPass(root_id, rect, gfx::Transform());
+
+ scoped_ptr<SharedQuadState> root_shared_state =
+ CreateTestSharedQuadState(gfx::Transform(), rect);
+
+ scoped_ptr<DrawQuad> render_pass_quad =
+ CreateTestRenderPassDrawQuad(root_shared_state.get(),
+ small_rect,
+ child_id);
+ root_pass->quad_list.push_back(render_pass_quad.PassAs<DrawQuad>());
+
+ RenderPass* child_pass_ptr = child_pass.get();
+
+ RenderPassList pass_list;
+ pass_list.push_back(child_pass.Pass());
+ pass_list.push_back(root_pass.Pass());
+
+ EXPECT_TRUE(this->RunPixelTestWithReadbackTarget(
+ &pass_list,
+ child_pass_ptr,
+ base::FilePath(FILE_PATH_LITERAL("green_small.png")),
+ ExactPixelComparator(true)));
+}
+
+TYPED_TEST(RendererPixelTest, PremultipliedTextureWithoutBackground) {
+ gfx::Rect rect(this->device_viewport_size_);
+
+ RenderPass::Id id(1, 1);
+ scoped_ptr<RenderPass> pass = CreateTestRootRenderPass(id, rect);
+
+ scoped_ptr<SharedQuadState> shared_state =
+ CreateTestSharedQuadState(gfx::Transform(), rect);
+
+ scoped_ptr<TextureDrawQuad> texture_quad = CreateTestTextureDrawQuad(
+ gfx::Rect(this->device_viewport_size_),
+ SkColorSetARGB(128, 0, 255, 0), // Texel color.
+ SK_ColorTRANSPARENT, // Background color.
+ true, // Premultiplied alpha.
+ shared_state.get(),
+ this->resource_provider_.get());
+ pass->quad_list.push_back(texture_quad.PassAs<DrawQuad>());
+
+ scoped_ptr<SolidColorDrawQuad> color_quad = SolidColorDrawQuad::Create();
+ color_quad->SetNew(shared_state.get(), rect, SK_ColorWHITE, false);
+ pass->quad_list.push_back(color_quad.PassAs<DrawQuad>());
+
+ RenderPassList pass_list;
+ pass_list.push_back(pass.Pass());
+
+ EXPECT_TRUE(this->RunPixelTest(
+ &pass_list,
+ base::FilePath(FILE_PATH_LITERAL("green_alpha.png")),
+ FuzzyPixelOffByOneComparator(true)));
+}
+
+TYPED_TEST(RendererPixelTest, PremultipliedTextureWithBackground) {
+ gfx::Rect rect(this->device_viewport_size_);
+
+ RenderPass::Id id(1, 1);
+ scoped_ptr<RenderPass> pass = CreateTestRootRenderPass(id, rect);
+
+ scoped_ptr<SharedQuadState> texture_quad_state =
+ CreateTestSharedQuadState(gfx::Transform(), rect);
+ texture_quad_state->opacity = 0.8f;
+
+ scoped_ptr<TextureDrawQuad> texture_quad = CreateTestTextureDrawQuad(
+ gfx::Rect(this->device_viewport_size_),
+ SkColorSetARGB(204, 120, 255, 120), // Texel color.
+ SK_ColorGREEN, // Background color.
+ true, // Premultiplied alpha.
+ texture_quad_state.get(),
+ this->resource_provider_.get());
+ pass->quad_list.push_back(texture_quad.PassAs<DrawQuad>());
+
+ scoped_ptr<SharedQuadState> color_quad_state =
+ CreateTestSharedQuadState(gfx::Transform(), rect);
+ scoped_ptr<SolidColorDrawQuad> color_quad = SolidColorDrawQuad::Create();
+ color_quad->SetNew(color_quad_state.get(), rect, SK_ColorWHITE, false);
+ pass->quad_list.push_back(color_quad.PassAs<DrawQuad>());
+
+ RenderPassList pass_list;
+ pass_list.push_back(pass.Pass());
+
+ EXPECT_TRUE(this->RunPixelTest(
+ &pass_list,
+ base::FilePath(FILE_PATH_LITERAL("green_alpha.png")),
+ FuzzyPixelOffByOneComparator(true)));
+}
+
+// TODO(skaslev): The software renderer does not support non-premultplied alpha.
+TEST_F(GLRendererPixelTest, NonPremultipliedTextureWithoutBackground) {
+ gfx::Rect rect(this->device_viewport_size_);
+
+ RenderPass::Id id(1, 1);
+ scoped_ptr<RenderPass> pass = CreateTestRootRenderPass(id, rect);
+
+ scoped_ptr<SharedQuadState> shared_state =
+ CreateTestSharedQuadState(gfx::Transform(), rect);
+
+ scoped_ptr<TextureDrawQuad> texture_quad = CreateTestTextureDrawQuad(
+ gfx::Rect(this->device_viewport_size_),
+ SkColorSetARGB(128, 0, 255, 0), // Texel color.
+ SK_ColorTRANSPARENT, // Background color.
+ false, // Premultiplied alpha.
+ shared_state.get(),
+ this->resource_provider_.get());
+ pass->quad_list.push_back(texture_quad.PassAs<DrawQuad>());
+
+ scoped_ptr<SolidColorDrawQuad> color_quad = SolidColorDrawQuad::Create();
+ color_quad->SetNew(shared_state.get(), rect, SK_ColorWHITE, false);
+ pass->quad_list.push_back(color_quad.PassAs<DrawQuad>());
+
+ RenderPassList pass_list;
+ pass_list.push_back(pass.Pass());
+
+ EXPECT_TRUE(this->RunPixelTest(
+ &pass_list,
+ base::FilePath(FILE_PATH_LITERAL("green_alpha.png")),
+ FuzzyPixelOffByOneComparator(true)));
+}
+
+// TODO(skaslev): The software renderer does not support non-premultplied alpha.
+TEST_F(GLRendererPixelTest, NonPremultipliedTextureWithBackground) {
+ gfx::Rect rect(this->device_viewport_size_);
+
+ RenderPass::Id id(1, 1);
+ scoped_ptr<RenderPass> pass = CreateTestRootRenderPass(id, rect);
+
+ scoped_ptr<SharedQuadState> texture_quad_state =
+ CreateTestSharedQuadState(gfx::Transform(), rect);
+ texture_quad_state->opacity = 0.8f;
+
+ scoped_ptr<TextureDrawQuad> texture_quad = CreateTestTextureDrawQuad(
+ gfx::Rect(this->device_viewport_size_),
+ SkColorSetARGB(204, 120, 255, 120), // Texel color.
+ SK_ColorGREEN, // Background color.
+ false, // Premultiplied alpha.
+ texture_quad_state.get(),
+ this->resource_provider_.get());
+ pass->quad_list.push_back(texture_quad.PassAs<DrawQuad>());
+
+ scoped_ptr<SharedQuadState> color_quad_state =
+ CreateTestSharedQuadState(gfx::Transform(), rect);
+ scoped_ptr<SolidColorDrawQuad> color_quad = SolidColorDrawQuad::Create();
+ color_quad->SetNew(color_quad_state.get(), rect, SK_ColorWHITE, false);
+ pass->quad_list.push_back(color_quad.PassAs<DrawQuad>());
+
+ RenderPassList pass_list;
+ pass_list.push_back(pass.Pass());
+
+ EXPECT_TRUE(this->RunPixelTest(
+ &pass_list,
+ base::FilePath(FILE_PATH_LITERAL("green_alpha.png")),
+ FuzzyPixelOffByOneComparator(true)));
+}
+
+class VideoGLRendererPixelTest : public GLRendererPixelTest {
+ protected:
+ scoped_ptr<YUVVideoDrawQuad> CreateTestYUVVideoDrawQuad(
+ SharedQuadState* shared_state, bool with_alpha, bool is_transparent) {
+ gfx::Rect rect(this->device_viewport_size_);
+ gfx::Rect opaque_rect(0, 0, 0, 0);
+
+ ResourceProvider::ResourceId y_resource =
+ resource_provider_->CreateResource(
+ this->device_viewport_size_,
+ GL_LUMINANCE,
+ ResourceProvider::TextureUsageAny);
+ ResourceProvider::ResourceId u_resource =
+ resource_provider_->CreateResource(
+ this->device_viewport_size_,
+ GL_LUMINANCE,
+ ResourceProvider::TextureUsageAny);
+ ResourceProvider::ResourceId v_resource =
+ resource_provider_->CreateResource(
+ this->device_viewport_size_,
+ GL_LUMINANCE,
+ ResourceProvider::TextureUsageAny);
+ ResourceProvider::ResourceId a_resource = 0;
+ if (with_alpha) {
+ a_resource = resource_provider_->CreateResource(
+ this->device_viewport_size_,
+ GL_LUMINANCE,
+ ResourceProvider::TextureUsageAny);
+ }
+
+ int w = this->device_viewport_size_.width();
+ int h = this->device_viewport_size_.height();
+ const int y_plane_size = w * h;
+ gfx::Rect uv_rect((w + 1) / 2, (h + 1) / 2);
+ const int uv_plane_size = uv_rect.size().GetArea();
+ scoped_ptr<uint8_t[]> y_plane(new uint8_t[y_plane_size]);
+ scoped_ptr<uint8_t[]> u_plane(new uint8_t[uv_plane_size]);
+ scoped_ptr<uint8_t[]> v_plane(new uint8_t[uv_plane_size]);
+ scoped_ptr<uint8_t[]> a_plane;
+ if (with_alpha)
+ a_plane.reset(new uint8_t[y_plane_size]);
+ // YUV values representing Green.
+ memset(y_plane.get(), 149, y_plane_size);
+ memset(u_plane.get(), 43, uv_plane_size);
+ memset(v_plane.get(), 21, uv_plane_size);
+ if (with_alpha)
+ memset(a_plane.get(), is_transparent ? 0 : 128, y_plane_size);
+
+ resource_provider_->SetPixels(y_resource, y_plane.get(), rect, rect,
+ gfx::Vector2d());
+ resource_provider_->SetPixels(u_resource, u_plane.get(), uv_rect, uv_rect,
+ gfx::Vector2d());
+ resource_provider_->SetPixels(v_resource, v_plane.get(), uv_rect, uv_rect,
+ gfx::Vector2d());
+ if (with_alpha) {
+ resource_provider_->SetPixels(a_resource, a_plane.get(), rect, rect,
+ gfx::Vector2d());
+ }
+
+ scoped_ptr<YUVVideoDrawQuad> yuv_quad = cc::YUVVideoDrawQuad::Create();
+ yuv_quad->SetNew(shared_state, rect, opaque_rect, gfx::Size(),
+ y_resource, u_resource, v_resource, a_resource);
+ return yuv_quad.Pass();
+ }
+};
+
+TEST_F(VideoGLRendererPixelTest, SimpleYUVRect) {
+ gfx::Rect rect(this->device_viewport_size_);
+
+ RenderPass::Id id(1, 1);
+ scoped_ptr<RenderPass> pass = CreateTestRootRenderPass(id, rect);
+
+ scoped_ptr<SharedQuadState> shared_state =
+ CreateTestSharedQuadState(gfx::Transform(), rect);
+
+ scoped_ptr<YUVVideoDrawQuad> yuv_quad =
+ CreateTestYUVVideoDrawQuad(shared_state.get(), false, false);
+
+ pass->quad_list.push_back(yuv_quad.PassAs<DrawQuad>());
+
+ RenderPassList pass_list;
+ pass_list.push_back(pass.Pass());
+
+ EXPECT_TRUE(this->RunPixelTest(
+ &pass_list,
+ base::FilePath(FILE_PATH_LITERAL("green.png")),
+ ExactPixelComparator(true)));
+}
+
+TEST_F(VideoGLRendererPixelTest, SimpleYUVARect) {
+ gfx::Rect rect(this->device_viewport_size_);
+
+ RenderPass::Id id(1, 1);
+ scoped_ptr<RenderPass> pass = CreateTestRootRenderPass(id, rect);
+
+ scoped_ptr<SharedQuadState> shared_state =
+ CreateTestSharedQuadState(gfx::Transform(), rect);
+
+ scoped_ptr<YUVVideoDrawQuad> yuv_quad =
+ CreateTestYUVVideoDrawQuad(shared_state.get(), true, false);
+
+ pass->quad_list.push_back(yuv_quad.PassAs<DrawQuad>());
+
+ scoped_ptr<SolidColorDrawQuad> color_quad = SolidColorDrawQuad::Create();
+ color_quad->SetNew(shared_state.get(), rect, SK_ColorWHITE, false);
+
+ pass->quad_list.push_back(color_quad.PassAs<DrawQuad>());
+
+ RenderPassList pass_list;
+ pass_list.push_back(pass.Pass());
+
+ EXPECT_TRUE(this->RunPixelTest(
+ &pass_list,
+ base::FilePath(FILE_PATH_LITERAL("green_alpha.png")),
+ ExactPixelComparator(true)));
+}
+
+TEST_F(VideoGLRendererPixelTest, FullyTransparentYUVARect) {
+ gfx::Rect rect(this->device_viewport_size_);
+
+ RenderPass::Id id(1, 1);
+ scoped_ptr<RenderPass> pass = CreateTestRootRenderPass(id, rect);
+
+ scoped_ptr<SharedQuadState> shared_state =
+ CreateTestSharedQuadState(gfx::Transform(), rect);
+
+ scoped_ptr<YUVVideoDrawQuad> yuv_quad =
+ CreateTestYUVVideoDrawQuad(shared_state.get(), true, true);
+
+ pass->quad_list.push_back(yuv_quad.PassAs<DrawQuad>());
+
+ scoped_ptr<SolidColorDrawQuad> color_quad = SolidColorDrawQuad::Create();
+ color_quad->SetNew(shared_state.get(), rect, SK_ColorBLACK, false);
+
+ pass->quad_list.push_back(color_quad.PassAs<DrawQuad>());
+
+ RenderPassList pass_list;
+ pass_list.push_back(pass.Pass());
+
+ EXPECT_TRUE(this->RunPixelTest(
+ &pass_list,
+ base::FilePath(FILE_PATH_LITERAL("black.png")),
+ ExactPixelComparator(true)));
+}
+
+TYPED_TEST(RendererPixelTest, FastPassColorFilterAlpha) {
+ gfx::Rect viewport_rect(this->device_viewport_size_);
+
+ RenderPass::Id root_pass_id(1, 1);
+ scoped_ptr<RenderPass> root_pass =
+ CreateTestRootRenderPass(root_pass_id, viewport_rect);
+
+ RenderPass::Id child_pass_id(2, 2);
+ gfx::Rect pass_rect(this->device_viewport_size_);
+ gfx::Transform transform_to_root;
+ scoped_ptr<RenderPass> child_pass =
+ CreateTestRenderPass(child_pass_id, pass_rect, transform_to_root);
+
+ gfx::Transform content_to_target_transform;
+ scoped_ptr<SharedQuadState> shared_state =
+ CreateTestSharedQuadState(content_to_target_transform, viewport_rect);
+ shared_state->opacity = 0.5f;
+
+ scoped_ptr<SolidColorDrawQuad> blue = SolidColorDrawQuad::Create();
+ blue->SetNew(shared_state.get(),
+ gfx::Rect(0,
+ 0,
+ this->device_viewport_size_.width(),
+ this->device_viewport_size_.height() / 2),
+ SK_ColorBLUE,
+ false);
+ scoped_ptr<SolidColorDrawQuad> yellow = SolidColorDrawQuad::Create();
+ yellow->SetNew(shared_state.get(),
+ gfx::Rect(0,
+ this->device_viewport_size_.height() / 2,
+ this->device_viewport_size_.width(),
+ this->device_viewport_size_.height() / 2),
+ SK_ColorYELLOW,
+ false);
+
+ scoped_ptr<SharedQuadState> blank_state =
+ CreateTestSharedQuadState(content_to_target_transform, viewport_rect);
+
+ scoped_ptr<SolidColorDrawQuad> white = SolidColorDrawQuad::Create();
+ white->SetNew(blank_state.get(),
+ viewport_rect,
+ SK_ColorWHITE,
+ false);
+
+ child_pass->quad_list.push_back(blue.PassAs<DrawQuad>());
+ child_pass->quad_list.push_back(yellow.PassAs<DrawQuad>());
+ child_pass->quad_list.push_back(white.PassAs<DrawQuad>());
+
+ scoped_ptr<SharedQuadState> pass_shared_state =
+ CreateTestSharedQuadState(gfx::Transform(), pass_rect);
+
+ SkScalar 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;
+ skia::RefPtr<SkColorFilter> colorFilter(skia::AdoptRef(
+ new SkColorMatrixFilter(matrix)));
+ skia::RefPtr<SkImageFilter> filter =
+ skia::AdoptRef(SkColorFilterImageFilter::Create(colorFilter.get(), NULL));
+
+ scoped_ptr<RenderPassDrawQuad> render_pass_quad =
+ RenderPassDrawQuad::Create();
+ render_pass_quad->SetNew(pass_shared_state.get(),
+ pass_rect,
+ child_pass_id,
+ false,
+ 0,
+ pass_rect,
+ gfx::RectF(),
+ FilterOperations(),
+ filter,
+ FilterOperations());
+
+ root_pass->quad_list.push_back(render_pass_quad.PassAs<DrawQuad>());
+
+ RenderPassList pass_list;
+ pass_list.push_back(child_pass.Pass());
+ pass_list.push_back(root_pass.Pass());
+
+ // This test has alpha=254 for the software renderer vs. alpha=255 for the gl
+ // renderer so use a fuzzy comparator.
+ EXPECT_TRUE(this->RunPixelTest(
+ &pass_list,
+ base::FilePath(FILE_PATH_LITERAL("blue_yellow_alpha.png")),
+ FuzzyForSoftwareOnlyPixelComparator<TypeParam>(false)));
+}
+
+TYPED_TEST(RendererPixelTest, FastPassColorFilterAlphaTranslation) {
+ gfx::Rect viewport_rect(this->device_viewport_size_);
+
+ RenderPass::Id root_pass_id(1, 1);
+ scoped_ptr<RenderPass> root_pass =
+ CreateTestRootRenderPass(root_pass_id, viewport_rect);
+
+ RenderPass::Id child_pass_id(2, 2);
+ gfx::Rect pass_rect(this->device_viewport_size_);
+ gfx::Transform transform_to_root;
+ scoped_ptr<RenderPass> child_pass =
+ CreateTestRenderPass(child_pass_id, pass_rect, transform_to_root);
+
+ gfx::Transform content_to_target_transform;
+ scoped_ptr<SharedQuadState> shared_state =
+ CreateTestSharedQuadState(content_to_target_transform, viewport_rect);
+ shared_state->opacity = 0.5f;
+
+ scoped_ptr<SolidColorDrawQuad> blue = SolidColorDrawQuad::Create();
+ blue->SetNew(shared_state.get(),
+ gfx::Rect(0,
+ 0,
+ this->device_viewport_size_.width(),
+ this->device_viewport_size_.height() / 2),
+ SK_ColorBLUE,
+ false);
+ scoped_ptr<SolidColorDrawQuad> yellow = SolidColorDrawQuad::Create();
+ yellow->SetNew(shared_state.get(),
+ gfx::Rect(0,
+ this->device_viewport_size_.height() / 2,
+ this->device_viewport_size_.width(),
+ this->device_viewport_size_.height() / 2),
+ SK_ColorYELLOW,
+ false);
+
+ scoped_ptr<SharedQuadState> blank_state =
+ CreateTestSharedQuadState(content_to_target_transform, viewport_rect);
+
+ scoped_ptr<SolidColorDrawQuad> white = SolidColorDrawQuad::Create();
+ white->SetNew(blank_state.get(),
+ viewport_rect,
+ SK_ColorWHITE,
+ false);
+
+ child_pass->quad_list.push_back(blue.PassAs<DrawQuad>());
+ child_pass->quad_list.push_back(yellow.PassAs<DrawQuad>());
+ child_pass->quad_list.push_back(white.PassAs<DrawQuad>());
+
+ scoped_ptr<SharedQuadState> pass_shared_state =
+ CreateTestSharedQuadState(gfx::Transform(), pass_rect);
+
+ SkScalar 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] = 0;
+ matrix[4] = 20.f;
+ matrix[5] = 0.213f - 0.213f * amount;
+ matrix[6] = 0.715f + 0.285f * amount;
+ matrix[7] = 1.f - (matrix[5] + matrix[6]);
+ matrix[8] = 0;
+ matrix[9] = 200.f;
+ matrix[10] = 0.213f - 0.213f * amount;
+ matrix[11] = 0.715f - 0.715f * amount;
+ matrix[12] = 1.f - (matrix[10] + matrix[11]);
+ matrix[13] = 0;
+ matrix[14] = 1.5f;
+ matrix[15] = matrix[16] = matrix[17] = matrix[19] = 0;
+ matrix[18] = 1;
+ skia::RefPtr<SkColorFilter> colorFilter(skia::AdoptRef(
+ new SkColorMatrixFilter(matrix)));
+ skia::RefPtr<SkImageFilter> filter =
+ skia::AdoptRef(SkColorFilterImageFilter::Create(colorFilter.get(), NULL));
+
+ scoped_ptr<RenderPassDrawQuad> render_pass_quad =
+ RenderPassDrawQuad::Create();
+ render_pass_quad->SetNew(pass_shared_state.get(),
+ pass_rect,
+ child_pass_id,
+ false,
+ 0,
+ pass_rect,
+ gfx::RectF(),
+ FilterOperations(),
+ filter,
+ FilterOperations());
+
+ root_pass->quad_list.push_back(render_pass_quad.PassAs<DrawQuad>());
+ RenderPassList pass_list;
+
+ pass_list.push_back(child_pass.Pass());
+ pass_list.push_back(root_pass.Pass());
+
+ // This test has alpha=254 for the software renderer vs. alpha=255 for the gl
+ // renderer so use a fuzzy comparator.
+ EXPECT_TRUE(this->RunPixelTest(
+ &pass_list,
+ base::FilePath(FILE_PATH_LITERAL("blue_yellow_alpha_translate.png")),
+ FuzzyForSoftwareOnlyPixelComparator<TypeParam>(false)));
+}
+
+TYPED_TEST(RendererPixelTest, EnlargedRenderPassTexture) {
+ gfx::Rect viewport_rect(this->device_viewport_size_);
+
+ RenderPass::Id root_pass_id(1, 1);
+ scoped_ptr<RenderPass> root_pass =
+ CreateTestRootRenderPass(root_pass_id, viewport_rect);
+
+ RenderPass::Id child_pass_id(2, 2);
+ gfx::Rect pass_rect(this->device_viewport_size_);
+ gfx::Transform transform_to_root;
+ scoped_ptr<RenderPass> child_pass =
+ CreateTestRenderPass(child_pass_id, pass_rect, transform_to_root);
+
+ gfx::Transform content_to_target_transform;
+ scoped_ptr<SharedQuadState> shared_state =
+ CreateTestSharedQuadState(content_to_target_transform, viewport_rect);
+
+ scoped_ptr<SolidColorDrawQuad> blue = SolidColorDrawQuad::Create();
+ blue->SetNew(shared_state.get(),
+ gfx::Rect(0,
+ 0,
+ this->device_viewport_size_.width(),
+ this->device_viewport_size_.height() / 2),
+ SK_ColorBLUE,
+ false);
+ scoped_ptr<SolidColorDrawQuad> yellow = SolidColorDrawQuad::Create();
+ yellow->SetNew(shared_state.get(),
+ gfx::Rect(0,
+ this->device_viewport_size_.height() / 2,
+ this->device_viewport_size_.width(),
+ this->device_viewport_size_.height() / 2),
+ SK_ColorYELLOW,
+ false);
+
+ child_pass->quad_list.push_back(blue.PassAs<DrawQuad>());
+ child_pass->quad_list.push_back(yellow.PassAs<DrawQuad>());
+
+ scoped_ptr<SharedQuadState> pass_shared_state =
+ CreateTestSharedQuadState(gfx::Transform(), pass_rect);
+ root_pass->quad_list.push_back(
+ CreateTestRenderPassDrawQuad(pass_shared_state.get(),
+ pass_rect,
+ child_pass_id));
+
+ RenderPassList pass_list;
+ pass_list.push_back(child_pass.Pass());
+ pass_list.push_back(root_pass.Pass());
+
+ this->renderer_->SetEnlargePassTextureAmountForTesting(gfx::Vector2d(50, 75));
+
+ EXPECT_TRUE(this->RunPixelTest(
+ &pass_list,
+ base::FilePath(FILE_PATH_LITERAL("blue_yellow.png")),
+ ExactPixelComparator(true)));
+}
+
+TYPED_TEST(RendererPixelTest, EnlargedRenderPassTextureWithAntiAliasing) {
+ gfx::Rect viewport_rect(this->device_viewport_size_);
+
+ RenderPass::Id root_pass_id(1, 1);
+ scoped_ptr<RenderPass> root_pass =
+ CreateTestRootRenderPass(root_pass_id, viewport_rect);
+
+ RenderPass::Id child_pass_id(2, 2);
+ gfx::Rect pass_rect(this->device_viewport_size_);
+ gfx::Transform transform_to_root;
+ scoped_ptr<RenderPass> child_pass =
+ CreateTestRenderPass(child_pass_id, pass_rect, transform_to_root);
+
+ gfx::Transform content_to_target_transform;
+ scoped_ptr<SharedQuadState> shared_state =
+ CreateTestSharedQuadState(content_to_target_transform, viewport_rect);
+
+ scoped_ptr<SolidColorDrawQuad> blue = SolidColorDrawQuad::Create();
+ blue->SetNew(shared_state.get(),
+ gfx::Rect(0,
+ 0,
+ this->device_viewport_size_.width(),
+ this->device_viewport_size_.height() / 2),
+ SK_ColorBLUE,
+ false);
+ scoped_ptr<SolidColorDrawQuad> yellow = SolidColorDrawQuad::Create();
+ yellow->SetNew(shared_state.get(),
+ gfx::Rect(0,
+ this->device_viewport_size_.height() / 2,
+ this->device_viewport_size_.width(),
+ this->device_viewport_size_.height() / 2),
+ SK_ColorYELLOW,
+ false);
+
+ child_pass->quad_list.push_back(blue.PassAs<DrawQuad>());
+ child_pass->quad_list.push_back(yellow.PassAs<DrawQuad>());
+
+ gfx::Transform aa_transform;
+ aa_transform.Translate(0.5, 0.0);
+
+ scoped_ptr<SharedQuadState> pass_shared_state =
+ CreateTestSharedQuadState(aa_transform, pass_rect);
+ root_pass->quad_list.push_back(
+ CreateTestRenderPassDrawQuad(pass_shared_state.get(),
+ pass_rect,
+ child_pass_id));
+
+ scoped_ptr<SharedQuadState> root_shared_state =
+ CreateTestSharedQuadState(gfx::Transform(), viewport_rect);
+ scoped_ptr<SolidColorDrawQuad> background = SolidColorDrawQuad::Create();
+ background->SetNew(root_shared_state.get(),
+ gfx::Rect(this->device_viewport_size_),
+ SK_ColorWHITE,
+ false);
+ root_pass->quad_list.push_back(background.PassAs<DrawQuad>());
+
+ RenderPassList pass_list;
+ pass_list.push_back(child_pass.Pass());
+ pass_list.push_back(root_pass.Pass());
+
+ this->renderer_->SetEnlargePassTextureAmountForTesting(gfx::Vector2d(50, 75));
+
+ EXPECT_TRUE(this->RunPixelTest(
+ &pass_list,
+ base::FilePath(FILE_PATH_LITERAL("blue_yellow_anti_aliasing.png")),
+ FuzzyPixelOffByOneComparator(true)));
+}
+
+template <typename RendererType>
+class RendererPixelTestWithBackgroundFilter
+ : public RendererPixelTest<RendererType> {
+ protected:
+ void SetUpRenderPassList() {
+ gfx::Rect device_viewport_rect(this->device_viewport_size_);
+
+ RenderPass::Id root_id(1, 1);
+ scoped_ptr<RenderPass> root_pass =
+ CreateTestRootRenderPass(root_id, device_viewport_rect);
+ root_pass->has_transparent_background = false;
+
+ gfx::Transform identity_content_to_target_transform;
+
+ RenderPass::Id filter_pass_id(2, 1);
+ gfx::Transform transform_to_root;
+ scoped_ptr<RenderPass> filter_pass =
+ CreateTestRenderPass(filter_pass_id,
+ filter_pass_content_rect_,
+ transform_to_root);
+
+ // A non-visible quad in the filtering render pass.
+ {
+ scoped_ptr<SharedQuadState> shared_state =
+ CreateTestSharedQuadState(identity_content_to_target_transform,
+ filter_pass_content_rect_);
+ scoped_ptr<SolidColorDrawQuad> color_quad = SolidColorDrawQuad::Create();
+ color_quad->SetNew(shared_state.get(),
+ filter_pass_content_rect_,
+ SK_ColorTRANSPARENT,
+ false);
+ filter_pass->quad_list.push_back(color_quad.PassAs<DrawQuad>());
+ filter_pass->shared_quad_state_list.push_back(shared_state.Pass());
+ }
+
+ {
+ scoped_ptr<SharedQuadState> shared_state =
+ CreateTestSharedQuadState(filter_pass_to_target_transform_,
+ filter_pass_content_rect_);
+ scoped_ptr<RenderPassDrawQuad> filter_pass_quad =
+ RenderPassDrawQuad::Create();
+ filter_pass_quad->SetNew(
+ shared_state.get(),
+ filter_pass_content_rect_,
+ filter_pass_id,
+ false, // is_replica
+ 0, // mask_resource_id
+ filter_pass_content_rect_, // contents_changed_since_last_frame
+ gfx::RectF(), // mask_uv_rect
+ FilterOperations(), // filters
+ skia::RefPtr<SkImageFilter>(), // filter
+ this->background_filters_);
+ root_pass->quad_list.push_back(filter_pass_quad.PassAs<DrawQuad>());
+ root_pass->shared_quad_state_list.push_back(shared_state.Pass());
+ }
+
+ const int kColumnWidth = device_viewport_rect.width() / 3;
+
+ gfx::Rect left_rect = gfx::Rect(0, 0, kColumnWidth, 20);
+ for (int i = 0; left_rect.y() < device_viewport_rect.height(); ++i) {
+ scoped_ptr<SharedQuadState> shared_state =
+ CreateTestSharedQuadState(identity_content_to_target_transform,
+ left_rect);
+ scoped_ptr<SolidColorDrawQuad> color_quad = SolidColorDrawQuad::Create();
+ color_quad->SetNew(shared_state.get(), left_rect, SK_ColorGREEN, false);
+ root_pass->quad_list.push_back(color_quad.PassAs<DrawQuad>());
+ root_pass->shared_quad_state_list.push_back(shared_state.Pass());
+ left_rect += gfx::Vector2d(0, left_rect.height() + 1);
+ }
+
+ gfx::Rect middle_rect = gfx::Rect(kColumnWidth+1, 0, kColumnWidth, 20);
+ for (int i = 0; middle_rect.y() < device_viewport_rect.height(); ++i) {
+ scoped_ptr<SharedQuadState> shared_state =
+ CreateTestSharedQuadState(identity_content_to_target_transform,
+ middle_rect);
+ scoped_ptr<SolidColorDrawQuad> color_quad = SolidColorDrawQuad::Create();
+ color_quad->SetNew(shared_state.get(), middle_rect, SK_ColorRED, false);
+ root_pass->quad_list.push_back(color_quad.PassAs<DrawQuad>());
+ root_pass->shared_quad_state_list.push_back(shared_state.Pass());
+ middle_rect += gfx::Vector2d(0, middle_rect.height() + 1);
+ }
+
+ gfx::Rect right_rect = gfx::Rect((kColumnWidth+1)*2, 0, kColumnWidth, 20);
+ for (int i = 0; right_rect.y() < device_viewport_rect.height(); ++i) {
+ scoped_ptr<SharedQuadState> shared_state =
+ CreateTestSharedQuadState(identity_content_to_target_transform,
+ right_rect);
+ scoped_ptr<SolidColorDrawQuad> color_quad = SolidColorDrawQuad::Create();
+ color_quad->SetNew(shared_state.get(), right_rect, SK_ColorBLUE, false);
+ root_pass->quad_list.push_back(color_quad.PassAs<DrawQuad>());
+ root_pass->shared_quad_state_list.push_back(shared_state.Pass());
+ right_rect += gfx::Vector2d(0, right_rect.height() + 1);
+ }
+
+ scoped_ptr<SharedQuadState> shared_state =
+ CreateTestSharedQuadState(identity_content_to_target_transform,
+ device_viewport_rect);
+ scoped_ptr<SolidColorDrawQuad> background_quad =
+ SolidColorDrawQuad::Create();
+ background_quad->SetNew(shared_state.get(),
+ device_viewport_rect,
+ SK_ColorWHITE,
+ false);
+ root_pass->quad_list.push_back(background_quad.PassAs<DrawQuad>());
+ root_pass->shared_quad_state_list.push_back(shared_state.Pass());
+
+ pass_list_.push_back(filter_pass.Pass());
+ pass_list_.push_back(root_pass.Pass());
+ }
+
+ RenderPassList pass_list_;
+ FilterOperations background_filters_;
+ gfx::Transform filter_pass_to_target_transform_;
+ gfx::Rect filter_pass_content_rect_;
+};
+
+typedef ::testing::Types<GLRenderer, SoftwareRenderer>
+ BackgroundFilterRendererTypes;
+TYPED_TEST_CASE(RendererPixelTestWithBackgroundFilter,
+ BackgroundFilterRendererTypes);
+
+typedef RendererPixelTestWithBackgroundFilter<GLRenderer>
+GLRendererPixelTestWithBackgroundFilter;
+
+// TODO(skaslev): The software renderer does not support filters yet.
+TEST_F(GLRendererPixelTestWithBackgroundFilter, InvertFilter) {
+ this->background_filters_.Append(
+ FilterOperation::CreateInvertFilter(1.f));
+
+ this->filter_pass_content_rect_ = gfx::Rect(this->device_viewport_size_);
+ this->filter_pass_content_rect_.Inset(12, 14, 16, 18);
+
+ this->SetUpRenderPassList();
+ EXPECT_TRUE(this->RunPixelTest(
+ &this->pass_list_,
+ base::FilePath(FILE_PATH_LITERAL("background_filter.png")),
+ ExactPixelComparator(true)));
+}
+
+class ExternalStencilPixelTest : public GLRendererPixelTest {
+ protected:
+ void ClearBackgroundToGreen() {
+ WebKit::WebGraphicsContext3D* context3d = output_surface_->context3d();
+ output_surface_->EnsureBackbuffer();
+ output_surface_->Reshape(device_viewport_size_, 1);
+ context3d->clearColor(0.f, 1.f, 0.f, 1.f);
+ context3d->clear(GL_COLOR_BUFFER_BIT);
+ }
+
+ void PopulateStencilBuffer() {
+ // Set two quadrants of the stencil buffer to 1.
+ WebKit::WebGraphicsContext3D* context3d = output_surface_->context3d();
+ ASSERT_TRUE(context3d->getContextAttributes().stencil);
+ output_surface_->EnsureBackbuffer();
+ output_surface_->Reshape(device_viewport_size_, 1);
+ context3d->clearStencil(0);
+ context3d->clear(GL_STENCIL_BUFFER_BIT);
+ context3d->enable(GL_SCISSOR_TEST);
+ context3d->clearStencil(1);
+ context3d->scissor(0,
+ 0,
+ device_viewport_size_.width() / 2,
+ device_viewport_size_.height() / 2);
+ context3d->clear(GL_STENCIL_BUFFER_BIT);
+ context3d->scissor(device_viewport_size_.width() / 2,
+ device_viewport_size_.height() / 2,
+ device_viewport_size_.width(),
+ device_viewport_size_.height());
+ context3d->clear(GL_STENCIL_BUFFER_BIT);
+ }
+};
+
+TEST_F(ExternalStencilPixelTest, StencilTestEnabled) {
+ ClearBackgroundToGreen();
+ PopulateStencilBuffer();
+ this->EnableExternalStencilTest();
+
+ // Draw a blue quad that covers the entire device viewport. It should be
+ // clipped to the bottom left and top right corners by the external stencil.
+ gfx::Rect rect(this->device_viewport_size_);
+ RenderPass::Id id(1, 1);
+ scoped_ptr<RenderPass> pass = CreateTestRootRenderPass(id, rect);
+ scoped_ptr<SharedQuadState> blue_shared_state =
+ CreateTestSharedQuadState(gfx::Transform(), rect);
+ scoped_ptr<SolidColorDrawQuad> blue = SolidColorDrawQuad::Create();
+ blue->SetNew(blue_shared_state.get(), rect, SK_ColorBLUE, false);
+ pass->quad_list.push_back(blue.PassAs<DrawQuad>());
+ pass->has_transparent_background = false;
+ RenderPassList pass_list;
+ pass_list.push_back(pass.Pass());
+
+ EXPECT_TRUE(this->RunPixelTest(
+ &pass_list,
+ base::FilePath(FILE_PATH_LITERAL("four_blue_green_checkers.png")),
+ ExactPixelComparator(true)));
+}
+
+TEST_F(ExternalStencilPixelTest, StencilTestDisabled) {
+ PopulateStencilBuffer();
+
+ // Draw a green quad that covers the entire device viewport. The stencil
+ // buffer should be ignored.
+ gfx::Rect rect(this->device_viewport_size_);
+ RenderPass::Id id(1, 1);
+ scoped_ptr<RenderPass> pass = CreateTestRootRenderPass(id, rect);
+ scoped_ptr<SharedQuadState> green_shared_state =
+ CreateTestSharedQuadState(gfx::Transform(), rect);
+ scoped_ptr<SolidColorDrawQuad> green = SolidColorDrawQuad::Create();
+ green->SetNew(green_shared_state.get(), rect, SK_ColorGREEN, false);
+ pass->quad_list.push_back(green.PassAs<DrawQuad>());
+ RenderPassList pass_list;
+ pass_list.push_back(pass.Pass());
+
+ EXPECT_TRUE(this->RunPixelTest(
+ &pass_list,
+ base::FilePath(FILE_PATH_LITERAL("green.png")),
+ ExactPixelComparator(true)));
+}
+
+TEST_F(ExternalStencilPixelTest, RenderSurfacesIgnoreStencil) {
+ // The stencil test should apply only to the final render pass.
+ ClearBackgroundToGreen();
+ PopulateStencilBuffer();
+ this->EnableExternalStencilTest();
+
+ gfx::Rect viewport_rect(this->device_viewport_size_);
+
+ RenderPass::Id root_pass_id(1, 1);
+ scoped_ptr<RenderPass> root_pass =
+ CreateTestRootRenderPass(root_pass_id, viewport_rect);
+ root_pass->has_transparent_background = false;
+
+ RenderPass::Id child_pass_id(2, 2);
+ gfx::Rect pass_rect(this->device_viewport_size_);
+ gfx::Transform transform_to_root;
+ scoped_ptr<RenderPass> child_pass =
+ CreateTestRenderPass(child_pass_id, pass_rect, transform_to_root);
+
+ gfx::Transform content_to_target_transform;
+ scoped_ptr<SharedQuadState> shared_state =
+ CreateTestSharedQuadState(content_to_target_transform, viewport_rect);
+
+ scoped_ptr<SolidColorDrawQuad> blue = SolidColorDrawQuad::Create();
+ blue->SetNew(shared_state.get(),
+ gfx::Rect(0,
+ 0,
+ this->device_viewport_size_.width(),
+ this->device_viewport_size_.height()),
+ SK_ColorBLUE,
+ false);
+ child_pass->quad_list.push_back(blue.PassAs<DrawQuad>());
+
+ scoped_ptr<SharedQuadState> pass_shared_state =
+ CreateTestSharedQuadState(gfx::Transform(), pass_rect);
+ root_pass->quad_list.push_back(
+ CreateTestRenderPassDrawQuad(pass_shared_state.get(),
+ pass_rect,
+ child_pass_id));
+ RenderPassList pass_list;
+ pass_list.push_back(child_pass.Pass());
+ pass_list.push_back(root_pass.Pass());
+
+ EXPECT_TRUE(this->RunPixelTest(
+ &pass_list,
+ base::FilePath(FILE_PATH_LITERAL("four_blue_green_checkers.png")),
+ ExactPixelComparator(true)));
+}
+
+// Software renderer does not support anti-aliased edges.
+TEST_F(GLRendererPixelTest, AntiAliasing) {
+ gfx::Rect rect(this->device_viewport_size_);
+
+ RenderPass::Id id(1, 1);
+ scoped_ptr<RenderPass> pass = CreateTestRootRenderPass(id, rect);
+
+ gfx::Transform red_content_to_target_transform;
+ red_content_to_target_transform.Rotate(10);
+ scoped_ptr<SharedQuadState> red_shared_state =
+ CreateTestSharedQuadState(red_content_to_target_transform, rect);
+
+ scoped_ptr<SolidColorDrawQuad> red = SolidColorDrawQuad::Create();
+ red->SetNew(red_shared_state.get(), rect, SK_ColorRED, false);
+
+ pass->quad_list.push_back(red.PassAs<DrawQuad>());
+
+ gfx::Transform yellow_content_to_target_transform;
+ yellow_content_to_target_transform.Rotate(5);
+ scoped_ptr<SharedQuadState> yellow_shared_state =
+ CreateTestSharedQuadState(yellow_content_to_target_transform, rect);
+
+ scoped_ptr<SolidColorDrawQuad> yellow = SolidColorDrawQuad::Create();
+ yellow->SetNew(yellow_shared_state.get(), rect, SK_ColorYELLOW, false);
+
+ pass->quad_list.push_back(yellow.PassAs<DrawQuad>());
+
+ gfx::Transform blue_content_to_target_transform;
+ scoped_ptr<SharedQuadState> blue_shared_state =
+ CreateTestSharedQuadState(blue_content_to_target_transform, rect);
+
+ scoped_ptr<SolidColorDrawQuad> blue = SolidColorDrawQuad::Create();
+ blue->SetNew(blue_shared_state.get(), rect, SK_ColorBLUE, false);
+
+ pass->quad_list.push_back(blue.PassAs<DrawQuad>());
+
+ RenderPassList pass_list;
+ pass_list.push_back(pass.Pass());
+
+ EXPECT_TRUE(this->RunPixelTest(
+ &pass_list,
+ base::FilePath(FILE_PATH_LITERAL("anti_aliasing.png")),
+ FuzzyPixelOffByOneComparator(true)));
+}
+
+// This test tests that anti-aliasing works for axis aligned quads.
+// Anti-aliasing is only supported in the gl renderer.
+TEST_F(GLRendererPixelTest, AxisAligned) {
+ gfx::Rect rect(this->device_viewport_size_);
+
+ RenderPass::Id id(1, 1);
+ gfx::Transform transform_to_root;
+ scoped_ptr<RenderPass> pass =
+ CreateTestRenderPass(id, rect, transform_to_root);
+
+ gfx::Transform red_content_to_target_transform;
+ red_content_to_target_transform.Translate(50, 50);
+ red_content_to_target_transform.Scale(
+ 0.5f + 1.0f / (rect.width() * 2.0f),
+ 0.5f + 1.0f / (rect.height() * 2.0f));
+ scoped_ptr<SharedQuadState> red_shared_state =
+ CreateTestSharedQuadState(red_content_to_target_transform, rect);
+
+ scoped_ptr<SolidColorDrawQuad> red = SolidColorDrawQuad::Create();
+ red->SetNew(red_shared_state.get(), rect, SK_ColorRED, false);
+
+ pass->quad_list.push_back(red.PassAs<DrawQuad>());
+
+ gfx::Transform yellow_content_to_target_transform;
+ yellow_content_to_target_transform.Translate(25.5f, 25.5f);
+ yellow_content_to_target_transform.Scale(0.5f, 0.5f);
+ scoped_ptr<SharedQuadState> yellow_shared_state =
+ CreateTestSharedQuadState(yellow_content_to_target_transform, rect);
+
+ scoped_ptr<SolidColorDrawQuad> yellow = SolidColorDrawQuad::Create();
+ yellow->SetNew(yellow_shared_state.get(), rect, SK_ColorYELLOW, false);
+
+ pass->quad_list.push_back(yellow.PassAs<DrawQuad>());
+
+ gfx::Transform blue_content_to_target_transform;
+ scoped_ptr<SharedQuadState> blue_shared_state =
+ CreateTestSharedQuadState(blue_content_to_target_transform, rect);
+
+ scoped_ptr<SolidColorDrawQuad> blue = SolidColorDrawQuad::Create();
+ blue->SetNew(blue_shared_state.get(), rect, SK_ColorBLUE, false);
+
+ pass->quad_list.push_back(blue.PassAs<DrawQuad>());
+
+ RenderPassList pass_list;
+ pass_list.push_back(pass.Pass());
+
+ EXPECT_TRUE(this->RunPixelTest(
+ &pass_list,
+ base::FilePath(FILE_PATH_LITERAL("axis_aligned.png")),
+ ExactPixelComparator(true)));
+}
+
+// This test tests that forcing anti-aliasing off works as expected.
+// Anti-aliasing is only supported in the gl renderer.
+TEST_F(GLRendererPixelTest, ForceAntiAliasingOff) {
+ gfx::Rect rect(this->device_viewport_size_);
+
+ RenderPass::Id id(1, 1);
+ gfx::Transform transform_to_root;
+ scoped_ptr<RenderPass> pass =
+ CreateTestRenderPass(id, rect, transform_to_root);
+
+ gfx::Transform hole_content_to_target_transform;
+ hole_content_to_target_transform.Translate(50, 50);
+ hole_content_to_target_transform.Scale(
+ 0.5f + 1.0f / (rect.width() * 2.0f),
+ 0.5f + 1.0f / (rect.height() * 2.0f));
+ scoped_ptr<SharedQuadState> hole_shared_state =
+ CreateTestSharedQuadState(hole_content_to_target_transform, rect);
+
+ scoped_ptr<SolidColorDrawQuad> hole = SolidColorDrawQuad::Create();
+ hole->SetAll(hole_shared_state.get(), rect, rect, rect, false,
+ SK_ColorTRANSPARENT, true);
+ pass->quad_list.push_back(hole.PassAs<DrawQuad>());
+
+ gfx::Transform green_content_to_target_transform;
+ scoped_ptr<SharedQuadState> green_shared_state =
+ CreateTestSharedQuadState(green_content_to_target_transform, rect);
+
+ scoped_ptr<SolidColorDrawQuad> green = SolidColorDrawQuad::Create();
+ green->SetNew(green_shared_state.get(), rect, SK_ColorGREEN, false);
+
+ pass->quad_list.push_back(green.PassAs<DrawQuad>());
+
+ RenderPassList pass_list;
+ pass_list.push_back(pass.Pass());
+
+ EXPECT_TRUE(this->RunPixelTest(
+ &pass_list,
+ base::FilePath(FILE_PATH_LITERAL("force_anti_aliasing_off.png")),
+ ExactPixelComparator(false)));
+}
+
+TEST_F(GLRendererPixelTest, AntiAliasingPerspective) {
+ gfx::Rect rect(this->device_viewport_size_);
+
+ scoped_ptr<RenderPass> pass =
+ CreateTestRootRenderPass(RenderPass::Id(1, 1), rect);
+
+ gfx::Rect red_rect(0, 0, 180, 500);
+ gfx::Transform red_content_to_target_transform(
+ 1.0, 2.4520, 10.6206, 19.0,
+ 0.0, 0.3528, 5.9737, 9.5,
+ 0.0, -0.2250, -0.9744, 0.0,
+ 0.0, 0.0225, 0.0974, 1.0);
+ scoped_ptr<SharedQuadState> red_shared_state =
+ CreateTestSharedQuadState(red_content_to_target_transform, red_rect);
+ scoped_ptr<SolidColorDrawQuad> red = SolidColorDrawQuad::Create();
+ red->SetNew(red_shared_state.get(), red_rect, SK_ColorRED, false);
+ pass->quad_list.push_back(red.PassAs<DrawQuad>());
+
+ gfx::Rect green_rect(19, 7, 180, 10);
+ scoped_ptr<SharedQuadState> green_shared_state =
+ CreateTestSharedQuadState(gfx::Transform(), green_rect);
+ scoped_ptr<SolidColorDrawQuad> green = SolidColorDrawQuad::Create();
+ green->SetNew(green_shared_state.get(), green_rect, SK_ColorGREEN, false);
+ pass->quad_list.push_back(green.PassAs<DrawQuad>());
+
+ scoped_ptr<SharedQuadState> blue_shared_state =
+ CreateTestSharedQuadState(gfx::Transform(), rect);
+ scoped_ptr<SolidColorDrawQuad> blue = SolidColorDrawQuad::Create();
+ blue->SetNew(blue_shared_state.get(), rect, SK_ColorBLUE, false);
+ pass->quad_list.push_back(blue.PassAs<DrawQuad>());
+
+ RenderPassList pass_list;
+ pass_list.push_back(pass.Pass());
+
+ EXPECT_TRUE(this->RunPixelTest(
+ &pass_list,
+ base::FilePath(FILE_PATH_LITERAL("anti_aliasing_perspective.png")),
+ FuzzyPixelOffByOneComparator(true)));
+}
+
+TYPED_TEST(RendererPixelTestWithSkiaGPUBackend, PictureDrawQuadIdentityScale) {
+ gfx::Size pile_tile_size(1000, 1000);
+ gfx::Rect viewport(this->device_viewport_size_);
+ bool use_skia_gpu_backend = this->UseSkiaGPUBackend();
+ // TODO(enne): the renderer should figure this out on its own.
+ bool contents_swizzled = !PlatformColor::SameComponentOrder(GL_RGBA);
+
+ RenderPass::Id id(1, 1);
+ gfx::Transform transform_to_root;
+ scoped_ptr<RenderPass> pass =
+ CreateTestRenderPass(id, viewport, transform_to_root);
+
+ // One clipped blue quad in the lower right corner. Outside the clip
+ // is red, which should not appear.
+ gfx::Rect blue_rect(gfx::Size(100, 100));
+ gfx::Rect blue_clip_rect(gfx::Point(50, 50), gfx::Size(50, 50));
+ scoped_refptr<FakePicturePileImpl> blue_pile =
+ FakePicturePileImpl::CreateFilledPile(pile_tile_size, blue_rect.size());
+ SkPaint red_paint;
+ red_paint.setColor(SK_ColorRED);
+ blue_pile->add_draw_rect_with_paint(blue_rect, red_paint);
+ SkPaint blue_paint;
+ blue_paint.setColor(SK_ColorBLUE);
+ blue_pile->add_draw_rect_with_paint(blue_clip_rect, blue_paint);
+ blue_pile->RerecordPile();
+
+ gfx::Transform blue_content_to_target_transform;
+ gfx::Vector2d offset(viewport.bottom_right() - blue_rect.bottom_right());
+ blue_content_to_target_transform.Translate(offset.x(), offset.y());
+ gfx::RectF blue_scissor_rect = blue_clip_rect;
+ blue_content_to_target_transform.TransformRect(&blue_scissor_rect);
+ scoped_ptr<SharedQuadState> blue_shared_state =
+ CreateTestSharedQuadStateClipped(blue_content_to_target_transform,
+ blue_rect,
+ gfx::ToEnclosingRect(blue_scissor_rect));
+
+ scoped_ptr<PictureDrawQuad> blue_quad = PictureDrawQuad::Create();
+
+ blue_quad->SetNew(blue_shared_state.get(),
+ viewport, // Intentionally bigger than clip.
+ gfx::Rect(),
+ viewport,
+ viewport.size(),
+ contents_swizzled,
+ viewport,
+ 1.f,
+ use_skia_gpu_backend,
+ blue_pile);
+ pass->quad_list.push_back(blue_quad.PassAs<DrawQuad>());
+
+ // One viewport-filling green quad.
+ scoped_refptr<FakePicturePileImpl> green_pile =
+ FakePicturePileImpl::CreateFilledPile(pile_tile_size, viewport.size());
+ SkPaint green_paint;
+ green_paint.setColor(SK_ColorGREEN);
+ green_pile->add_draw_rect_with_paint(viewport, green_paint);
+ green_pile->RerecordPile();
+
+ gfx::Transform green_content_to_target_transform;
+ scoped_ptr<SharedQuadState> green_shared_state =
+ CreateTestSharedQuadState(green_content_to_target_transform, viewport);
+
+ scoped_ptr<PictureDrawQuad> green_quad = PictureDrawQuad::Create();
+ green_quad->SetNew(green_shared_state.get(),
+ viewport,
+ gfx::Rect(),
+ gfx::RectF(0.f, 0.f, 1.f, 1.f),
+ viewport.size(),
+ contents_swizzled,
+ viewport,
+ 1.f,
+ use_skia_gpu_backend,
+ green_pile);
+ pass->quad_list.push_back(green_quad.PassAs<DrawQuad>());
+
+ RenderPassList pass_list;
+ pass_list.push_back(pass.Pass());
+
+ EXPECT_TRUE(this->RunPixelTest(
+ &pass_list,
+ base::FilePath(FILE_PATH_LITERAL("green_with_blue_corner.png")),
+ ExactPixelComparator(true)));
+}
+
+TYPED_TEST(RendererPixelTestWithSkiaGPUBackend,
+ PictureDrawQuadNonIdentityScale) {
+ gfx::Size pile_tile_size(1000, 1000);
+ gfx::Rect viewport(this->device_viewport_size_);
+ bool use_skia_gpu_backend = this->UseSkiaGPUBackend();
+ // TODO(enne): the renderer should figure this out on its own.
+ bool contents_swizzled = !PlatformColor::SameComponentOrder(GL_RGBA);
+
+ RenderPass::Id id(1, 1);
+ gfx::Transform transform_to_root;
+ scoped_ptr<RenderPass> pass =
+ CreateTestRenderPass(id, viewport, transform_to_root);
+
+ // As scaling up the blue checkerboards will cause sampling on the GPU,
+ // a few extra "cleanup rects" need to be added to clobber the blending
+ // to make the output image more clean. This will also test subrects
+ // of the layer.
+ gfx::Transform green_content_to_target_transform;
+ gfx::Rect green_rect1(gfx::Point(80, 0), gfx::Size(20, 100));
+ gfx::Rect green_rect2(gfx::Point(0, 80), gfx::Size(100, 20));
+ scoped_refptr<FakePicturePileImpl> green_pile =
+ FakePicturePileImpl::CreateFilledPile(pile_tile_size, viewport.size());
+ SkPaint red_paint;
+ red_paint.setColor(SK_ColorRED);
+ green_pile->add_draw_rect_with_paint(viewport, red_paint);
+ SkPaint green_paint;
+ green_paint.setColor(SK_ColorGREEN);
+ green_pile->add_draw_rect_with_paint(green_rect1, green_paint);
+ green_pile->add_draw_rect_with_paint(green_rect2, green_paint);
+ green_pile->RerecordPile();
+
+ scoped_ptr<SharedQuadState> top_right_green_shared_quad_state =
+ CreateTestSharedQuadState(green_content_to_target_transform, viewport);
+
+ scoped_ptr<PictureDrawQuad> green_quad1 = PictureDrawQuad::Create();
+ green_quad1->SetNew(top_right_green_shared_quad_state.get(),
+ green_rect1,
+ gfx::Rect(),
+ gfx::RectF(green_rect1.size()),
+ green_rect1.size(),
+ contents_swizzled,
+ green_rect1,
+ 1.f,
+ use_skia_gpu_backend,
+ green_pile);
+ pass->quad_list.push_back(green_quad1.PassAs<DrawQuad>());
+
+ scoped_ptr<PictureDrawQuad> green_quad2 = PictureDrawQuad::Create();
+ green_quad2->SetNew(top_right_green_shared_quad_state.get(),
+ green_rect2,
+ gfx::Rect(),
+ gfx::RectF(green_rect2.size()),
+ green_rect2.size(),
+ contents_swizzled,
+ green_rect2,
+ 1.f,
+ use_skia_gpu_backend,
+ green_pile);
+ pass->quad_list.push_back(green_quad2.PassAs<DrawQuad>());
+
+ // Add a green clipped checkerboard in the bottom right to help test
+ // interleaving picture quad content and solid color content.
+ gfx::Rect bottom_right_rect(
+ gfx::Point(viewport.width() / 2, viewport.height() / 2),
+ gfx::Size(viewport.width() / 2, viewport.height() / 2));
+ scoped_ptr<SharedQuadState> bottom_right_green_shared_state =
+ CreateTestSharedQuadStateClipped(
+ green_content_to_target_transform, viewport, bottom_right_rect);
+ scoped_ptr<SolidColorDrawQuad> bottom_right_color_quad =
+ SolidColorDrawQuad::Create();
+ bottom_right_color_quad->SetNew(
+ bottom_right_green_shared_state.get(), viewport, SK_ColorGREEN, false);
+ pass->quad_list.push_back(bottom_right_color_quad.PassAs<DrawQuad>());
+
+ // Add two blue checkerboards taking up the bottom left and top right,
+ // but use content scales as content rects to make this happen.
+ // The content is at a 4x content scale.
+ gfx::Rect layer_rect(gfx::Size(20, 30));
+ float contents_scale = 4.f;
+ // Two rects that touch at their corners, arbitrarily placed in the layer.
+ gfx::RectF blue_layer_rect1(gfx::PointF(5.5f, 9.0f), gfx::SizeF(2.5f, 2.5f));
+ gfx::RectF blue_layer_rect2(gfx::PointF(8.0f, 6.5f), gfx::SizeF(2.5f, 2.5f));
+ gfx::RectF union_layer_rect = blue_layer_rect1;
+ union_layer_rect.Union(blue_layer_rect2);
+
+ // Because scaling up will cause sampling outside the rects, add one extra
+ // pixel of buffer at the final content scale.
+ float inset = -1.f / contents_scale;
+ blue_layer_rect1.Inset(inset, inset, inset, inset);
+ blue_layer_rect2.Inset(inset, inset, inset, inset);
+
+ scoped_refptr<FakePicturePileImpl> pile =
+ FakePicturePileImpl::CreateFilledPile(pile_tile_size, layer_rect.size());
+
+ Region outside(layer_rect);
+ outside.Subtract(gfx::ToEnclosingRect(union_layer_rect));
+ for (Region::Iterator iter(outside); iter.has_rect(); iter.next()) {
+ pile->add_draw_rect_with_paint(iter.rect(), red_paint);
+ }
+
+ SkPaint blue_paint;
+ blue_paint.setColor(SK_ColorBLUE);
+ pile->add_draw_rect_with_paint(blue_layer_rect1, blue_paint);
+ pile->add_draw_rect_with_paint(blue_layer_rect2, blue_paint);
+ pile->RerecordPile();
+
+ gfx::Rect content_rect(
+ gfx::ScaleToEnclosingRect(layer_rect, contents_scale));
+ gfx::Rect content_union_rect(
+ gfx::ToEnclosingRect(gfx::ScaleRect(union_layer_rect, contents_scale)));
+
+ // At a scale of 4x the rectangles with a width of 2.5 will take up 10 pixels,
+ // so scale an additional 10x to make them 100x100.
+ gfx::Transform content_to_target_transform;
+ content_to_target_transform.Scale(10.0, 10.0);
+ gfx::Rect quad_content_rect(gfx::Size(20, 20));
+ scoped_ptr<SharedQuadState> blue_shared_state =
+ CreateTestSharedQuadState(content_to_target_transform, quad_content_rect);
+
+ scoped_ptr<PictureDrawQuad> blue_quad = PictureDrawQuad::Create();
+ blue_quad->SetNew(blue_shared_state.get(),
+ quad_content_rect,
+ gfx::Rect(),
+ quad_content_rect,
+ content_union_rect.size(),
+ contents_swizzled,
+ content_union_rect,
+ contents_scale,
+ use_skia_gpu_backend,
+ pile);
+ pass->quad_list.push_back(blue_quad.PassAs<DrawQuad>());
+
+ // Fill left half of viewport with green.
+ gfx::Transform half_green_content_to_target_transform;
+ gfx::Rect half_green_rect(gfx::Size(viewport.width() / 2, viewport.height()));
+ scoped_ptr<SharedQuadState> half_green_shared_state =
+ CreateTestSharedQuadState(half_green_content_to_target_transform,
+ half_green_rect);
+ scoped_ptr<SolidColorDrawQuad> half_color_quad = SolidColorDrawQuad::Create();
+ half_color_quad->SetNew(
+ half_green_shared_state.get(), half_green_rect, SK_ColorGREEN, false);
+ pass->quad_list.push_back(half_color_quad.PassAs<DrawQuad>());
+
+ RenderPassList pass_list;
+ pass_list.push_back(pass.Pass());
+
+ EXPECT_TRUE(this->RunPixelTest(
+ &pass_list,
+ base::FilePath(FILE_PATH_LITERAL("four_blue_green_checkers.png")),
+ ExactPixelComparator(true)));
+}
+#endif // !defined(OS_ANDROID)
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/output/shader.cc b/chromium/cc/output/shader.cc
new file mode 100644
index 00000000000..8ab2114555f
--- /dev/null
+++ b/chromium/cc/output/shader.cc
@@ -0,0 +1,1606 @@
+// Copyright 2011 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 "cc/output/shader.h"
+
+#include <algorithm>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "cc/output/gl_renderer.h" // For the GLC() macro.
+#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h"
+#include "third_party/khronos/GLES2/gl2.h"
+
+#define SHADER0(Src) #Src
+#define VERTEX_SHADER(Src) SetVertexTexCoordPrecision(SHADER0(Src))
+#define FRAGMENT_SHADER(Src) SetFragTexCoordPrecision(precision, SHADER0(Src))
+
+using WebKit::WebGraphicsContext3D;
+
+namespace cc {
+
+namespace {
+
+static void GetProgramUniformLocations(WebGraphicsContext3D* context,
+ unsigned program,
+ size_t count,
+ const char** uniforms,
+ int* locations,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ for (size_t i = 0; i < count; i++) {
+ if (using_bind_uniform) {
+ locations[i] = (*base_uniform_index)++;
+ context->bindUniformLocationCHROMIUM(program, locations[i], uniforms[i]);
+ } else {
+ locations[i] = context->getUniformLocation(program, uniforms[i]);
+ DCHECK_NE(locations[i], -1);
+ }
+ }
+}
+
+static std::string SetFragTexCoordPrecision(
+ TexCoordPrecision requested_precision, std::string shader_string) {
+ switch (requested_precision) {
+ case TexCoordPrecisionHigh:
+ DCHECK_NE(shader_string.find("TexCoordPrecision"), std::string::npos);
+ return
+ "#ifdef GL_FRAGMENT_PRECISION_HIGH\n"
+ " #define TexCoordPrecision highp\n"
+ "#else\n"
+ " #define TexCoordPrecision mediump\n"
+ "#endif\n" +
+ shader_string;
+ case TexCoordPrecisionMedium:
+ DCHECK_NE(shader_string.find("TexCoordPrecision"), std::string::npos);
+ return "#define TexCoordPrecision mediump\n" +
+ shader_string;
+ case TexCoordPrecisionNA:
+ DCHECK_EQ(shader_string.find("TexCoordPrecision"), std::string::npos);
+ DCHECK_EQ(shader_string.find("texture2D"), std::string::npos);
+ return shader_string;
+ }
+ return shader_string;
+}
+
+static std::string SetVertexTexCoordPrecision(const char* shader_string) {
+ // We unconditionally use highp in the vertex shader since
+ // we are unlikely to be vertex shader bound when drawing large quads.
+ // Also, some vertex shaders mutate the texture coordinate in such a
+ // way that the effective precision might be lower than expected.
+ return "#define TexCoordPrecision highp\n" +
+ std::string(shader_string);
+}
+
+TexCoordPrecision TexCoordPrecisionRequired(WebGraphicsContext3D* context,
+ int *highp_threshold_cache,
+ int highp_threshold_min,
+ int x, int y) {
+ if (*highp_threshold_cache == 0) {
+ // Initialize range and precision with minimum spec values for when
+ // GetShaderPrecisionFormat is a test stub.
+ // TODO(brianderson): Implement better stubs of GetShaderPrecisionFormat
+ // everywhere.
+ GLint range[2] = { 14, 14 };
+ GLint precision = 10;
+ GLC(context, context->getShaderPrecisionFormat(GL_FRAGMENT_SHADER,
+ GL_MEDIUM_FLOAT,
+ range, &precision));
+ *highp_threshold_cache = 1 << precision;
+ }
+
+ int highp_threshold = std::max(*highp_threshold_cache, highp_threshold_min);
+ if (x > highp_threshold || y > highp_threshold)
+ return TexCoordPrecisionHigh;
+ return TexCoordPrecisionMedium;
+}
+
+} // namespace
+
+TexCoordPrecision TexCoordPrecisionRequired(WebGraphicsContext3D* context,
+ int *highp_threshold_cache,
+ int highp_threshold_min,
+ gfx::Point max_coordinate) {
+ return TexCoordPrecisionRequired(context,
+ highp_threshold_cache, highp_threshold_min,
+ max_coordinate.x(), max_coordinate.y());
+}
+
+TexCoordPrecision TexCoordPrecisionRequired(WebGraphicsContext3D* context,
+ int *highp_threshold_cache,
+ int highp_threshold_min,
+ gfx::Size max_size) {
+ return TexCoordPrecisionRequired(context,
+ highp_threshold_cache, highp_threshold_min,
+ max_size.width(), max_size.height());
+}
+
+VertexShaderPosTex::VertexShaderPosTex()
+ : matrix_location_(-1) {}
+
+void VertexShaderPosTex::Init(WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "matrix",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+ matrix_location_ = locations[0];
+}
+
+std::string VertexShaderPosTex::GetShaderString() const {
+ return VERTEX_SHADER(
+ attribute vec4 a_position;
+ attribute TexCoordPrecision vec2 a_texCoord;
+ uniform mat4 matrix;
+ varying TexCoordPrecision vec2 v_texCoord;
+ void main() {
+ gl_Position = matrix * a_position;
+ v_texCoord = a_texCoord;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+VertexShaderPosTexYUVStretch::VertexShaderPosTexYUVStretch()
+ : matrix_location_(-1),
+ tex_scale_location_(-1) {}
+
+void VertexShaderPosTexYUVStretch::Init(WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "matrix",
+ "texScale",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+ matrix_location_ = locations[0];
+ tex_scale_location_ = locations[1];
+}
+
+std::string VertexShaderPosTexYUVStretch::GetShaderString() const {
+ return VERTEX_SHADER(
+ precision mediump float;
+ attribute vec4 a_position;
+ attribute TexCoordPrecision vec2 a_texCoord;
+ uniform mat4 matrix;
+ varying TexCoordPrecision vec2 v_texCoord;
+ uniform TexCoordPrecision vec2 texScale;
+ void main() {
+ gl_Position = matrix * a_position;
+ v_texCoord = a_texCoord * texScale;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+VertexShaderPos::VertexShaderPos()
+ : matrix_location_(-1) {}
+
+void VertexShaderPos::Init(WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "matrix",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+ matrix_location_ = locations[0];
+}
+
+std::string VertexShaderPos::GetShaderString() const {
+ return VERTEX_SHADER(
+ attribute vec4 a_position;
+ uniform mat4 matrix;
+ void main() {
+ gl_Position = matrix * a_position;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+VertexShaderPosTexTransform::VertexShaderPosTexTransform()
+ : matrix_location_(-1),
+ tex_transform_location_(-1),
+ vertex_opacity_location_(-1) {}
+
+void VertexShaderPosTexTransform::Init(WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "matrix",
+ "texTransform",
+ "opacity",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+ matrix_location_ = locations[0];
+ tex_transform_location_ = locations[1];
+ vertex_opacity_location_ = locations[2];
+}
+
+std::string VertexShaderPosTexTransform::GetShaderString() const {
+ return VERTEX_SHADER(
+ attribute vec4 a_position;
+ attribute TexCoordPrecision vec2 a_texCoord;
+ attribute float a_index;
+ uniform mat4 matrix[8];
+ uniform TexCoordPrecision vec4 texTransform[8];
+ uniform float opacity[32];
+ varying TexCoordPrecision vec2 v_texCoord;
+ varying float v_alpha;
+ void main() {
+ int quad_index = int(a_index * 0.25); // NOLINT
+ gl_Position = matrix[quad_index] * a_position;
+ TexCoordPrecision vec4 texTrans = texTransform[quad_index];
+ v_texCoord = a_texCoord * texTrans.zw + texTrans.xy;
+ v_alpha = opacity[int(a_index)]; // NOLINT
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+std::string VertexShaderPosTexIdentity::GetShaderString() const {
+ return VERTEX_SHADER(
+ attribute vec4 a_position;
+ varying TexCoordPrecision vec2 v_texCoord;
+ void main() {
+ gl_Position = a_position;
+ v_texCoord = (a_position.xy + vec2(1.0)) * 0.5;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+VertexShaderQuad::VertexShaderQuad()
+ : matrix_location_(-1),
+ quad_location_(-1) {}
+
+void VertexShaderQuad::Init(WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "matrix",
+ "quad",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+ matrix_location_ = locations[0];
+ quad_location_ = locations[1];
+}
+
+std::string VertexShaderQuad::GetShaderString() const {
+#if defined(OS_ANDROID)
+// TODO(epenner): Find the cause of this 'quad' uniform
+// being missing if we don't add dummy variables.
+// http://crbug.com/240602
+ return VERTEX_SHADER(
+ attribute TexCoordPrecision vec4 a_position;
+ attribute float a_index;
+ uniform mat4 matrix;
+ uniform TexCoordPrecision vec2 quad[4];
+ uniform TexCoordPrecision vec2 dummy_uniform;
+ varying TexCoordPrecision vec2 dummy_varying;
+ void main() {
+ vec2 pos = quad[int(a_index)]; // NOLINT
+ gl_Position = matrix * vec4(pos, a_position.z, a_position.w);
+ dummy_varying = dummy_uniform;
+ }
+ ); // NOLINT(whitespace/parens)
+#else
+ return VERTEX_SHADER(
+ attribute TexCoordPrecision vec4 a_position;
+ attribute float a_index;
+ uniform mat4 matrix;
+ uniform TexCoordPrecision vec2 quad[4];
+ void main() {
+ vec2 pos = quad[int(a_index)]; // NOLINT
+ gl_Position = matrix * vec4(pos, a_position.z, a_position.w);
+ }
+ ); // NOLINT(whitespace/parens)
+#endif
+}
+
+VertexShaderQuadAA::VertexShaderQuadAA()
+ : matrix_location_(-1),
+ viewport_location_(-1),
+ quad_location_(-1),
+ edge_location_(-1) {}
+
+void VertexShaderQuadAA::Init(WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "matrix",
+ "viewport",
+ "quad",
+ "edge",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+ matrix_location_ = locations[0];
+ viewport_location_ = locations[1];
+ quad_location_ = locations[2];
+ edge_location_ = locations[3];
+}
+
+std::string VertexShaderQuadAA::GetShaderString() const {
+ return VERTEX_SHADER(
+ attribute TexCoordPrecision vec4 a_position;
+ attribute float a_index;
+ uniform mat4 matrix;
+ uniform vec4 viewport;
+ uniform TexCoordPrecision vec2 quad[4];
+ uniform TexCoordPrecision vec3 edge[8];
+ varying TexCoordPrecision vec4 edge_dist[2]; // 8 edge distances.
+
+ void main() {
+ vec2 pos = quad[int(a_index)]; // NOLINT
+ gl_Position = matrix * vec4(pos, a_position.z, a_position.w);
+ vec2 ndc_pos = 0.5 * (1.0 + gl_Position.xy / gl_Position.w);
+ vec3 screen_pos = vec3(viewport.xy + viewport.zw * ndc_pos, 1.0);
+ edge_dist[0] = vec4(dot(edge[0], screen_pos),
+ dot(edge[1], screen_pos),
+ dot(edge[2], screen_pos),
+ dot(edge[3], screen_pos)) * gl_Position.w;
+ edge_dist[1] = vec4(dot(edge[4], screen_pos),
+ dot(edge[5], screen_pos),
+ dot(edge[6], screen_pos),
+ dot(edge[7], screen_pos)) * gl_Position.w;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+VertexShaderQuadTexTransformAA::VertexShaderQuadTexTransformAA()
+ : matrix_location_(-1),
+ viewport_location_(-1),
+ quad_location_(-1),
+ edge_location_(-1),
+ tex_transform_location_(-1) {}
+
+void VertexShaderQuadTexTransformAA::Init(WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "matrix",
+ "viewport",
+ "quad",
+ "edge",
+ "texTrans",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+ matrix_location_ = locations[0];
+ viewport_location_ = locations[1];
+ quad_location_ = locations[2];
+ edge_location_ = locations[3];
+ tex_transform_location_ = locations[4];
+}
+
+std::string VertexShaderQuadTexTransformAA::GetShaderString() const {
+ return VERTEX_SHADER(
+ attribute TexCoordPrecision vec4 a_position;
+ attribute float a_index;
+ uniform mat4 matrix;
+ uniform vec4 viewport;
+ uniform TexCoordPrecision vec2 quad[4];
+ uniform TexCoordPrecision vec3 edge[8];
+ uniform TexCoordPrecision vec4 texTrans;
+ varying TexCoordPrecision vec2 v_texCoord;
+ varying TexCoordPrecision vec4 edge_dist[2]; // 8 edge distances.
+
+ void main() {
+ vec2 pos = quad[int(a_index)]; // NOLINT
+ gl_Position = matrix * vec4(pos, a_position.z, a_position.w);
+ vec2 ndc_pos = 0.5 * (1.0 + gl_Position.xy / gl_Position.w);
+ vec3 screen_pos = vec3(viewport.xy + viewport.zw * ndc_pos, 1.0);
+ edge_dist[0] = vec4(dot(edge[0], screen_pos),
+ dot(edge[1], screen_pos),
+ dot(edge[2], screen_pos),
+ dot(edge[3], screen_pos)) * gl_Position.w;
+ edge_dist[1] = vec4(dot(edge[4], screen_pos),
+ dot(edge[5], screen_pos),
+ dot(edge[6], screen_pos),
+ dot(edge[7], screen_pos)) * gl_Position.w;
+ v_texCoord = (pos.xy + vec2(0.5)) * texTrans.zw + texTrans.xy;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+VertexShaderTile::VertexShaderTile()
+ : matrix_location_(-1),
+ quad_location_(-1),
+ vertex_tex_transform_location_(-1) {}
+
+void VertexShaderTile::Init(WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "matrix",
+ "quad",
+ "vertexTexTransform",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+ matrix_location_ = locations[0];
+ quad_location_ = locations[1];
+ vertex_tex_transform_location_ = locations[2];
+}
+
+std::string VertexShaderTile::GetShaderString() const {
+ return VERTEX_SHADER(
+ attribute TexCoordPrecision vec4 a_position;
+ attribute float a_index;
+ uniform mat4 matrix;
+ uniform TexCoordPrecision vec2 quad[4];
+ uniform TexCoordPrecision vec4 vertexTexTransform;
+ varying TexCoordPrecision vec2 v_texCoord;
+ void main() {
+ vec2 pos = quad[int(a_index)]; // NOLINT
+ gl_Position = matrix * vec4(pos, a_position.z, a_position.w);
+ v_texCoord = pos.xy * vertexTexTransform.zw + vertexTexTransform.xy;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+VertexShaderTileAA::VertexShaderTileAA()
+ : matrix_location_(-1),
+ viewport_location_(-1),
+ quad_location_(-1),
+ edge_location_(-1),
+ vertex_tex_transform_location_(-1) {}
+
+void VertexShaderTileAA::Init(WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "matrix",
+ "viewport",
+ "quad",
+ "edge",
+ "vertexTexTransform",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+ matrix_location_ = locations[0];
+ viewport_location_ = locations[1];
+ quad_location_ = locations[2];
+ edge_location_ = locations[3];
+ vertex_tex_transform_location_ = locations[4];
+}
+
+std::string VertexShaderTileAA::GetShaderString() const {
+ return VERTEX_SHADER(
+ attribute TexCoordPrecision vec4 a_position;
+ attribute float a_index;
+ uniform mat4 matrix;
+ uniform vec4 viewport;
+ uniform TexCoordPrecision vec2 quad[4];
+ uniform TexCoordPrecision vec3 edge[8];
+ uniform TexCoordPrecision vec4 vertexTexTransform;
+ varying TexCoordPrecision vec2 v_texCoord;
+ varying TexCoordPrecision vec4 edge_dist[2]; // 8 edge distances.
+
+ void main() {
+ vec2 pos = quad[int(a_index)]; // NOLINT
+ gl_Position = matrix * vec4(pos, a_position.z, a_position.w);
+ vec2 ndc_pos = 0.5 * (1.0 + gl_Position.xy / gl_Position.w);
+ vec3 screen_pos = vec3(viewport.xy + viewport.zw * ndc_pos, 1.0);
+ edge_dist[0] = vec4(dot(edge[0], screen_pos),
+ dot(edge[1], screen_pos),
+ dot(edge[2], screen_pos),
+ dot(edge[3], screen_pos)) * gl_Position.w;
+ edge_dist[1] = vec4(dot(edge[4], screen_pos),
+ dot(edge[5], screen_pos),
+ dot(edge[6], screen_pos),
+ dot(edge[7], screen_pos)) * gl_Position.w;
+ v_texCoord = pos.xy * vertexTexTransform.zw + vertexTexTransform.xy;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+VertexShaderVideoTransform::VertexShaderVideoTransform()
+ : matrix_location_(-1),
+ tex_matrix_location_(-1) {}
+
+void VertexShaderVideoTransform::Init(WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "matrix",
+ "texMatrix",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+ matrix_location_ = locations[0];
+ tex_matrix_location_ = locations[1];
+}
+
+std::string VertexShaderVideoTransform::GetShaderString() const {
+ return VERTEX_SHADER(
+ attribute vec4 a_position;
+ attribute TexCoordPrecision vec2 a_texCoord;
+ uniform mat4 matrix;
+ uniform TexCoordPrecision mat4 texMatrix;
+ varying TexCoordPrecision vec2 v_texCoord;
+ void main() {
+ gl_Position = matrix * a_position;
+ v_texCoord =
+ vec2(texMatrix * vec4(a_texCoord.x, 1.0 - a_texCoord.y, 0.0, 1.0));
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+FragmentTexAlphaBinding::FragmentTexAlphaBinding()
+ : sampler_location_(-1),
+ alpha_location_(-1) {}
+
+void FragmentTexAlphaBinding::Init(WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "s_texture",
+ "alpha",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+ sampler_location_ = locations[0];
+ alpha_location_ = locations[1];
+}
+
+FragmentTexColorMatrixAlphaBinding::FragmentTexColorMatrixAlphaBinding()
+ : sampler_location_(-1),
+ alpha_location_(-1),
+ color_matrix_location_(-1),
+ color_offset_location_(-1) {}
+
+void FragmentTexColorMatrixAlphaBinding::Init(WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "s_texture",
+ "alpha",
+ "colorMatrix",
+ "colorOffset",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+ sampler_location_ = locations[0];
+ alpha_location_ = locations[1];
+ color_matrix_location_ = locations[2];
+ color_offset_location_ = locations[3];
+}
+
+FragmentTexOpaqueBinding::FragmentTexOpaqueBinding()
+ : sampler_location_(-1) {}
+
+void FragmentTexOpaqueBinding::Init(WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "s_texture",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+ sampler_location_ = locations[0];
+}
+
+FragmentShaderOESImageExternal::FragmentShaderOESImageExternal()
+ : sampler_location_(-1) {}
+
+void FragmentShaderOESImageExternal::Init(WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "s_texture",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+ sampler_location_ = locations[0];
+}
+
+std::string FragmentShaderOESImageExternal::GetShaderString(
+ TexCoordPrecision precision) const {
+ // Cannot use the SHADER() macro because of the '#' char
+ return "#extension GL_OES_EGL_image_external : require\n" +
+ FRAGMENT_SHADER(
+ precision mediump float;
+ varying TexCoordPrecision vec2 v_texCoord;
+ uniform samplerExternalOES s_texture;
+ void main() {
+ vec4 texColor = texture2D(s_texture, v_texCoord);
+ gl_FragColor = texColor;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+std::string FragmentShaderRGBATexAlpha::GetShaderString(
+ TexCoordPrecision precision) const {
+ return FRAGMENT_SHADER(
+ precision mediump float;
+ varying TexCoordPrecision vec2 v_texCoord;
+ uniform sampler2D s_texture;
+ uniform float alpha;
+ void main() {
+ vec4 texColor = texture2D(s_texture, v_texCoord);
+ gl_FragColor = texColor * alpha;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+std::string FragmentShaderRGBATexColorMatrixAlpha::GetShaderString(
+ TexCoordPrecision precision) const {
+ return FRAGMENT_SHADER(
+ precision mediump float;
+ varying TexCoordPrecision vec2 v_texCoord;
+ uniform sampler2D s_texture;
+ uniform float alpha;
+ uniform mat4 colorMatrix;
+ uniform vec4 colorOffset;
+ void main() {
+ vec4 texColor = texture2D(s_texture, v_texCoord);
+ float nonZeroAlpha = max(texColor.a, 0.00001);
+ texColor = vec4(texColor.rgb / nonZeroAlpha, nonZeroAlpha);
+ texColor = colorMatrix * texColor + colorOffset;
+ texColor.rgb *= texColor.a;
+ texColor = clamp(texColor, 0.0, 1.0);
+ gl_FragColor = texColor * alpha;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+std::string FragmentShaderRGBATexVaryingAlpha::GetShaderString(
+ TexCoordPrecision precision) const {
+ return FRAGMENT_SHADER(
+ precision mediump float;
+ varying TexCoordPrecision vec2 v_texCoord;
+ varying float v_alpha;
+ uniform sampler2D s_texture;
+ void main() {
+ vec4 texColor = texture2D(s_texture, v_texCoord);
+ gl_FragColor = texColor * v_alpha;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+std::string FragmentShaderRGBATexPremultiplyAlpha::GetShaderString(
+ TexCoordPrecision precision) const {
+ return FRAGMENT_SHADER(
+ precision mediump float;
+ varying TexCoordPrecision vec2 v_texCoord;
+ varying float v_alpha;
+ uniform sampler2D s_texture;
+ void main() {
+ vec4 texColor = texture2D(s_texture, v_texCoord);
+ texColor.rgb *= texColor.a;
+ gl_FragColor = texColor * v_alpha;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+FragmentTexBackgroundBinding::FragmentTexBackgroundBinding()
+ : background_color_location_(-1),
+ sampler_location_(-1) {
+}
+
+void FragmentTexBackgroundBinding::Init(WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "s_texture",
+ "background_color",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+
+ sampler_location_ = locations[0];
+ DCHECK_NE(sampler_location_, -1);
+
+ background_color_location_ = locations[1];
+ DCHECK_NE(background_color_location_, -1);
+}
+
+std::string FragmentShaderTexBackgroundVaryingAlpha::GetShaderString(
+ TexCoordPrecision precision) const {
+ return FRAGMENT_SHADER(
+ precision mediump float;
+ varying TexCoordPrecision vec2 v_texCoord;
+ varying float v_alpha;
+ uniform vec4 background_color;
+ uniform sampler2D s_texture;
+ void main() {
+ vec4 texColor = texture2D(s_texture, v_texCoord);
+ texColor += background_color * (1.0 - texColor.a);
+ gl_FragColor = texColor * v_alpha;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+std::string FragmentShaderTexBackgroundPremultiplyAlpha::GetShaderString(
+ TexCoordPrecision precision) const {
+ return FRAGMENT_SHADER(
+ precision mediump float;
+ varying TexCoordPrecision vec2 v_texCoord;
+ varying float v_alpha;
+ uniform vec4 background_color;
+ uniform sampler2D s_texture;
+ void main() {
+ vec4 texColor = texture2D(s_texture, v_texCoord);
+ texColor.rgb *= texColor.a;
+ texColor += background_color * (1.0 - texColor.a);
+ gl_FragColor = texColor * v_alpha;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+std::string FragmentShaderRGBATexRectVaryingAlpha::GetShaderString(
+ TexCoordPrecision precision) const {
+ return "#extension GL_ARB_texture_rectangle : require\n" +
+ FRAGMENT_SHADER(
+ precision mediump float;
+ varying TexCoordPrecision vec2 v_texCoord;
+ varying float v_alpha;
+ uniform sampler2DRect s_texture;
+ void main() {
+ vec4 texColor = texture2DRect(s_texture, v_texCoord);
+ gl_FragColor = texColor * v_alpha;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+std::string FragmentShaderRGBATexOpaque::GetShaderString(
+ TexCoordPrecision precision) const {
+ return FRAGMENT_SHADER(
+ precision mediump float;
+ varying TexCoordPrecision vec2 v_texCoord;
+ uniform sampler2D s_texture;
+ void main() {
+ vec4 texColor = texture2D(s_texture, v_texCoord);
+ gl_FragColor = vec4(texColor.rgb, 1.0);
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+std::string FragmentShaderRGBATex::GetShaderString(
+ TexCoordPrecision precision) const {
+ return FRAGMENT_SHADER(
+ precision mediump float;
+ varying TexCoordPrecision vec2 v_texCoord;
+ uniform sampler2D s_texture;
+ void main() {
+ gl_FragColor = texture2D(s_texture, v_texCoord);
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+std::string FragmentShaderRGBATexSwizzleAlpha::GetShaderString(
+ TexCoordPrecision precision) const {
+ return FRAGMENT_SHADER(
+ precision mediump float;
+ varying TexCoordPrecision vec2 v_texCoord;
+ uniform sampler2D s_texture;
+ uniform float alpha;
+ void main() {
+ vec4 texColor = texture2D(s_texture, v_texCoord);
+ gl_FragColor =
+ vec4(texColor.z, texColor.y, texColor.x, texColor.w) * alpha;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+std::string FragmentShaderRGBATexSwizzleOpaque::GetShaderString(
+ TexCoordPrecision precision) const {
+ return FRAGMENT_SHADER(
+ precision mediump float;
+ varying TexCoordPrecision vec2 v_texCoord;
+ uniform sampler2D s_texture;
+ void main() {
+ vec4 texColor = texture2D(s_texture, v_texCoord);
+ gl_FragColor = vec4(texColor.z, texColor.y, texColor.x, 1.0);
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+FragmentShaderRGBATexAlphaAA::FragmentShaderRGBATexAlphaAA()
+ : sampler_location_(-1),
+ alpha_location_(-1) {}
+
+void FragmentShaderRGBATexAlphaAA::Init(WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "s_texture",
+ "alpha",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+ sampler_location_ = locations[0];
+ alpha_location_ = locations[1];
+}
+
+std::string FragmentShaderRGBATexAlphaAA::GetShaderString(
+ TexCoordPrecision precision) const {
+ return FRAGMENT_SHADER(
+ precision mediump float;
+ uniform sampler2D s_texture;
+ uniform float alpha;
+ varying TexCoordPrecision vec2 v_texCoord;
+ varying TexCoordPrecision vec4 edge_dist[2]; // 8 edge distances.
+
+ void main() {
+ vec4 texColor = texture2D(s_texture, v_texCoord);
+ vec4 d4 = min(edge_dist[0], edge_dist[1]);
+ vec2 d2 = min(d4.xz, d4.yw);
+ float aa = clamp(gl_FragCoord.w * min(d2.x, d2.y), 0.0, 1.0);
+ gl_FragColor = texColor * alpha * aa;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+FragmentTexClampAlphaAABinding::FragmentTexClampAlphaAABinding()
+ : sampler_location_(-1),
+ alpha_location_(-1),
+ fragment_tex_transform_location_(-1) {}
+
+void FragmentTexClampAlphaAABinding::Init(WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "s_texture",
+ "alpha",
+ "fragmentTexTransform",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+ sampler_location_ = locations[0];
+ alpha_location_ = locations[1];
+ fragment_tex_transform_location_ = locations[2];
+}
+
+std::string FragmentShaderRGBATexClampAlphaAA::GetShaderString(
+ TexCoordPrecision precision) const {
+ return FRAGMENT_SHADER(
+ precision mediump float;
+ uniform sampler2D s_texture;
+ uniform float alpha;
+ uniform TexCoordPrecision vec4 fragmentTexTransform;
+ varying TexCoordPrecision vec2 v_texCoord;
+ varying TexCoordPrecision vec4 edge_dist[2]; // 8 edge distances.
+
+ void main() {
+ TexCoordPrecision vec2 texCoord =
+ clamp(v_texCoord, 0.0, 1.0) * fragmentTexTransform.zw +
+ fragmentTexTransform.xy;
+ vec4 texColor = texture2D(s_texture, texCoord);
+ vec4 d4 = min(edge_dist[0], edge_dist[1]);
+ vec2 d2 = min(d4.xz, d4.yw);
+ float aa = clamp(gl_FragCoord.w * min(d2.x, d2.y), 0.0, 1.0);
+ gl_FragColor = texColor * alpha * aa;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+std::string FragmentShaderRGBATexClampSwizzleAlphaAA::GetShaderString(
+ TexCoordPrecision precision) const {
+ return FRAGMENT_SHADER(
+ precision mediump float;
+ uniform sampler2D s_texture;
+ uniform float alpha;
+ uniform TexCoordPrecision vec4 fragmentTexTransform;
+ varying TexCoordPrecision vec2 v_texCoord;
+ varying TexCoordPrecision vec4 edge_dist[2]; // 8 edge distances.
+
+ void main() {
+ TexCoordPrecision vec2 texCoord =
+ clamp(v_texCoord, 0.0, 1.0) * fragmentTexTransform.zw +
+ fragmentTexTransform.xy;
+ vec4 texColor = texture2D(s_texture, texCoord);
+ vec4 d4 = min(edge_dist[0], edge_dist[1]);
+ vec2 d2 = min(d4.xz, d4.yw);
+ float aa = clamp(gl_FragCoord.w * min(d2.x, d2.y), 0.0, 1.0);
+ gl_FragColor = vec4(texColor.z, texColor.y, texColor.x, texColor.w) *
+ alpha * aa;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+FragmentShaderRGBATexAlphaMask::FragmentShaderRGBATexAlphaMask()
+ : sampler_location_(-1),
+ mask_sampler_location_(-1),
+ alpha_location_(-1),
+ mask_tex_coord_scale_location_(-1) {}
+
+void FragmentShaderRGBATexAlphaMask::Init(WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "s_texture",
+ "s_mask",
+ "alpha",
+ "maskTexCoordScale",
+ "maskTexCoordOffset",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+ sampler_location_ = locations[0];
+ mask_sampler_location_ = locations[1];
+ alpha_location_ = locations[2];
+ mask_tex_coord_scale_location_ = locations[3];
+ mask_tex_coord_offset_location_ = locations[4];
+}
+
+std::string FragmentShaderRGBATexAlphaMask::GetShaderString(
+ TexCoordPrecision precision) const {
+ return FRAGMENT_SHADER(
+ precision mediump float;
+ varying TexCoordPrecision vec2 v_texCoord;
+ uniform sampler2D s_texture;
+ uniform sampler2D s_mask;
+ uniform TexCoordPrecision vec2 maskTexCoordScale;
+ uniform TexCoordPrecision vec2 maskTexCoordOffset;
+ uniform float alpha;
+ void main() {
+ vec4 texColor = texture2D(s_texture, v_texCoord);
+ TexCoordPrecision vec2 maskTexCoord =
+ vec2(maskTexCoordOffset.x + v_texCoord.x * maskTexCoordScale.x,
+ maskTexCoordOffset.y + v_texCoord.y * maskTexCoordScale.y);
+ vec4 maskColor = texture2D(s_mask, maskTexCoord);
+ gl_FragColor = texColor * alpha * maskColor.w;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+FragmentShaderRGBATexAlphaMaskAA::FragmentShaderRGBATexAlphaMaskAA()
+ : sampler_location_(-1),
+ mask_sampler_location_(-1),
+ alpha_location_(-1),
+ mask_tex_coord_scale_location_(-1),
+ mask_tex_coord_offset_location_(-1) {}
+
+void FragmentShaderRGBATexAlphaMaskAA::Init(WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "s_texture",
+ "s_mask",
+ "alpha",
+ "maskTexCoordScale",
+ "maskTexCoordOffset",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+ sampler_location_ = locations[0];
+ mask_sampler_location_ = locations[1];
+ alpha_location_ = locations[2];
+ mask_tex_coord_scale_location_ = locations[3];
+ mask_tex_coord_offset_location_ = locations[4];
+}
+
+std::string FragmentShaderRGBATexAlphaMaskAA::GetShaderString(
+ TexCoordPrecision precision) const {
+ return FRAGMENT_SHADER(
+ precision mediump float;
+ uniform sampler2D s_texture;
+ uniform sampler2D s_mask;
+ uniform TexCoordPrecision vec2 maskTexCoordScale;
+ uniform TexCoordPrecision vec2 maskTexCoordOffset;
+ uniform float alpha;
+ varying TexCoordPrecision vec2 v_texCoord;
+ varying TexCoordPrecision vec4 edge_dist[2]; // 8 edge distances.
+
+ void main() {
+ vec4 texColor = texture2D(s_texture, v_texCoord);
+ TexCoordPrecision vec2 maskTexCoord =
+ vec2(maskTexCoordOffset.x + v_texCoord.x * maskTexCoordScale.x,
+ maskTexCoordOffset.y + v_texCoord.y * maskTexCoordScale.y);
+ vec4 maskColor = texture2D(s_mask, maskTexCoord);
+ vec4 d4 = min(edge_dist[0], edge_dist[1]);
+ vec2 d2 = min(d4.xz, d4.yw);
+ float aa = clamp(gl_FragCoord.w * min(d2.x, d2.y), 0.0, 1.0);
+ gl_FragColor = texColor * alpha * maskColor.w * aa;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+FragmentShaderRGBATexAlphaMaskColorMatrixAA::
+ FragmentShaderRGBATexAlphaMaskColorMatrixAA()
+ : sampler_location_(-1),
+ mask_sampler_location_(-1),
+ alpha_location_(-1),
+ mask_tex_coord_scale_location_(-1),
+ color_matrix_location_(-1),
+ color_offset_location_(-1) {}
+
+void FragmentShaderRGBATexAlphaMaskColorMatrixAA::Init(
+ WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "s_texture",
+ "s_mask",
+ "alpha",
+ "maskTexCoordScale",
+ "maskTexCoordOffset",
+ "colorMatrix",
+ "colorOffset",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+ sampler_location_ = locations[0];
+ mask_sampler_location_ = locations[1];
+ alpha_location_ = locations[2];
+ mask_tex_coord_scale_location_ = locations[3];
+ mask_tex_coord_offset_location_ = locations[4];
+ color_matrix_location_ = locations[5];
+ color_offset_location_ = locations[6];
+}
+
+std::string FragmentShaderRGBATexAlphaMaskColorMatrixAA::GetShaderString(
+ TexCoordPrecision precision) const {
+ return FRAGMENT_SHADER(
+ precision mediump float;
+ uniform sampler2D s_texture;
+ uniform sampler2D s_mask;
+ uniform vec2 maskTexCoordScale;
+ uniform vec2 maskTexCoordOffset;
+ uniform mat4 colorMatrix;
+ uniform vec4 colorOffset;
+ uniform float alpha;
+ varying TexCoordPrecision vec2 v_texCoord;
+ varying TexCoordPrecision vec4 edge_dist[2]; // 8 edge distances.
+
+ void main() {
+ vec4 texColor = texture2D(s_texture, v_texCoord);
+ float nonZeroAlpha = max(texColor.a, 0.00001);
+ texColor = vec4(texColor.rgb / nonZeroAlpha, nonZeroAlpha);
+ texColor = colorMatrix * texColor + colorOffset;
+ texColor.rgb *= texColor.a;
+ texColor = clamp(texColor, 0.0, 1.0);
+ TexCoordPrecision vec2 maskTexCoord =
+ vec2(maskTexCoordOffset.x + v_texCoord.x * maskTexCoordScale.x,
+ maskTexCoordOffset.y + v_texCoord.y * maskTexCoordScale.y);
+ vec4 maskColor = texture2D(s_mask, maskTexCoord);
+ vec4 d4 = min(edge_dist[0], edge_dist[1]);
+ vec2 d2 = min(d4.xz, d4.yw);
+ float aa = clamp(gl_FragCoord.w * min(d2.x, d2.y), 0.0, 1.0);
+ gl_FragColor = texColor * alpha * maskColor.w * aa;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+FragmentShaderRGBATexAlphaColorMatrixAA::
+ FragmentShaderRGBATexAlphaColorMatrixAA()
+ : sampler_location_(-1),
+ alpha_location_(-1),
+ color_matrix_location_(-1),
+ color_offset_location_(-1) {}
+
+void FragmentShaderRGBATexAlphaColorMatrixAA::Init(
+ WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "s_texture",
+ "alpha",
+ "colorMatrix",
+ "colorOffset",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+ sampler_location_ = locations[0];
+ alpha_location_ = locations[1];
+ color_matrix_location_ = locations[2];
+ color_offset_location_ = locations[3];
+}
+
+std::string FragmentShaderRGBATexAlphaColorMatrixAA::GetShaderString(
+ TexCoordPrecision precision) const {
+ return FRAGMENT_SHADER(
+ precision mediump float;
+ uniform sampler2D s_texture;
+ uniform float alpha;
+ uniform mat4 colorMatrix;
+ uniform vec4 colorOffset;
+ varying TexCoordPrecision vec2 v_texCoord;
+ varying TexCoordPrecision vec4 edge_dist[2]; // 8 edge distances.
+
+ void main() {
+ vec4 texColor = texture2D(s_texture, v_texCoord);
+ float nonZeroAlpha = max(texColor.a, 0.00001);
+ texColor = vec4(texColor.rgb / nonZeroAlpha, nonZeroAlpha);
+ texColor = colorMatrix * texColor + colorOffset;
+ texColor.rgb *= texColor.a;
+ texColor = clamp(texColor, 0.0, 1.0);
+ vec4 d4 = min(edge_dist[0], edge_dist[1]);
+ vec2 d2 = min(d4.xz, d4.yw);
+ float aa = clamp(gl_FragCoord.w * min(d2.x, d2.y), 0.0, 1.0);
+ gl_FragColor = texColor * alpha * aa;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+FragmentShaderRGBATexAlphaMaskColorMatrix::
+ FragmentShaderRGBATexAlphaMaskColorMatrix()
+ : sampler_location_(-1),
+ mask_sampler_location_(-1),
+ alpha_location_(-1),
+ mask_tex_coord_scale_location_(-1) {}
+
+void FragmentShaderRGBATexAlphaMaskColorMatrix::Init(
+ WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "s_texture",
+ "s_mask",
+ "alpha",
+ "maskTexCoordScale",
+ "maskTexCoordOffset",
+ "colorMatrix",
+ "colorOffset",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+ sampler_location_ = locations[0];
+ mask_sampler_location_ = locations[1];
+ alpha_location_ = locations[2];
+ mask_tex_coord_scale_location_ = locations[3];
+ mask_tex_coord_offset_location_ = locations[4];
+ color_matrix_location_ = locations[5];
+ color_offset_location_ = locations[6];
+}
+
+std::string FragmentShaderRGBATexAlphaMaskColorMatrix::GetShaderString(
+ TexCoordPrecision precision) const {
+ return FRAGMENT_SHADER(
+ precision mediump float;
+ varying TexCoordPrecision vec2 v_texCoord;
+ uniform sampler2D s_texture;
+ uniform sampler2D s_mask;
+ uniform vec2 maskTexCoordScale;
+ uniform vec2 maskTexCoordOffset;
+ uniform mat4 colorMatrix;
+ uniform vec4 colorOffset;
+ uniform float alpha;
+ void main() {
+ vec4 texColor = texture2D(s_texture, v_texCoord);
+ float nonZeroAlpha = max(texColor.a, 0.00001);
+ texColor = vec4(texColor.rgb / nonZeroAlpha, nonZeroAlpha);
+ texColor = colorMatrix * texColor + colorOffset;
+ texColor.rgb *= texColor.a;
+ texColor = clamp(texColor, 0.0, 1.0);
+ TexCoordPrecision vec2 maskTexCoord =
+ vec2(maskTexCoordOffset.x + v_texCoord.x * maskTexCoordScale.x,
+ maskTexCoordOffset.y + v_texCoord.y * maskTexCoordScale.y);
+ vec4 maskColor = texture2D(s_mask, maskTexCoord);
+ gl_FragColor = texColor * alpha * maskColor.w;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+FragmentShaderYUVVideo::FragmentShaderYUVVideo()
+ : y_texture_location_(-1),
+ u_texture_location_(-1),
+ v_texture_location_(-1),
+ alpha_location_(-1),
+ yuv_matrix_location_(-1),
+ yuv_adj_location_(-1) {}
+
+void FragmentShaderYUVVideo::Init(WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "y_texture",
+ "u_texture",
+ "v_texture",
+ "alpha",
+ "yuv_matrix",
+ "yuv_adj",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+ y_texture_location_ = locations[0];
+ u_texture_location_ = locations[1];
+ v_texture_location_ = locations[2];
+ alpha_location_ = locations[3];
+ yuv_matrix_location_ = locations[4];
+ yuv_adj_location_ = locations[5];
+}
+
+std::string FragmentShaderYUVVideo::GetShaderString(
+ TexCoordPrecision precision) const {
+ return FRAGMENT_SHADER(
+ precision mediump float;
+ precision mediump int;
+ varying TexCoordPrecision vec2 v_texCoord;
+ uniform sampler2D y_texture;
+ uniform sampler2D u_texture;
+ uniform sampler2D v_texture;
+ uniform float alpha;
+ uniform vec3 yuv_adj;
+ uniform mat3 yuv_matrix;
+ void main() {
+ float y_raw = texture2D(y_texture, v_texCoord).x;
+ float u_unsigned = texture2D(u_texture, v_texCoord).x;
+ float v_unsigned = texture2D(v_texture, v_texCoord).x;
+ vec3 yuv = vec3(y_raw, u_unsigned, v_unsigned) + yuv_adj;
+ vec3 rgb = yuv_matrix * yuv;
+ gl_FragColor = vec4(rgb, 1.0) * alpha;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+FragmentShaderYUVAVideo::FragmentShaderYUVAVideo()
+ : y_texture_location_(-1),
+ u_texture_location_(-1),
+ v_texture_location_(-1),
+ a_texture_location_(-1),
+ alpha_location_(-1),
+ yuv_matrix_location_(-1),
+ yuv_adj_location_(-1) {
+}
+
+void FragmentShaderYUVAVideo::Init(WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "y_texture",
+ "u_texture",
+ "v_texture",
+ "a_texture",
+ "alpha",
+ "cc_matrix",
+ "yuv_adj",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+ y_texture_location_ = locations[0];
+ u_texture_location_ = locations[1];
+ v_texture_location_ = locations[2];
+ a_texture_location_ = locations[3];
+ alpha_location_ = locations[4];
+ yuv_matrix_location_ = locations[5];
+ yuv_adj_location_ = locations[6];
+}
+
+std::string FragmentShaderYUVAVideo::GetShaderString(
+ TexCoordPrecision precision) const {
+ return FRAGMENT_SHADER(
+ precision mediump float;
+ precision mediump int;
+ varying TexCoordPrecision vec2 v_texCoord;
+ uniform sampler2D y_texture;
+ uniform sampler2D u_texture;
+ uniform sampler2D v_texture;
+ uniform sampler2D a_texture;
+ uniform float alpha;
+ uniform vec3 yuv_adj;
+ uniform mat3 yuv_matrix;
+ void main() {
+ float y_raw = texture2D(y_texture, v_texCoord).x;
+ float u_unsigned = texture2D(u_texture, v_texCoord).x;
+ float v_unsigned = texture2D(v_texture, v_texCoord).x;
+ float a_raw = texture2D(a_texture, v_texCoord).x;
+ vec3 yuv = vec3(y_raw, u_unsigned, v_unsigned) + yuv_adj;
+ vec3 rgb = yuv_matrix * yuv;
+ gl_FragColor = vec4(rgb, 1.0) * (alpha * a_raw);
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+FragmentShaderColor::FragmentShaderColor()
+ : color_location_(-1) {}
+
+void FragmentShaderColor::Init(WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "color",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+ color_location_ = locations[0];
+}
+
+std::string FragmentShaderColor::GetShaderString(
+ TexCoordPrecision precision) const {
+ return FRAGMENT_SHADER(
+ precision mediump float;
+ uniform vec4 color;
+ void main() {
+ gl_FragColor = color;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+FragmentShaderColorAA::FragmentShaderColorAA()
+ : color_location_(-1) {}
+
+void FragmentShaderColorAA::Init(WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "color",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+ color_location_ = locations[0];
+}
+
+std::string FragmentShaderColorAA::GetShaderString(
+ TexCoordPrecision precision) const {
+ return FRAGMENT_SHADER(
+ precision mediump float;
+ uniform vec4 color;
+ varying vec4 edge_dist[2]; // 8 edge distances.
+
+ void main() {
+ vec4 d4 = min(edge_dist[0], edge_dist[1]);
+ vec2 d2 = min(d4.xz, d4.yw);
+ float aa = clamp(gl_FragCoord.w * min(d2.x, d2.y), 0.0, 1.0);
+ gl_FragColor = color * aa;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+FragmentShaderCheckerboard::FragmentShaderCheckerboard()
+ : alpha_location_(-1),
+ tex_transform_location_(-1),
+ frequency_location_(-1) {}
+
+void FragmentShaderCheckerboard::Init(WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {
+ static const char* uniforms[] = {
+ "alpha",
+ "texTransform",
+ "frequency",
+ "color",
+ };
+ int locations[arraysize(uniforms)];
+
+ GetProgramUniformLocations(context,
+ program,
+ arraysize(uniforms),
+ uniforms,
+ locations,
+ using_bind_uniform,
+ base_uniform_index);
+ alpha_location_ = locations[0];
+ tex_transform_location_ = locations[1];
+ frequency_location_ = locations[2];
+ color_location_ = locations[3];
+}
+
+std::string FragmentShaderCheckerboard::GetShaderString(
+ TexCoordPrecision precision) const {
+ // Shader based on Example 13-17 of "OpenGL ES 2.0 Programming Guide"
+ // by Munshi, Ginsburg, Shreiner.
+ return FRAGMENT_SHADER(
+ precision mediump float;
+ precision mediump int;
+ varying vec2 v_texCoord;
+ uniform float alpha;
+ uniform float frequency;
+ uniform vec4 texTransform;
+ uniform vec4 color;
+ void main() {
+ vec4 color1 = vec4(1.0, 1.0, 1.0, 1.0);
+ vec4 color2 = color;
+ vec2 texCoord =
+ clamp(v_texCoord, 0.0, 1.0) * texTransform.zw + texTransform.xy;
+ vec2 coord = mod(floor(texCoord * frequency * 2.0), 2.0);
+ float picker = abs(coord.x - coord.y);
+ gl_FragColor = mix(color1, color2, picker) * alpha;
+ }
+ ); // NOLINT(whitespace/parens)
+}
+
+} // namespace cc
diff --git a/chromium/cc/output/shader.h b/chromium/cc/output/shader.h
new file mode 100644
index 00000000000..7135befd389
--- /dev/null
+++ b/chromium/cc/output/shader.h
@@ -0,0 +1,745 @@
+// Copyright 2011 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.
+
+#ifndef CC_OUTPUT_SHADER_H_
+#define CC_OUTPUT_SHADER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "cc/base/cc_export.h"
+#include "third_party/skia/include/core/SkColorPriv.h"
+
+namespace gfx {
+class Point;
+class Size;
+}
+
+namespace WebKit { class WebGraphicsContext3D; }
+
+namespace cc {
+
+enum TexCoordPrecision {
+ TexCoordPrecisionNA,
+ TexCoordPrecisionMedium,
+ TexCoordPrecisionHigh,
+};
+
+// Note: The highp_threshold_cache must be provided by the caller to make
+// the caching multi-thread/context safe in an easy low-overhead manner.
+// The caller must make sure to clear highp_threshold_cache to 0, so it can be
+// reinitialized, if a new or different context is used.
+CC_EXPORT TexCoordPrecision TexCoordPrecisionRequired(
+ WebKit::WebGraphicsContext3D* context,
+ int *highp_threshold_cache,
+ int highp_threshold_min,
+ gfx::Point max_coordinate);
+
+CC_EXPORT TexCoordPrecision TexCoordPrecisionRequired(
+ WebKit::WebGraphicsContext3D* context,
+ int *highp_threshold_cache,
+ int highp_threshold_min,
+ gfx::Size max_size);
+
+class VertexShaderPosTex {
+ public:
+ VertexShaderPosTex();
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index);
+ std::string GetShaderString() const;
+
+ int matrix_location() const { return matrix_location_; }
+
+ private:
+ int matrix_location_;
+
+ DISALLOW_COPY_AND_ASSIGN(VertexShaderPosTex);
+};
+
+class VertexShaderPosTexYUVStretch {
+ public:
+ VertexShaderPosTexYUVStretch();
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index);
+ std::string GetShaderString() const;
+
+ int matrix_location() const { return matrix_location_; }
+ int tex_scale_location() const { return tex_scale_location_; }
+
+ private:
+ int matrix_location_;
+ int tex_scale_location_;
+
+ DISALLOW_COPY_AND_ASSIGN(VertexShaderPosTexYUVStretch);
+};
+
+class VertexShaderPos {
+ public:
+ VertexShaderPos();
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index);
+ std::string GetShaderString() const;
+
+ int matrix_location() const { return matrix_location_; }
+
+ private:
+ int matrix_location_;
+
+ DISALLOW_COPY_AND_ASSIGN(VertexShaderPos);
+};
+
+class VertexShaderPosTexIdentity {
+ public:
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index) {}
+ std::string GetShaderString() const;
+};
+
+class VertexShaderPosTexTransform {
+ public:
+ VertexShaderPosTexTransform();
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index);
+ std::string GetShaderString() const;
+
+ int matrix_location() const { return matrix_location_; }
+ int tex_transform_location() const { return tex_transform_location_; }
+ int vertex_opacity_location() const { return vertex_opacity_location_; }
+
+ private:
+ int matrix_location_;
+ int tex_transform_location_;
+ int vertex_opacity_location_;
+
+ DISALLOW_COPY_AND_ASSIGN(VertexShaderPosTexTransform);
+};
+
+class VertexShaderQuad {
+ public:
+ VertexShaderQuad();
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index);
+ std::string GetShaderString() const;
+
+ int matrix_location() const { return matrix_location_; }
+ int viewport_location() const { return -1; }
+ int quad_location() const { return quad_location_; }
+ int edge_location() const { return -1; }
+
+ private:
+ int matrix_location_;
+ int quad_location_;
+
+ DISALLOW_COPY_AND_ASSIGN(VertexShaderQuad);
+};
+
+class VertexShaderQuadAA {
+ public:
+ VertexShaderQuadAA();
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index);
+ std::string GetShaderString() const;
+
+ int matrix_location() const { return matrix_location_; }
+ int viewport_location() const { return viewport_location_; }
+ int quad_location() const { return quad_location_; }
+ int edge_location() const { return edge_location_; }
+
+ private:
+ int matrix_location_;
+ int viewport_location_;
+ int quad_location_;
+ int edge_location_;
+
+ DISALLOW_COPY_AND_ASSIGN(VertexShaderQuadAA);
+};
+
+
+class VertexShaderQuadTexTransformAA {
+ public:
+ VertexShaderQuadTexTransformAA();
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index);
+ std::string GetShaderString() const;
+
+ int matrix_location() const { return matrix_location_; }
+ int viewport_location() const { return viewport_location_; }
+ int quad_location() const { return quad_location_; }
+ int edge_location() const { return edge_location_; }
+ int tex_transform_location() const { return tex_transform_location_; }
+
+ private:
+ int matrix_location_;
+ int viewport_location_;
+ int quad_location_;
+ int edge_location_;
+ int tex_transform_location_;
+
+ DISALLOW_COPY_AND_ASSIGN(VertexShaderQuadTexTransformAA);
+};
+
+class VertexShaderTile {
+ public:
+ VertexShaderTile();
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index);
+ std::string GetShaderString() const;
+
+ int matrix_location() const { return matrix_location_; }
+ int viewport_location() const { return -1; }
+ int quad_location() const { return quad_location_; }
+ int edge_location() const { return -1; }
+ int vertex_tex_transform_location() const {
+ return vertex_tex_transform_location_;
+ }
+
+ private:
+ int matrix_location_;
+ int quad_location_;
+ int vertex_tex_transform_location_;
+
+ DISALLOW_COPY_AND_ASSIGN(VertexShaderTile);
+};
+
+class VertexShaderTileAA {
+ public:
+ VertexShaderTileAA();
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index);
+ std::string GetShaderString() const;
+
+ int matrix_location() const { return matrix_location_; }
+ int viewport_location() const { return viewport_location_; }
+ int quad_location() const { return quad_location_; }
+ int edge_location() const { return edge_location_; }
+ int vertex_tex_transform_location() const {
+ return vertex_tex_transform_location_;
+ }
+
+ private:
+ int matrix_location_;
+ int viewport_location_;
+ int quad_location_;
+ int edge_location_;
+ int vertex_tex_transform_location_;
+
+ DISALLOW_COPY_AND_ASSIGN(VertexShaderTileAA);
+};
+
+class VertexShaderVideoTransform {
+ public:
+ VertexShaderVideoTransform();
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index);
+ std::string GetShaderString() const;
+
+ int matrix_location() const { return matrix_location_; }
+ int tex_matrix_location() const { return tex_matrix_location_; }
+
+ private:
+ int matrix_location_;
+ int tex_matrix_location_;
+
+ DISALLOW_COPY_AND_ASSIGN(VertexShaderVideoTransform);
+};
+
+class FragmentTexAlphaBinding {
+ public:
+ FragmentTexAlphaBinding();
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index);
+ int alpha_location() const { return alpha_location_; }
+ int fragment_tex_transform_location() const { return -1; }
+ int sampler_location() const { return sampler_location_; }
+
+ private:
+ int sampler_location_;
+ int alpha_location_;
+
+ DISALLOW_COPY_AND_ASSIGN(FragmentTexAlphaBinding);
+};
+
+class FragmentTexColorMatrixAlphaBinding {
+ public:
+ FragmentTexColorMatrixAlphaBinding();
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool usingBindUniform,
+ int* baseUniformIndex);
+ int alpha_location() const { return alpha_location_; }
+ int color_matrix_location() const { return color_matrix_location_; }
+ int color_offset_location() const { return color_offset_location_; }
+ int fragment_tex_transform_location() const { return -1; }
+ int sampler_location() const { return sampler_location_; }
+
+ private:
+ int sampler_location_;
+ int alpha_location_;
+ int color_matrix_location_;
+ int color_offset_location_;
+};
+
+class FragmentTexOpaqueBinding {
+ public:
+ FragmentTexOpaqueBinding();
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index);
+ int alpha_location() const { return -1; }
+ int fragment_tex_transform_location() const { return -1; }
+ int background_color_location() const { return -1; }
+ int sampler_location() const { return sampler_location_; }
+
+ private:
+ int sampler_location_;
+
+ DISALLOW_COPY_AND_ASSIGN(FragmentTexOpaqueBinding);
+};
+
+class FragmentTexBackgroundBinding {
+ public:
+ FragmentTexBackgroundBinding();
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index);
+ int background_color_location() const { return background_color_location_; }
+ int sampler_location() const { return sampler_location_; }
+
+ private:
+ int background_color_location_;
+ int sampler_location_;
+
+ DISALLOW_COPY_AND_ASSIGN(FragmentTexBackgroundBinding);
+};
+
+class FragmentShaderRGBATexVaryingAlpha : public FragmentTexOpaqueBinding {
+ public:
+ std::string GetShaderString(TexCoordPrecision precision) const;
+};
+
+class FragmentShaderRGBATexPremultiplyAlpha : public FragmentTexOpaqueBinding {
+ public:
+ std::string GetShaderString(TexCoordPrecision precision) const;
+};
+
+class FragmentShaderTexBackgroundVaryingAlpha
+ : public FragmentTexBackgroundBinding {
+ public:
+ std::string GetShaderString(TexCoordPrecision precision) const;
+};
+
+class FragmentShaderTexBackgroundPremultiplyAlpha
+ : public FragmentTexBackgroundBinding {
+ public:
+ std::string GetShaderString(TexCoordPrecision precision) const;
+};
+
+class FragmentShaderRGBATexAlpha : public FragmentTexAlphaBinding {
+ public:
+ std::string GetShaderString(TexCoordPrecision precision) const;
+};
+
+class FragmentShaderRGBATexColorMatrixAlpha
+ : public FragmentTexColorMatrixAlphaBinding {
+ public:
+ std::string GetShaderString(TexCoordPrecision precision) const;
+};
+
+class FragmentShaderRGBATexRectVaryingAlpha : public FragmentTexOpaqueBinding {
+ public:
+ std::string GetShaderString(TexCoordPrecision precision) const;
+};
+
+class FragmentShaderRGBATexOpaque : public FragmentTexOpaqueBinding {
+ public:
+ std::string GetShaderString(TexCoordPrecision precision) const;
+};
+
+class FragmentShaderRGBATex : public FragmentTexOpaqueBinding {
+ public:
+ std::string GetShaderString(TexCoordPrecision precision) const;
+};
+
+// Swizzles the red and blue component of sampled texel with alpha.
+class FragmentShaderRGBATexSwizzleAlpha : public FragmentTexAlphaBinding {
+ public:
+ std::string GetShaderString(TexCoordPrecision precision) const;
+};
+
+// Swizzles the red and blue component of sampled texel without alpha.
+class FragmentShaderRGBATexSwizzleOpaque : public FragmentTexOpaqueBinding {
+ public:
+ std::string GetShaderString(TexCoordPrecision precision) const;
+};
+
+// Fragment shader for external textures.
+class FragmentShaderOESImageExternal : public FragmentTexAlphaBinding {
+ public:
+ FragmentShaderOESImageExternal();
+
+ std::string GetShaderString(TexCoordPrecision precision) const;
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index);
+ private:
+ int sampler_location_;
+
+ DISALLOW_COPY_AND_ASSIGN(FragmentShaderOESImageExternal);
+};
+
+class FragmentShaderRGBATexAlphaAA {
+ public:
+ FragmentShaderRGBATexAlphaAA();
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index);
+ std::string GetShaderString(TexCoordPrecision precision) const;
+
+ int alpha_location() const { return alpha_location_; }
+ int sampler_location() const { return sampler_location_; }
+
+ private:
+ int sampler_location_;
+ int alpha_location_;
+
+ DISALLOW_COPY_AND_ASSIGN(FragmentShaderRGBATexAlphaAA);
+};
+
+class FragmentTexClampAlphaAABinding {
+ public:
+ FragmentTexClampAlphaAABinding();
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index);
+ int alpha_location() const { return alpha_location_; }
+ int sampler_location() const { return sampler_location_; }
+ int fragment_tex_transform_location() const {
+ return fragment_tex_transform_location_;
+ }
+
+ private:
+ int sampler_location_;
+ int alpha_location_;
+ int fragment_tex_transform_location_;
+
+ DISALLOW_COPY_AND_ASSIGN(FragmentTexClampAlphaAABinding);
+};
+
+class FragmentShaderRGBATexClampAlphaAA
+ : public FragmentTexClampAlphaAABinding {
+ public:
+ std::string GetShaderString(TexCoordPrecision precision) const;
+};
+
+// Swizzles the red and blue component of sampled texel.
+class FragmentShaderRGBATexClampSwizzleAlphaAA
+ : public FragmentTexClampAlphaAABinding {
+ public:
+ std::string GetShaderString(TexCoordPrecision precision) const;
+};
+
+class FragmentShaderRGBATexAlphaMask {
+ public:
+ FragmentShaderRGBATexAlphaMask();
+ std::string GetShaderString(TexCoordPrecision precision) const;
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index);
+ int alpha_location() const { return alpha_location_; }
+ int sampler_location() const { return sampler_location_; }
+ int mask_sampler_location() const { return mask_sampler_location_; }
+ int mask_tex_coord_scale_location() const {
+ return mask_tex_coord_scale_location_;
+ }
+ int mask_tex_coord_offset_location() const {
+ return mask_tex_coord_offset_location_;
+ }
+
+ private:
+ int sampler_location_;
+ int mask_sampler_location_;
+ int alpha_location_;
+ int mask_tex_coord_scale_location_;
+ int mask_tex_coord_offset_location_;
+
+ DISALLOW_COPY_AND_ASSIGN(FragmentShaderRGBATexAlphaMask);
+};
+
+class FragmentShaderRGBATexAlphaMaskAA {
+ public:
+ FragmentShaderRGBATexAlphaMaskAA();
+ std::string GetShaderString(TexCoordPrecision precision) const;
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index);
+ int alpha_location() const { return alpha_location_; }
+ int sampler_location() const { return sampler_location_; }
+ int mask_sampler_location() const { return mask_sampler_location_; }
+ int mask_tex_coord_scale_location() const {
+ return mask_tex_coord_scale_location_;
+ }
+ int mask_tex_coord_offset_location() const {
+ return mask_tex_coord_offset_location_;
+ }
+
+ private:
+ int sampler_location_;
+ int mask_sampler_location_;
+ int alpha_location_;
+ int mask_tex_coord_scale_location_;
+ int mask_tex_coord_offset_location_;
+
+ DISALLOW_COPY_AND_ASSIGN(FragmentShaderRGBATexAlphaMaskAA);
+};
+
+class FragmentShaderRGBATexAlphaMaskColorMatrixAA {
+ public:
+ FragmentShaderRGBATexAlphaMaskColorMatrixAA();
+ std::string GetShaderString(TexCoordPrecision precision) const;
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index);
+ int alpha_location() const { return alpha_location_; }
+ int sampler_location() const { return sampler_location_; }
+ int mask_sampler_location() const { return mask_sampler_location_; }
+ int mask_tex_coord_scale_location() const {
+ return mask_tex_coord_scale_location_;
+ }
+ int mask_tex_coord_offset_location() const {
+ return mask_tex_coord_offset_location_;
+ }
+ int color_matrix_location() const { return color_matrix_location_; }
+ int color_offset_location() const { return color_offset_location_; }
+
+ private:
+ int sampler_location_;
+ int mask_sampler_location_;
+ int alpha_location_;
+ int mask_tex_coord_scale_location_;
+ int mask_tex_coord_offset_location_;
+ int color_matrix_location_;
+ int color_offset_location_;
+};
+
+class FragmentShaderRGBATexAlphaColorMatrixAA {
+ public:
+ FragmentShaderRGBATexAlphaColorMatrixAA();
+ std::string GetShaderString(TexCoordPrecision precision) const;
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index);
+ int alpha_location() const { return alpha_location_; }
+ int sampler_location() const { return sampler_location_; }
+ int color_matrix_location() const { return color_matrix_location_; }
+ int color_offset_location() const { return color_offset_location_; }
+
+ private:
+ int sampler_location_;
+ int alpha_location_;
+ int color_matrix_location_;
+ int color_offset_location_;
+};
+
+class FragmentShaderRGBATexAlphaMaskColorMatrix {
+ public:
+ FragmentShaderRGBATexAlphaMaskColorMatrix();
+ std::string GetShaderString(TexCoordPrecision precision) const;
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index);
+ int alpha_location() const { return alpha_location_; }
+ int sampler_location() const { return sampler_location_; }
+ int mask_sampler_location() const { return mask_sampler_location_; }
+ int mask_tex_coord_scale_location() const {
+ return mask_tex_coord_scale_location_;
+ }
+ int mask_tex_coord_offset_location() const {
+ return mask_tex_coord_offset_location_;
+ }
+ int color_matrix_location() const { return color_matrix_location_; }
+ int color_offset_location() const { return color_offset_location_; }
+
+ private:
+ int sampler_location_;
+ int mask_sampler_location_;
+ int alpha_location_;
+ int mask_tex_coord_scale_location_;
+ int mask_tex_coord_offset_location_;
+ int color_matrix_location_;
+ int color_offset_location_;
+};
+
+class FragmentShaderYUVVideo {
+ public:
+ FragmentShaderYUVVideo();
+ std::string GetShaderString(TexCoordPrecision precision) const;
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index);
+ int y_texture_location() const { return y_texture_location_; }
+ int u_texture_location() const { return u_texture_location_; }
+ int v_texture_location() const { return v_texture_location_; }
+ int alpha_location() const { return alpha_location_; }
+ int yuv_matrix_location() const { return yuv_matrix_location_; }
+ int yuv_adj_location() const { return yuv_adj_location_; }
+
+ private:
+ int y_texture_location_;
+ int u_texture_location_;
+ int v_texture_location_;
+ int alpha_location_;
+ int yuv_matrix_location_;
+ int yuv_adj_location_;
+
+ DISALLOW_COPY_AND_ASSIGN(FragmentShaderYUVVideo);
+};
+
+
+class FragmentShaderYUVAVideo {
+ public:
+ FragmentShaderYUVAVideo();
+ std::string GetShaderString(TexCoordPrecision precision) const;
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index);
+
+ int y_texture_location() const { return y_texture_location_; }
+ int u_texture_location() const { return u_texture_location_; }
+ int v_texture_location() const { return v_texture_location_; }
+ int a_texture_location() const { return a_texture_location_; }
+ int alpha_location() const { return alpha_location_; }
+ int yuv_matrix_location() const { return yuv_matrix_location_; }
+ int yuv_adj_location() const { return yuv_adj_location_; }
+
+ private:
+ int y_texture_location_;
+ int u_texture_location_;
+ int v_texture_location_;
+ int a_texture_location_;
+ int alpha_location_;
+ int yuv_matrix_location_;
+ int yuv_adj_location_;
+
+ DISALLOW_COPY_AND_ASSIGN(FragmentShaderYUVAVideo);
+};
+
+class FragmentShaderColor {
+ public:
+ FragmentShaderColor();
+ std::string GetShaderString(TexCoordPrecision precision) const;
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index);
+ int color_location() const { return color_location_; }
+
+ private:
+ int color_location_;
+
+ DISALLOW_COPY_AND_ASSIGN(FragmentShaderColor);
+};
+
+class FragmentShaderColorAA {
+ public:
+ FragmentShaderColorAA();
+ std::string GetShaderString(TexCoordPrecision precision) const;
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index);
+ int color_location() const { return color_location_; }
+
+ private:
+ int color_location_;
+
+ DISALLOW_COPY_AND_ASSIGN(FragmentShaderColorAA);
+};
+
+class FragmentShaderCheckerboard {
+ public:
+ FragmentShaderCheckerboard();
+ std::string GetShaderString(TexCoordPrecision precision) const;
+
+ void Init(WebKit::WebGraphicsContext3D* context,
+ unsigned program,
+ bool using_bind_uniform,
+ int* base_uniform_index);
+ int alpha_location() const { return alpha_location_; }
+ int tex_transform_location() const { return tex_transform_location_; }
+ int frequency_location() const { return frequency_location_; }
+ int color_location() const { return color_location_; }
+
+ private:
+ int alpha_location_;
+ int tex_transform_location_;
+ int frequency_location_;
+ int color_location_;
+
+ DISALLOW_COPY_AND_ASSIGN(FragmentShaderCheckerboard);
+};
+
+} // namespace cc
+
+#endif // CC_OUTPUT_SHADER_H_
diff --git a/chromium/cc/output/shader_unittest.cc b/chromium/cc/output/shader_unittest.cc
new file mode 100644
index 00000000000..33cc13cd56c
--- /dev/null
+++ b/chromium/cc/output/shader_unittest.cc
@@ -0,0 +1,47 @@
+// Copyright 2013 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 "cc/output/shader.h"
+
+#include "cc/debug/fake_web_graphics_context_3d.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/size.h"
+
+namespace cc {
+
+TEST(ShaderTest, HighpThresholds) {
+ // The FakeWebGraphicsContext3D always uses a mediump precision of 10 bits
+ // which corresponds to a native highp threshold of 2^10 = 1024
+ FakeWebGraphicsContext3D context;
+
+ int threshold_cache = 0;
+ int threshold_min;
+ gfx::Point closePoint(512, 512);
+ gfx::Size smallSize(512, 512);
+ gfx::Point farPoint(2560, 2560);
+ gfx::Size bigSize(2560, 2560);
+
+ threshold_min = 0;
+ EXPECT_EQ(TexCoordPrecisionMedium, TexCoordPrecisionRequired(
+ &context, &threshold_cache, threshold_min, closePoint));
+ EXPECT_EQ(TexCoordPrecisionMedium, TexCoordPrecisionRequired(
+ &context, &threshold_cache, threshold_min, smallSize));
+ EXPECT_EQ(TexCoordPrecisionHigh, TexCoordPrecisionRequired(
+ &context, &threshold_cache, threshold_min, farPoint));
+ EXPECT_EQ(TexCoordPrecisionHigh, TexCoordPrecisionRequired(
+ &context, &threshold_cache, threshold_min, bigSize));
+
+ threshold_min = 3000;
+ EXPECT_EQ(TexCoordPrecisionMedium, TexCoordPrecisionRequired(
+ &context, &threshold_cache, threshold_min, closePoint));
+ EXPECT_EQ(TexCoordPrecisionMedium, TexCoordPrecisionRequired(
+ &context, &threshold_cache, threshold_min, smallSize));
+ EXPECT_EQ(TexCoordPrecisionMedium, TexCoordPrecisionRequired(
+ &context, &threshold_cache, threshold_min, farPoint));
+ EXPECT_EQ(TexCoordPrecisionMedium, TexCoordPrecisionRequired(
+ &context, &threshold_cache, threshold_min, bigSize));
+}
+
+} // namespace cc
diff --git a/chromium/cc/output/software_frame_data.cc b/chromium/cc/output/software_frame_data.cc
new file mode 100644
index 00000000000..0b430325092
--- /dev/null
+++ b/chromium/cc/output/software_frame_data.cc
@@ -0,0 +1,15 @@
+// Copyright 2013 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 "cc/output/software_frame_data.h"
+
+namespace cc {
+
+SoftwareFrameData::SoftwareFrameData()
+ : id(0),
+ handle(base::SharedMemory::NULLHandle()) {}
+
+SoftwareFrameData::~SoftwareFrameData() {}
+
+} // namespace cc
diff --git a/chromium/cc/output/software_frame_data.h b/chromium/cc/output/software_frame_data.h
new file mode 100644
index 00000000000..b7fb44a4e81
--- /dev/null
+++ b/chromium/cc/output/software_frame_data.h
@@ -0,0 +1,27 @@
+// Copyright 2013 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.
+
+#ifndef CC_OUTPUT_SOFTWARE_FRAME_DATA_H_
+#define CC_OUTPUT_SOFTWARE_FRAME_DATA_H_
+
+#include "base/memory/shared_memory.h"
+#include "cc/base/cc_export.h"
+#include "ui/gfx/rect.h"
+
+namespace cc {
+
+class CC_EXPORT SoftwareFrameData {
+ public:
+ SoftwareFrameData();
+ ~SoftwareFrameData();
+
+ unsigned id;
+ gfx::Size size;
+ gfx::Rect damage_rect;
+ base::SharedMemoryHandle handle;
+};
+
+} // namespace cc
+
+#endif // CC_OUTPUT_SOFTWARE_FRAME_DATA_H_
diff --git a/chromium/cc/output/software_output_device.cc b/chromium/cc/output/software_output_device.cc
new file mode 100644
index 00000000000..fc77eb4c701
--- /dev/null
+++ b/chromium/cc/output/software_output_device.cc
@@ -0,0 +1,59 @@
+// Copyright 2013 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 "cc/output/software_output_device.h"
+
+#include "base/logging.h"
+#include "cc/output/software_frame_data.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkDevice.h"
+#include "ui/gfx/skia_util.h"
+
+namespace cc {
+
+SoftwareOutputDevice::SoftwareOutputDevice() {}
+
+SoftwareOutputDevice::~SoftwareOutputDevice() {}
+
+void SoftwareOutputDevice::Resize(gfx::Size viewport_size) {
+ if (viewport_size_ == viewport_size)
+ return;
+
+ viewport_size_ = viewport_size;
+ device_ = skia::AdoptRef(new SkDevice(SkBitmap::kARGB_8888_Config,
+ viewport_size.width(), viewport_size.height(), true));
+ canvas_ = skia::AdoptRef(new SkCanvas(device_.get()));
+}
+
+SkCanvas* SoftwareOutputDevice::BeginPaint(gfx::Rect damage_rect) {
+ DCHECK(device_);
+ damage_rect_ = damage_rect;
+ return canvas_.get();
+}
+
+void SoftwareOutputDevice::EndPaint(SoftwareFrameData* frame_data) {
+ DCHECK(frame_data);
+ frame_data->id = 0;
+ frame_data->size = viewport_size_;
+ frame_data->damage_rect = damage_rect_;
+ frame_data->handle = base::SharedMemory::NULLHandle();
+}
+
+void SoftwareOutputDevice::CopyToBitmap(
+ gfx::Rect rect, SkBitmap* output) {
+ DCHECK(device_);
+ const SkBitmap& bitmap = device_->accessBitmap(false);
+ bitmap.extractSubset(output, gfx::RectToSkIRect(rect));
+}
+
+void SoftwareOutputDevice::Scroll(
+ gfx::Vector2d delta, gfx::Rect clip_rect) {
+ NOTIMPLEMENTED();
+}
+
+void SoftwareOutputDevice::ReclaimSoftwareFrame(unsigned id) {
+ NOTIMPLEMENTED();
+}
+
+} // namespace cc
diff --git a/chromium/cc/output/software_output_device.h b/chromium/cc/output/software_output_device.h
new file mode 100644
index 00000000000..fbada745a0a
--- /dev/null
+++ b/chromium/cc/output/software_output_device.h
@@ -0,0 +1,55 @@
+// 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.
+
+#ifndef CC_OUTPUT_SOFTWARE_OUTPUT_DEVICE_H_
+#define CC_OUTPUT_SOFTWARE_OUTPUT_DEVICE_H_
+
+#include "base/basictypes.h"
+#include "cc/base/cc_export.h"
+#include "skia/ext/refptr.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size.h"
+#include "ui/gfx/vector2d.h"
+
+class SkBitmap;
+class SkDevice;
+class SkCanvas;
+
+namespace cc {
+
+class SoftwareFrameData;
+
+// This is a "tear-off" class providing software drawing support to
+// OutputSurface, such as to a platform-provided window framebuffer.
+class CC_EXPORT SoftwareOutputDevice {
+ public:
+ SoftwareOutputDevice();
+ virtual ~SoftwareOutputDevice();
+
+ // SoftwareOutputDevice implementation.
+ virtual void Resize(gfx::Size size);
+
+ virtual SkCanvas* BeginPaint(gfx::Rect damage_rect);
+ virtual void EndPaint(SoftwareFrameData* frame_data);
+
+ virtual void CopyToBitmap(gfx::Rect rect, SkBitmap* output);
+ virtual void Scroll(gfx::Vector2d delta,
+ gfx::Rect clip_rect);
+
+ // TODO(skaslev) Remove this after UberCompositor lands.
+ virtual void ReclaimSoftwareFrame(unsigned id);
+
+ protected:
+ gfx::Size viewport_size_;
+ gfx::Rect damage_rect_;
+ skia::RefPtr<SkDevice> device_;
+ skia::RefPtr<SkCanvas> canvas_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SoftwareOutputDevice);
+};
+
+} // namespace cc
+
+#endif // CC_OUTPUT_SOFTWARE_OUTPUT_DEVICE_H_
diff --git a/chromium/cc/output/software_renderer.cc b/chromium/cc/output/software_renderer.cc
new file mode 100644
index 00000000000..7735e3c8c10
--- /dev/null
+++ b/chromium/cc/output/software_renderer.cc
@@ -0,0 +1,522 @@
+// 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 "cc/output/software_renderer.h"
+
+#include "base/debug/trace_event.h"
+#include "cc/base/math_util.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/output/compositor_frame_ack.h"
+#include "cc/output/compositor_frame_metadata.h"
+#include "cc/output/copy_output_request.h"
+#include "cc/output/output_surface.h"
+#include "cc/output/software_output_device.h"
+#include "cc/quads/checkerboard_draw_quad.h"
+#include "cc/quads/debug_border_draw_quad.h"
+#include "cc/quads/picture_draw_quad.h"
+#include "cc/quads/render_pass_draw_quad.h"
+#include "cc/quads/solid_color_draw_quad.h"
+#include "cc/quads/texture_draw_quad.h"
+#include "cc/quads/tile_draw_quad.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/skia/include/core/SkDevice.h"
+#include "third_party/skia/include/core/SkMatrix.h"
+#include "third_party/skia/include/core/SkShader.h"
+#include "third_party/skia/include/effects/SkLayerRasterizer.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/skia_util.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+
+namespace {
+
+static inline bool IsScalarNearlyInteger(SkScalar scalar) {
+ return SkScalarNearlyZero(scalar - SkScalarRound(scalar));
+}
+
+bool IsScaleAndIntegerTranslate(const SkMatrix& matrix) {
+ return IsScalarNearlyInteger(matrix[SkMatrix::kMTransX]) &&
+ IsScalarNearlyInteger(matrix[SkMatrix::kMTransY]) &&
+ SkScalarNearlyZero(matrix[SkMatrix::kMSkewX]) &&
+ SkScalarNearlyZero(matrix[SkMatrix::kMSkewY]) &&
+ SkScalarNearlyZero(matrix[SkMatrix::kMPersp0]) &&
+ SkScalarNearlyZero(matrix[SkMatrix::kMPersp1]) &&
+ SkScalarNearlyZero(matrix[SkMatrix::kMPersp2] - 1.0f);
+}
+
+} // anonymous namespace
+
+scoped_ptr<SoftwareRenderer> SoftwareRenderer::Create(
+ RendererClient* client,
+ OutputSurface* output_surface,
+ ResourceProvider* resource_provider) {
+ return make_scoped_ptr(
+ new SoftwareRenderer(client, output_surface, resource_provider));
+}
+
+SoftwareRenderer::SoftwareRenderer(RendererClient* client,
+ OutputSurface* output_surface,
+ ResourceProvider* resource_provider)
+ : DirectRenderer(client, output_surface, resource_provider),
+ visible_(true),
+ is_scissor_enabled_(false),
+ output_device_(output_surface->software_device()),
+ current_canvas_(NULL) {
+ if (resource_provider_) {
+ capabilities_.max_texture_size = resource_provider_->max_texture_size();
+ capabilities_.best_texture_format =
+ resource_provider_->best_texture_format();
+ }
+ capabilities_.using_set_visibility = true;
+ // The updater can access bitmaps while the SoftwareRenderer is using them.
+ capabilities_.allow_partial_texture_updates = true;
+ capabilities_.using_partial_swap = true;
+
+ capabilities_.using_map_image = Settings().use_map_image;
+ capabilities_.using_shared_memory_resources = true;
+}
+
+SoftwareRenderer::~SoftwareRenderer() {}
+
+const RendererCapabilities& SoftwareRenderer::Capabilities() const {
+ return capabilities_;
+}
+
+void SoftwareRenderer::BeginDrawingFrame(DrawingFrame* frame) {
+ TRACE_EVENT0("cc", "SoftwareRenderer::BeginDrawingFrame");
+ root_canvas_ = output_device_->BeginPaint(
+ gfx::ToEnclosingRect(frame->root_damage_rect));
+}
+
+void SoftwareRenderer::FinishDrawingFrame(DrawingFrame* frame) {
+ TRACE_EVENT0("cc", "SoftwareRenderer::FinishDrawingFrame");
+ current_framebuffer_lock_.reset();
+ current_canvas_ = NULL;
+ root_canvas_ = NULL;
+
+ current_frame_data_.reset(new SoftwareFrameData);
+ output_device_->EndPaint(current_frame_data_.get());
+}
+
+void SoftwareRenderer::SwapBuffers() {
+ CompositorFrame compositor_frame;
+ compositor_frame.metadata = client_->MakeCompositorFrameMetadata();
+ compositor_frame.software_frame_data = current_frame_data_.Pass();
+ output_surface_->SwapBuffers(&compositor_frame);
+}
+
+void SoftwareRenderer::ReceiveSwapBuffersAck(const CompositorFrameAck& ack) {
+ output_device_->ReclaimSoftwareFrame(ack.last_software_frame_id);
+}
+
+bool SoftwareRenderer::FlippedFramebuffer() const {
+ return false;
+}
+
+void SoftwareRenderer::EnsureScissorTestEnabled() {
+ is_scissor_enabled_ = true;
+ SetClipRect(scissor_rect_);
+}
+
+void SoftwareRenderer::EnsureScissorTestDisabled() {
+ // There is no explicit notion of enabling/disabling scissoring in software
+ // rendering, but the underlying effect we want is to clear any existing
+ // clipRect on the current SkCanvas. This is done by setting clipRect to
+ // the viewport's dimensions.
+ is_scissor_enabled_ = false;
+ SkDevice* device = current_canvas_->getDevice();
+ SetClipRect(gfx::Rect(device->width(), device->height()));
+}
+
+void SoftwareRenderer::Finish() {}
+
+void SoftwareRenderer::BindFramebufferToOutputSurface(DrawingFrame* frame) {
+ DCHECK(!client_->ExternalStencilTestEnabled());
+ current_framebuffer_lock_.reset();
+ current_canvas_ = root_canvas_;
+}
+
+bool SoftwareRenderer::BindFramebufferToTexture(
+ DrawingFrame* frame,
+ const ScopedResource* texture,
+ gfx::Rect target_rect) {
+ current_framebuffer_lock_.reset();
+ current_framebuffer_lock_ = make_scoped_ptr(
+ new ResourceProvider::ScopedWriteLockSoftware(
+ resource_provider_, texture->id()));
+ current_canvas_ = current_framebuffer_lock_->sk_canvas();
+ InitializeViewport(frame,
+ target_rect,
+ gfx::Rect(target_rect.size()),
+ target_rect.size());
+ return true;
+}
+
+void SoftwareRenderer::SetScissorTestRect(gfx::Rect scissor_rect) {
+ is_scissor_enabled_ = true;
+ scissor_rect_ = scissor_rect;
+ SetClipRect(scissor_rect);
+}
+
+void SoftwareRenderer::SetClipRect(gfx::Rect rect) {
+ // Skia applies the current matrix to clip rects so we reset it temporary.
+ SkMatrix current_matrix = current_canvas_->getTotalMatrix();
+ current_canvas_->resetMatrix();
+ current_canvas_->clipRect(gfx::RectToSkRect(rect), SkRegion::kReplace_Op);
+ current_canvas_->setMatrix(current_matrix);
+}
+
+void SoftwareRenderer::ClearCanvas(SkColor color) {
+ // SkCanvas::clear doesn't respect the current clipping region
+ // so we SkCanvas::drawColor instead if scissoring is active.
+ if (is_scissor_enabled_)
+ current_canvas_->drawColor(color, SkXfermode::kSrc_Mode);
+ else
+ current_canvas_->clear(color);
+}
+
+void SoftwareRenderer::ClearFramebuffer(DrawingFrame* frame) {
+ if (frame->current_render_pass->has_transparent_background) {
+ ClearCanvas(SkColorSetARGB(0, 0, 0, 0));
+ } else {
+#ifndef NDEBUG
+ // On DEBUG builds, opaque render passes are cleared to blue
+ // to easily see regions that were not drawn on the screen.
+ ClearCanvas(SkColorSetARGB(255, 0, 0, 255));
+#endif
+ }
+}
+
+void SoftwareRenderer::SetDrawViewport(gfx::Rect window_space_viewport) {}
+
+bool SoftwareRenderer::IsSoftwareResource(
+ ResourceProvider::ResourceId resource_id) const {
+ switch (resource_provider_->GetResourceType(resource_id)) {
+ case ResourceProvider::GLTexture:
+ return false;
+ case ResourceProvider::Bitmap:
+ return true;
+ case ResourceProvider::InvalidType:
+ break;
+ }
+
+ LOG(FATAL) << "Invalid resource type.";
+ return false;
+}
+
+void SoftwareRenderer::DoDrawQuad(DrawingFrame* frame, const DrawQuad* quad) {
+ TRACE_EVENT0("cc", "SoftwareRenderer::DoDrawQuad");
+ gfx::Transform quad_rect_matrix;
+ QuadRectTransform(&quad_rect_matrix, quad->quadTransform(), quad->rect);
+ gfx::Transform contents_device_transform =
+ frame->window_matrix * frame->projection_matrix * quad_rect_matrix;
+ contents_device_transform.FlattenTo2d();
+ SkMatrix sk_device_matrix;
+ gfx::TransformToFlattenedSkMatrix(contents_device_transform,
+ &sk_device_matrix);
+ current_canvas_->setMatrix(sk_device_matrix);
+
+ current_paint_.reset();
+ if (!IsScaleAndIntegerTranslate(sk_device_matrix)) {
+ // TODO(danakj): Until we can enable AA only on exterior edges of the
+ // layer, disable AA if any interior edges are present. crbug.com/248175
+ bool all_four_edges_are_exterior = quad->IsTopEdge() &&
+ quad->IsLeftEdge() &&
+ quad->IsBottomEdge() &&
+ quad->IsRightEdge();
+ if (Settings().allow_antialiasing &&
+ all_four_edges_are_exterior)
+ current_paint_.setAntiAlias(true);
+ current_paint_.setFilterBitmap(true);
+ }
+
+ if (quad->ShouldDrawWithBlending()) {
+ current_paint_.setAlpha(quad->opacity() * 255);
+ current_paint_.setXfermodeMode(SkXfermode::kSrcOver_Mode);
+ } else {
+ current_paint_.setXfermodeMode(SkXfermode::kSrc_Mode);
+ }
+
+ switch (quad->material) {
+ case DrawQuad::CHECKERBOARD:
+ DrawCheckerboardQuad(frame, CheckerboardDrawQuad::MaterialCast(quad));
+ break;
+ case DrawQuad::DEBUG_BORDER:
+ DrawDebugBorderQuad(frame, DebugBorderDrawQuad::MaterialCast(quad));
+ break;
+ case DrawQuad::PICTURE_CONTENT:
+ DrawPictureQuad(frame, PictureDrawQuad::MaterialCast(quad));
+ break;
+ case DrawQuad::RENDER_PASS:
+ DrawRenderPassQuad(frame, RenderPassDrawQuad::MaterialCast(quad));
+ break;
+ case DrawQuad::SOLID_COLOR:
+ DrawSolidColorQuad(frame, SolidColorDrawQuad::MaterialCast(quad));
+ break;
+ case DrawQuad::TEXTURE_CONTENT:
+ DrawTextureQuad(frame, TextureDrawQuad::MaterialCast(quad));
+ break;
+ case DrawQuad::TILED_CONTENT:
+ DrawTileQuad(frame, TileDrawQuad::MaterialCast(quad));
+ break;
+ case DrawQuad::INVALID:
+ case DrawQuad::IO_SURFACE_CONTENT:
+ case DrawQuad::YUV_VIDEO_CONTENT:
+ case DrawQuad::STREAM_VIDEO_CONTENT:
+ DrawUnsupportedQuad(frame, quad);
+ NOTREACHED();
+ break;
+ }
+
+ current_canvas_->resetMatrix();
+}
+
+void SoftwareRenderer::DrawCheckerboardQuad(const DrawingFrame* frame,
+ const CheckerboardDrawQuad* quad) {
+ current_paint_.setColor(quad->color);
+ current_paint_.setAlpha(quad->opacity() * SkColorGetA(quad->color));
+ current_canvas_->drawRect(gfx::RectFToSkRect(QuadVertexRect()),
+ current_paint_);
+}
+
+void SoftwareRenderer::DrawDebugBorderQuad(const DrawingFrame* frame,
+ const DebugBorderDrawQuad* quad) {
+ // We need to apply the matrix manually to have pixel-sized stroke width.
+ SkPoint vertices[4];
+ gfx::RectFToSkRect(QuadVertexRect()).toQuad(vertices);
+ SkPoint transformed_vertices[4];
+ current_canvas_->getTotalMatrix().mapPoints(transformed_vertices,
+ vertices,
+ 4);
+ current_canvas_->resetMatrix();
+
+ current_paint_.setColor(quad->color);
+ current_paint_.setAlpha(quad->opacity() * SkColorGetA(quad->color));
+ current_paint_.setStyle(SkPaint::kStroke_Style);
+ current_paint_.setStrokeWidth(quad->width);
+ current_canvas_->drawPoints(SkCanvas::kPolygon_PointMode,
+ 4, transformed_vertices, current_paint_);
+}
+
+void SoftwareRenderer::DrawPictureQuad(const DrawingFrame* frame,
+ const PictureDrawQuad* quad) {
+ SkMatrix content_matrix;
+ content_matrix.setRectToRect(
+ gfx::RectFToSkRect(quad->tex_coord_rect),
+ gfx::RectFToSkRect(QuadVertexRect()),
+ SkMatrix::kFill_ScaleToFit);
+ current_canvas_->concat(content_matrix);
+
+ if (quad->ShouldDrawWithBlending()) {
+ TRACE_EVENT0("cc", "SoftwareRenderer::DrawPictureQuad with blending");
+ SkBitmap temp_bitmap;
+ temp_bitmap.setConfig(SkBitmap::kARGB_8888_Config,
+ quad->texture_size.width(),
+ quad->texture_size.height());
+ temp_bitmap.allocPixels();
+ SkDevice temp_device(temp_bitmap);
+ SkCanvas temp_canvas(&temp_device);
+
+ quad->picture_pile->RasterToBitmap(
+ &temp_canvas, quad->content_rect, quad->contents_scale, NULL);
+
+ current_paint_.setFilterBitmap(true);
+ current_canvas_->drawBitmap(temp_bitmap, 0, 0, &current_paint_);
+ } else {
+ TRACE_EVENT0("cc",
+ "SoftwareRenderer::DrawPictureQuad direct from PicturePile");
+ quad->picture_pile->RasterDirect(
+ current_canvas_, quad->content_rect, quad->contents_scale, NULL);
+ }
+}
+
+void SoftwareRenderer::DrawSolidColorQuad(const DrawingFrame* frame,
+ const SolidColorDrawQuad* quad) {
+ current_paint_.setColor(quad->color);
+ current_paint_.setAlpha(quad->opacity() * SkColorGetA(quad->color));
+ current_canvas_->drawRect(gfx::RectFToSkRect(QuadVertexRect()),
+ current_paint_);
+}
+
+void SoftwareRenderer::DrawTextureQuad(const DrawingFrame* frame,
+ const TextureDrawQuad* quad) {
+ if (!IsSoftwareResource(quad->resource_id)) {
+ DrawUnsupportedQuad(frame, quad);
+ return;
+ }
+
+ // TODO(skaslev): Add support for non-premultiplied alpha.
+ ResourceProvider::ScopedReadLockSoftware lock(resource_provider_,
+ quad->resource_id);
+ const SkBitmap* bitmap = lock.sk_bitmap();
+ gfx::RectF uv_rect = gfx::ScaleRect(gfx::BoundingRect(quad->uv_top_left,
+ quad->uv_bottom_right),
+ bitmap->width(),
+ bitmap->height());
+ SkRect sk_uv_rect = gfx::RectFToSkRect(uv_rect);
+ SkRect quad_rect = gfx::RectFToSkRect(QuadVertexRect());
+
+ if (quad->flipped)
+ current_canvas_->scale(1, -1);
+
+ bool blend_background = quad->background_color != SK_ColorTRANSPARENT &&
+ !bitmap->isOpaque();
+ bool needs_layer = blend_background && (current_paint_.getAlpha() != 0xFF);
+ if (needs_layer) {
+ current_canvas_->saveLayerAlpha(&quad_rect, current_paint_.getAlpha());
+ current_paint_.setAlpha(0xFF);
+ }
+ if (blend_background) {
+ SkPaint background_paint;
+ background_paint.setColor(quad->background_color);
+ current_canvas_->drawRect(quad_rect, background_paint);
+ }
+
+ current_canvas_->drawBitmapRectToRect(*bitmap,
+ &sk_uv_rect,
+ quad_rect,
+ &current_paint_);
+
+ if (needs_layer)
+ current_canvas_->restore();
+}
+
+void SoftwareRenderer::DrawTileQuad(const DrawingFrame* frame,
+ const TileDrawQuad* quad) {
+ DCHECK(!output_surface_->ForcedDrawToSoftwareDevice());
+ DCHECK(IsSoftwareResource(quad->resource_id));
+ ResourceProvider::ScopedReadLockSoftware lock(resource_provider_,
+ quad->resource_id);
+
+ SkRect uv_rect = gfx::RectFToSkRect(quad->tex_coord_rect);
+ current_paint_.setFilterBitmap(true);
+ current_canvas_->drawBitmapRectToRect(*lock.sk_bitmap(), &uv_rect,
+ gfx::RectFToSkRect(QuadVertexRect()),
+ &current_paint_);
+}
+
+void SoftwareRenderer::DrawRenderPassQuad(const DrawingFrame* frame,
+ const RenderPassDrawQuad* quad) {
+ CachedResource* content_texture =
+ render_pass_textures_.get(quad->render_pass_id);
+ if (!content_texture || !content_texture->id())
+ return;
+
+ DCHECK(IsSoftwareResource(content_texture->id()));
+ ResourceProvider::ScopedReadLockSoftware lock(resource_provider_,
+ content_texture->id());
+
+ SkRect dest_rect = gfx::RectFToSkRect(QuadVertexRect());
+ SkRect content_rect = SkRect::MakeWH(quad->rect.width(), quad->rect.height());
+
+ SkMatrix content_mat;
+ content_mat.setRectToRect(content_rect, dest_rect,
+ SkMatrix::kFill_ScaleToFit);
+
+ const SkBitmap* content = lock.sk_bitmap();
+ skia::RefPtr<SkShader> shader = skia::AdoptRef(
+ SkShader::CreateBitmapShader(*content,
+ SkShader::kClamp_TileMode,
+ SkShader::kClamp_TileMode));
+ shader->setLocalMatrix(content_mat);
+ current_paint_.setShader(shader.get());
+
+ SkImageFilter* filter = quad->filter.get();
+ if (filter)
+ current_paint_.setImageFilter(filter);
+
+ if (quad->mask_resource_id) {
+ ResourceProvider::ScopedReadLockSoftware mask_lock(resource_provider_,
+ quad->mask_resource_id);
+
+ const SkBitmap* mask = mask_lock.sk_bitmap();
+
+ SkRect mask_rect = SkRect::MakeXYWH(
+ quad->mask_uv_rect.x() * mask->width(),
+ quad->mask_uv_rect.y() * mask->height(),
+ quad->mask_uv_rect.width() * mask->width(),
+ quad->mask_uv_rect.height() * mask->height());
+
+ SkMatrix mask_mat;
+ mask_mat.setRectToRect(mask_rect, dest_rect, SkMatrix::kFill_ScaleToFit);
+
+ skia::RefPtr<SkShader> mask_shader = skia::AdoptRef(
+ SkShader::CreateBitmapShader(*mask,
+ SkShader::kClamp_TileMode,
+ SkShader::kClamp_TileMode));
+ mask_shader->setLocalMatrix(mask_mat);
+
+ SkPaint mask_paint;
+ mask_paint.setShader(mask_shader.get());
+
+ skia::RefPtr<SkLayerRasterizer> mask_rasterizer =
+ skia::AdoptRef(new SkLayerRasterizer);
+ mask_rasterizer->addLayer(mask_paint);
+
+ current_paint_.setRasterizer(mask_rasterizer.get());
+ current_canvas_->drawRect(dest_rect, current_paint_);
+ } else {
+ // TODO(skaslev): Apply background filters and blend with content
+ current_canvas_->drawRect(dest_rect, current_paint_);
+ }
+}
+
+void SoftwareRenderer::DrawUnsupportedQuad(const DrawingFrame* frame,
+ const DrawQuad* quad) {
+#ifdef NDEBUG
+ current_paint_.setColor(SK_ColorWHITE);
+#else
+ current_paint_.setColor(SK_ColorMAGENTA);
+#endif
+ current_paint_.setAlpha(quad->opacity() * 255);
+ current_canvas_->drawRect(gfx::RectFToSkRect(QuadVertexRect()),
+ current_paint_);
+}
+
+void SoftwareRenderer::CopyCurrentRenderPassToBitmap(
+ DrawingFrame* frame,
+ scoped_ptr<CopyOutputRequest> request) {
+ gfx::Rect copy_rect = frame->current_render_pass->output_rect;
+ if (request->has_area()) {
+ // Intersect with the request's area, positioned with its origin at the
+ // origin of the full copy_rect.
+ copy_rect.Intersect(request->area() - copy_rect.OffsetFromOrigin());
+ }
+ gfx::Rect window_copy_rect = MoveFromDrawToWindowSpace(copy_rect);
+
+ scoped_ptr<SkBitmap> bitmap(new SkBitmap);
+ bitmap->setConfig(SkBitmap::kARGB_8888_Config,
+ window_copy_rect.width(),
+ window_copy_rect.height());
+ current_canvas_->readPixels(
+ bitmap.get(), window_copy_rect.x(), window_copy_rect.y());
+
+ request->SendBitmapResult(bitmap.Pass());
+}
+
+void SoftwareRenderer::GetFramebufferPixels(void* pixels, gfx::Rect rect) {
+ TRACE_EVENT0("cc", "SoftwareRenderer::GetFramebufferPixels");
+ SkBitmap subset_bitmap;
+ rect += current_viewport_rect_.OffsetFromOrigin();
+ output_device_->CopyToBitmap(rect, &subset_bitmap);
+ subset_bitmap.copyPixelsTo(pixels,
+ 4 * rect.width() * rect.height(),
+ 4 * rect.width());
+}
+
+void SoftwareRenderer::SetVisible(bool visible) {
+ if (visible_ == visible)
+ return;
+ visible_ = visible;
+}
+
+void SoftwareRenderer::SetDiscardBackBufferWhenNotVisible(bool discard) {
+ // TODO(piman, skaslev): Can we release the backbuffer? We don't currently
+ // receive memory policy yet anyway.
+ NOTIMPLEMENTED();
+}
+
+} // namespace cc
diff --git a/chromium/cc/output/software_renderer.h b/chromium/cc/output/software_renderer.h
new file mode 100644
index 00000000000..f810b68274d
--- /dev/null
+++ b/chromium/cc/output/software_renderer.h
@@ -0,0 +1,113 @@
+// 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.
+
+#ifndef CC_OUTPUT_SOFTWARE_RENDERER_H_
+#define CC_OUTPUT_SOFTWARE_RENDERER_H_
+
+#include "base/basictypes.h"
+#include "cc/base/cc_export.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/output/direct_renderer.h"
+
+namespace cc {
+
+class OutputSurface;
+class RendererClient;
+class ResourceProvider;
+class SoftwareOutputDevice;
+
+class CheckerboardDrawQuad;
+class DebugBorderDrawQuad;
+class PictureDrawQuad;
+class RenderPassDrawQuad;
+class SolidColorDrawQuad;
+class TextureDrawQuad;
+class TileDrawQuad;
+
+class CC_EXPORT SoftwareRenderer : public DirectRenderer {
+ public:
+ static scoped_ptr<SoftwareRenderer> Create(
+ RendererClient* client,
+ OutputSurface* output_surface,
+ ResourceProvider* resource_provider);
+
+ virtual ~SoftwareRenderer();
+ virtual const RendererCapabilities& Capabilities() const OVERRIDE;
+ virtual void Finish() OVERRIDE;
+ virtual void SwapBuffers() OVERRIDE;
+ virtual void GetFramebufferPixels(void* pixels, gfx::Rect rect) OVERRIDE;
+ virtual void SetVisible(bool visible) OVERRIDE;
+ virtual void SendManagedMemoryStats(
+ size_t bytes_visible,
+ size_t bytes_visible_and_nearby,
+ size_t bytes_allocated) OVERRIDE {}
+ virtual void ReceiveSwapBuffersAck(
+ const CompositorFrameAck& ack) OVERRIDE;
+ virtual void SetDiscardBackBufferWhenNotVisible(bool discard) OVERRIDE;
+
+ protected:
+ virtual void BindFramebufferToOutputSurface(DrawingFrame* frame) OVERRIDE;
+ virtual bool BindFramebufferToTexture(
+ DrawingFrame* frame,
+ const ScopedResource* texture,
+ gfx::Rect target_rect) OVERRIDE;
+ virtual void SetDrawViewport(gfx::Rect window_space_viewport) OVERRIDE;
+ virtual void SetScissorTestRect(gfx::Rect scissor_rect) OVERRIDE;
+ virtual void ClearFramebuffer(DrawingFrame* frame) OVERRIDE;
+ virtual void DoDrawQuad(DrawingFrame* frame, const DrawQuad* quad) OVERRIDE;
+ virtual void BeginDrawingFrame(DrawingFrame* frame) OVERRIDE;
+ virtual void FinishDrawingFrame(DrawingFrame* frame) OVERRIDE;
+ virtual bool FlippedFramebuffer() const OVERRIDE;
+ virtual void EnsureScissorTestEnabled() OVERRIDE;
+ virtual void EnsureScissorTestDisabled() OVERRIDE;
+ virtual void CopyCurrentRenderPassToBitmap(
+ DrawingFrame* frame,
+ scoped_ptr<CopyOutputRequest> request) OVERRIDE;
+
+ SoftwareRenderer(
+ RendererClient* client,
+ OutputSurface* output_surface,
+ ResourceProvider* resource_provider);
+
+ private:
+ void ClearCanvas(SkColor color);
+ void SetClipRect(gfx::Rect rect);
+ bool IsSoftwareResource(ResourceProvider::ResourceId resource_id) const;
+
+ void DrawCheckerboardQuad(const DrawingFrame* frame,
+ const CheckerboardDrawQuad* quad);
+ void DrawDebugBorderQuad(const DrawingFrame* frame,
+ const DebugBorderDrawQuad* quad);
+ void DrawPictureQuad(const DrawingFrame* frame,
+ const PictureDrawQuad* quad);
+ void DrawRenderPassQuad(const DrawingFrame* frame,
+ const RenderPassDrawQuad* quad);
+ void DrawSolidColorQuad(const DrawingFrame* frame,
+ const SolidColorDrawQuad* quad);
+ void DrawTextureQuad(const DrawingFrame* frame,
+ const TextureDrawQuad* quad);
+ void DrawTileQuad(const DrawingFrame* frame,
+ const TileDrawQuad* quad);
+ void DrawUnsupportedQuad(const DrawingFrame* frame,
+ const DrawQuad* quad);
+
+ RendererCapabilities capabilities_;
+ bool visible_;
+ bool is_scissor_enabled_;
+ gfx::Rect scissor_rect_;
+
+ SoftwareOutputDevice* output_device_;
+ SkCanvas* root_canvas_;
+ SkCanvas* current_canvas_;
+ SkPaint current_paint_;
+ scoped_ptr<ResourceProvider::ScopedWriteLockSoftware>
+ current_framebuffer_lock_;
+ scoped_ptr<SoftwareFrameData> current_frame_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(SoftwareRenderer);
+};
+
+} // namespace cc
+
+#endif // CC_OUTPUT_SOFTWARE_RENDERER_H_
diff --git a/chromium/cc/output/software_renderer_unittest.cc b/chromium/cc/output/software_renderer_unittest.cc
new file mode 100644
index 00000000000..238d654ac19
--- /dev/null
+++ b/chromium/cc/output/software_renderer_unittest.cc
@@ -0,0 +1,270 @@
+// 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 "cc/output/software_renderer.h"
+
+#include "cc/layers/quad_sink.h"
+#include "cc/output/compositor_frame_metadata.h"
+#include "cc/output/software_output_device.h"
+#include "cc/quads/render_pass.h"
+#include "cc/quads/render_pass_draw_quad.h"
+#include "cc/quads/solid_color_draw_quad.h"
+#include "cc/quads/tile_draw_quad.h"
+#include "cc/test/animation_test_common.h"
+#include "cc/test/fake_output_surface.h"
+#include "cc/test/geometry_test_utils.h"
+#include "cc/test/render_pass_test_common.h"
+#include "cc/test/render_pass_test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkDevice.h"
+
+namespace cc {
+namespace {
+
+class SoftwareRendererTest : public testing::Test, public RendererClient {
+ public:
+ SoftwareRendererTest() : should_clear_root_render_pass_(true) {}
+
+ void InitializeRenderer(
+ scoped_ptr<SoftwareOutputDevice> software_output_device) {
+ output_surface_ = FakeOutputSurface::CreateSoftware(
+ software_output_device.Pass());
+ resource_provider_ = ResourceProvider::Create(output_surface_.get(), 0);
+ renderer_ = SoftwareRenderer::Create(
+ this, output_surface_.get(), resource_provider());
+ }
+
+ ResourceProvider* resource_provider() const {
+ return resource_provider_.get();
+ }
+
+ SoftwareRenderer* renderer() const { return renderer_.get(); }
+
+ void set_viewport(gfx::Rect viewport) {
+ viewport_ = viewport;
+ }
+
+ void set_should_clear_root_render_pass(bool clear_root_render_pass) {
+ should_clear_root_render_pass_ = clear_root_render_pass;
+ }
+
+ // RendererClient implementation.
+ virtual gfx::Rect DeviceViewport() const OVERRIDE {
+ return viewport_;
+ }
+ virtual float DeviceScaleFactor() const OVERRIDE {
+ return 1.f;
+ }
+ virtual const LayerTreeSettings& Settings() const OVERRIDE {
+ return settings_;
+ }
+ virtual void SetFullRootLayerDamage() OVERRIDE {}
+ virtual bool HasImplThread() const OVERRIDE { return false; }
+ virtual bool ShouldClearRootRenderPass() const OVERRIDE {
+ return should_clear_root_render_pass_;
+ }
+ virtual CompositorFrameMetadata MakeCompositorFrameMetadata() const OVERRIDE {
+ return CompositorFrameMetadata();
+ }
+ virtual bool AllowPartialSwap() const OVERRIDE {
+ return true;
+ }
+ virtual bool ExternalStencilTestEnabled() const OVERRIDE { return false; }
+
+ protected:
+ scoped_ptr<FakeOutputSurface> output_surface_;
+ scoped_ptr<ResourceProvider> resource_provider_;
+ scoped_ptr<SoftwareRenderer> renderer_;
+ gfx::Rect viewport_;
+ LayerTreeSettings settings_;
+ bool should_clear_root_render_pass_;
+};
+
+TEST_F(SoftwareRendererTest, SolidColorQuad) {
+ gfx::Size outer_size(100, 100);
+ gfx::Size inner_size(98, 98);
+ gfx::Rect outer_rect(outer_size);
+ gfx::Rect inner_rect(gfx::Point(1, 1), inner_size);
+ set_viewport(gfx::Rect(outer_size));
+
+ InitializeRenderer(make_scoped_ptr(new SoftwareOutputDevice));
+
+ scoped_ptr<SharedQuadState> shared_quad_state = SharedQuadState::Create();
+ shared_quad_state->SetAll(
+ gfx::Transform(), outer_size, outer_rect, outer_rect, false, 1.0);
+ RenderPass::Id root_render_pass_id = RenderPass::Id(1, 1);
+ scoped_ptr<TestRenderPass> root_render_pass = TestRenderPass::Create();
+ root_render_pass->SetNew(
+ root_render_pass_id, outer_rect, outer_rect, gfx::Transform());
+ scoped_ptr<SolidColorDrawQuad> outer_quad = SolidColorDrawQuad::Create();
+ outer_quad->SetNew(
+ shared_quad_state.get(), outer_rect, SK_ColorYELLOW, false);
+ scoped_ptr<SolidColorDrawQuad> inner_quad = SolidColorDrawQuad::Create();
+ inner_quad->SetNew(shared_quad_state.get(), inner_rect, SK_ColorCYAN, false);
+ root_render_pass->AppendQuad(inner_quad.PassAs<DrawQuad>());
+ root_render_pass->AppendQuad(outer_quad.PassAs<DrawQuad>());
+
+ RenderPassList list;
+ list.push_back(root_render_pass.PassAs<RenderPass>());
+ renderer()->DrawFrame(&list);
+
+ SkBitmap output;
+ output.setConfig(SkBitmap::kARGB_8888_Config,
+ DeviceViewport().width(),
+ DeviceViewport().height());
+ output.allocPixels();
+ renderer()->GetFramebufferPixels(output.getPixels(), outer_rect);
+
+ EXPECT_EQ(SK_ColorYELLOW, output.getColor(0, 0));
+ EXPECT_EQ(SK_ColorYELLOW,
+ output.getColor(outer_size.width() - 1, outer_size.height() - 1));
+ EXPECT_EQ(SK_ColorCYAN, output.getColor(1, 1));
+ EXPECT_EQ(SK_ColorCYAN,
+ output.getColor(inner_size.width() - 1, inner_size.height() - 1));
+}
+
+TEST_F(SoftwareRendererTest, TileQuad) {
+ gfx::Size outer_size(100, 100);
+ gfx::Size inner_size(98, 98);
+ gfx::Rect outer_rect(outer_size);
+ gfx::Rect inner_rect(gfx::Point(1, 1), inner_size);
+ set_viewport(gfx::Rect(outer_size));
+ InitializeRenderer(make_scoped_ptr(new SoftwareOutputDevice));
+
+ ResourceProvider::ResourceId resource_yellow =
+ resource_provider()->CreateResource(
+ outer_size, GL_RGBA, ResourceProvider::TextureUsageAny);
+ ResourceProvider::ResourceId resource_cyan =
+ resource_provider()->CreateResource(
+ inner_size, GL_RGBA, ResourceProvider::TextureUsageAny);
+
+ SkBitmap yellow_tile;
+ yellow_tile.setConfig(
+ SkBitmap::kARGB_8888_Config, outer_size.width(), outer_size.height());
+ yellow_tile.allocPixels();
+ yellow_tile.eraseColor(SK_ColorYELLOW);
+
+ SkBitmap cyan_tile;
+ cyan_tile.setConfig(
+ SkBitmap::kARGB_8888_Config, inner_size.width(), inner_size.height());
+ cyan_tile.allocPixels();
+ cyan_tile.eraseColor(SK_ColorCYAN);
+
+ resource_provider()->SetPixels(
+ resource_yellow,
+ static_cast<uint8_t*>(yellow_tile.getPixels()),
+ gfx::Rect(outer_size),
+ gfx::Rect(outer_size),
+ gfx::Vector2d());
+ resource_provider()->SetPixels(resource_cyan,
+ static_cast<uint8_t*>(cyan_tile.getPixels()),
+ gfx::Rect(inner_size),
+ gfx::Rect(inner_size),
+ gfx::Vector2d());
+
+ gfx::Rect root_rect = DeviceViewport();
+
+ scoped_ptr<SharedQuadState> shared_quad_state = SharedQuadState::Create();
+ shared_quad_state->SetAll(
+ gfx::Transform(), outer_size, outer_rect, outer_rect, false, 1.0);
+ RenderPass::Id root_render_pass_id = RenderPass::Id(1, 1);
+ scoped_ptr<TestRenderPass> root_render_pass = TestRenderPass::Create();
+ root_render_pass->SetNew(
+ root_render_pass_id, root_rect, root_rect, gfx::Transform());
+ scoped_ptr<TileDrawQuad> outer_quad = TileDrawQuad::Create();
+ outer_quad->SetNew(shared_quad_state.get(),
+ outer_rect,
+ outer_rect,
+ resource_yellow,
+ gfx::RectF(outer_size),
+ outer_size,
+ false);
+ scoped_ptr<TileDrawQuad> inner_quad = TileDrawQuad::Create();
+ inner_quad->SetNew(shared_quad_state.get(),
+ inner_rect,
+ inner_rect,
+ resource_cyan,
+ gfx::RectF(inner_size),
+ inner_size,
+ false);
+ root_render_pass->AppendQuad(inner_quad.PassAs<DrawQuad>());
+ root_render_pass->AppendQuad(outer_quad.PassAs<DrawQuad>());
+
+ RenderPassList list;
+ list.push_back(root_render_pass.PassAs<RenderPass>());
+ renderer()->DrawFrame(&list);
+
+ SkBitmap output;
+ output.setConfig(SkBitmap::kARGB_8888_Config,
+ DeviceViewport().width(),
+ DeviceViewport().height());
+ output.allocPixels();
+ renderer()->GetFramebufferPixels(output.getPixels(), outer_rect);
+
+ EXPECT_EQ(SK_ColorYELLOW, output.getColor(0, 0));
+ EXPECT_EQ(SK_ColorYELLOW,
+ output.getColor(outer_size.width() - 1, outer_size.height() - 1));
+ EXPECT_EQ(SK_ColorCYAN, output.getColor(1, 1));
+ EXPECT_EQ(SK_ColorCYAN,
+ output.getColor(inner_size.width() - 1, inner_size.height() - 1));
+}
+
+TEST_F(SoftwareRendererTest, ShouldClearRootRenderPass) {
+ gfx::Rect viewport_rect(0, 0, 100, 100);
+ set_viewport(viewport_rect);
+ set_should_clear_root_render_pass(false);
+ InitializeRenderer(make_scoped_ptr(new SoftwareOutputDevice));
+
+ RenderPassList list;
+
+ SkBitmap output;
+ output.setConfig(SkBitmap::kARGB_8888_Config,
+ viewport_rect.width(),
+ viewport_rect.height());
+ output.allocPixels();
+
+ // Draw a fullscreen green quad in a first frame.
+ RenderPass::Id root_clear_pass_id(1, 0);
+ TestRenderPass* root_clear_pass = AddRenderPass(
+ &list, root_clear_pass_id, viewport_rect, gfx::Transform());
+ AddQuad(root_clear_pass, viewport_rect, SK_ColorGREEN);
+
+ renderer()->DecideRenderPassAllocationsForFrame(list);
+ renderer()->DrawFrame(&list);
+ renderer()->GetFramebufferPixels(output.getPixels(), viewport_rect);
+
+ EXPECT_EQ(SK_ColorGREEN, output.getColor(0, 0));
+ EXPECT_EQ(SK_ColorGREEN,
+ output.getColor(viewport_rect.width() - 1, viewport_rect.height() - 1));
+
+ list.clear();
+
+ // Draw a smaller magenta rect without filling the viewport in a separate
+ // frame.
+ gfx::Rect smaller_rect(20, 20, 60, 60);
+
+ RenderPass::Id root_smaller_pass_id(2, 0);
+ TestRenderPass* root_smaller_pass = AddRenderPass(
+ &list, root_smaller_pass_id, viewport_rect, gfx::Transform());
+ AddQuad(root_smaller_pass, smaller_rect, SK_ColorMAGENTA);
+
+ renderer()->DecideRenderPassAllocationsForFrame(list);
+ renderer()->DrawFrame(&list);
+ renderer()->GetFramebufferPixels(output.getPixels(), viewport_rect);
+
+ // If we didn't clear, the borders should still be green.
+ EXPECT_EQ(SK_ColorGREEN, output.getColor(0, 0));
+ EXPECT_EQ(SK_ColorGREEN,
+ output.getColor(viewport_rect.width() - 1, viewport_rect.height() - 1));
+
+ EXPECT_EQ(SK_ColorMAGENTA,
+ output.getColor(smaller_rect.x(), smaller_rect.y()));
+ EXPECT_EQ(SK_ColorMAGENTA,
+ output.getColor(smaller_rect.right() - 1, smaller_rect.bottom() - 1));
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/quads/checkerboard_draw_quad.cc b/chromium/cc/quads/checkerboard_draw_quad.cc
new file mode 100644
index 00000000000..5e17d885f48
--- /dev/null
+++ b/chromium/cc/quads/checkerboard_draw_quad.cc
@@ -0,0 +1,53 @@
+// 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 "cc/quads/checkerboard_draw_quad.h"
+
+#include "base/logging.h"
+#include "base/values.h"
+
+namespace cc {
+
+CheckerboardDrawQuad::CheckerboardDrawQuad() : color(0) {}
+
+scoped_ptr<CheckerboardDrawQuad> CheckerboardDrawQuad::Create() {
+ return make_scoped_ptr(new CheckerboardDrawQuad);
+}
+
+void CheckerboardDrawQuad::SetNew(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ SkColor color) {
+ gfx::Rect opaque_rect = SkColorGetA(color) == 255 ? rect : gfx::Rect();
+ gfx::Rect visible_rect = rect;
+ bool needs_blending = false;
+ DrawQuad::SetAll(shared_quad_state, DrawQuad::CHECKERBOARD, rect, opaque_rect,
+ visible_rect, needs_blending);
+ this->color = color;
+}
+
+void CheckerboardDrawQuad::SetAll(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::Rect visible_rect,
+ bool needs_blending,
+ SkColor color) {
+ DrawQuad::SetAll(shared_quad_state, DrawQuad::CHECKERBOARD, rect, opaque_rect,
+ visible_rect, needs_blending);
+ this->color = color;
+}
+
+void CheckerboardDrawQuad::IterateResources(
+ const ResourceIteratorCallback& callback) {}
+
+const CheckerboardDrawQuad* CheckerboardDrawQuad::MaterialCast(
+ const DrawQuad* quad) {
+ DCHECK(quad->material == DrawQuad::CHECKERBOARD);
+ return static_cast<const CheckerboardDrawQuad*>(quad);
+}
+
+void CheckerboardDrawQuad::ExtendValue(base::DictionaryValue* value) const {
+ value->SetInteger("color", color);
+}
+
+} // namespace cc
diff --git a/chromium/cc/quads/checkerboard_draw_quad.h b/chromium/cc/quads/checkerboard_draw_quad.h
new file mode 100644
index 00000000000..4f7452eb0a6
--- /dev/null
+++ b/chromium/cc/quads/checkerboard_draw_quad.h
@@ -0,0 +1,44 @@
+// 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.
+
+#ifndef CC_QUADS_CHECKERBOARD_DRAW_QUAD_H_
+#define CC_QUADS_CHECKERBOARD_DRAW_QUAD_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/quads/draw_quad.h"
+#include "third_party/skia/include/core/SkColor.h"
+
+namespace cc {
+
+class CC_EXPORT CheckerboardDrawQuad : public DrawQuad {
+ public:
+ static scoped_ptr<CheckerboardDrawQuad> Create();
+
+ void SetNew(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ SkColor color);
+
+ void SetAll(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::Rect visible_rect,
+ bool needs_blending,
+ SkColor color);
+
+ SkColor color;
+
+ virtual void IterateResources(const ResourceIteratorCallback& callback)
+ OVERRIDE;
+
+ static const CheckerboardDrawQuad* MaterialCast(const DrawQuad*);
+
+ private:
+ virtual void ExtendValue(base::DictionaryValue* value) const OVERRIDE;
+ CheckerboardDrawQuad();
+};
+
+} // namespace cc
+
+#endif // CC_QUADS_CHECKERBOARD_DRAW_QUAD_H_
diff --git a/chromium/cc/quads/content_draw_quad_base.cc b/chromium/cc/quads/content_draw_quad_base.cc
new file mode 100644
index 00000000000..947af0b2d94
--- /dev/null
+++ b/chromium/cc/quads/content_draw_quad_base.cc
@@ -0,0 +1,58 @@
+// Copyright 2013 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 "cc/quads/content_draw_quad_base.h"
+
+#include "base/logging.h"
+#include "base/values.h"
+#include "cc/base/math_util.h"
+
+namespace cc {
+
+ContentDrawQuadBase::ContentDrawQuadBase()
+ : swizzle_contents(false) {
+}
+
+ContentDrawQuadBase::~ContentDrawQuadBase() {
+}
+
+void ContentDrawQuadBase::SetNew(const SharedQuadState* shared_quad_state,
+ DrawQuad::Material material,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ const gfx::RectF& tex_coord_rect,
+ gfx::Size texture_size,
+ bool swizzle_contents) {
+ gfx::Rect visible_rect = rect;
+ bool needs_blending = false;
+ DrawQuad::SetAll(shared_quad_state, material, rect, opaque_rect,
+ visible_rect, needs_blending);
+ this->tex_coord_rect = tex_coord_rect;
+ this->texture_size = texture_size;
+ this->swizzle_contents = swizzle_contents;
+}
+
+void ContentDrawQuadBase::SetAll(const SharedQuadState* shared_quad_state,
+ DrawQuad::Material material,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::Rect visible_rect,
+ bool needs_blending,
+ const gfx::RectF& tex_coord_rect,
+ gfx::Size texture_size,
+ bool swizzle_contents) {
+ DrawQuad::SetAll(shared_quad_state, material, rect, opaque_rect,
+ visible_rect, needs_blending);
+ this->tex_coord_rect = tex_coord_rect;
+ this->texture_size = texture_size;
+ this->swizzle_contents = swizzle_contents;
+}
+
+void ContentDrawQuadBase::ExtendValue(base::DictionaryValue* value) const {
+ value->Set("tex_coord_rect", MathUtil::AsValue(tex_coord_rect).release());
+ value->Set("texture_size", MathUtil::AsValue(texture_size).release());
+ value->SetBoolean("swizzle_contents", swizzle_contents);
+}
+
+} // namespace cc
diff --git a/chromium/cc/quads/content_draw_quad_base.h b/chromium/cc/quads/content_draw_quad_base.h
new file mode 100644
index 00000000000..cbf18ca2b15
--- /dev/null
+++ b/chromium/cc/quads/content_draw_quad_base.h
@@ -0,0 +1,49 @@
+// Copyright 2013 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.
+
+#ifndef CC_QUADS_CONTENT_DRAW_QUAD_BASE_H_
+#define CC_QUADS_CONTENT_DRAW_QUAD_BASE_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/quads/draw_quad.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/size.h"
+
+namespace cc {
+
+class CC_EXPORT ContentDrawQuadBase : public DrawQuad {
+ public:
+ void SetNew(const SharedQuadState* shared_quad_state,
+ DrawQuad::Material material,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ const gfx::RectF& tex_coord_rect,
+ gfx::Size texture_size,
+ bool swizzle_contents);
+
+ void SetAll(const SharedQuadState* shared_quad_state,
+ DrawQuad::Material material,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::Rect visible_rect,
+ bool needs_blending,
+ const gfx::RectF& tex_coord_rect,
+ gfx::Size texture_size,
+ bool swizzle_contents);
+
+ gfx::RectF tex_coord_rect;
+ gfx::Size texture_size;
+ bool swizzle_contents;
+
+ protected:
+ ContentDrawQuadBase();
+ virtual ~ContentDrawQuadBase();
+ virtual void ExtendValue(base::DictionaryValue* value) const OVERRIDE;
+};
+
+} // namespace cc
+
+#endif // CC_QUADS_CONTENT_DRAW_QUAD_BASE_H_
diff --git a/chromium/cc/quads/debug_border_draw_quad.cc b/chromium/cc/quads/debug_border_draw_quad.cc
new file mode 100644
index 00000000000..89ee8e01281
--- /dev/null
+++ b/chromium/cc/quads/debug_border_draw_quad.cc
@@ -0,0 +1,61 @@
+// 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 "cc/quads/debug_border_draw_quad.h"
+
+#include "base/logging.h"
+#include "base/values.h"
+
+namespace cc {
+
+DebugBorderDrawQuad::DebugBorderDrawQuad()
+ : color(0),
+ width(0) {
+}
+
+scoped_ptr<DebugBorderDrawQuad> DebugBorderDrawQuad::Create() {
+ return make_scoped_ptr(new DebugBorderDrawQuad);
+}
+
+void DebugBorderDrawQuad::SetNew(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ SkColor color,
+ int width) {
+ gfx::Rect opaque_rect;
+ gfx::Rect visible_rect = rect;
+ bool needs_blending = SkColorGetA(color) < 255;
+ DrawQuad::SetAll(shared_quad_state, DrawQuad::DEBUG_BORDER, rect, opaque_rect,
+ visible_rect, needs_blending);
+ this->color = color;
+ this->width = width;
+}
+
+void DebugBorderDrawQuad::SetAll(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::Rect visible_rect,
+ bool needs_blending,
+ SkColor color,
+ int width) {
+ DrawQuad::SetAll(shared_quad_state, DrawQuad::DEBUG_BORDER, rect, opaque_rect,
+ visible_rect, needs_blending);
+ this->color = color;
+ this->width = width;
+}
+
+void DebugBorderDrawQuad::IterateResources(
+ const ResourceIteratorCallback& callback) {}
+
+const DebugBorderDrawQuad* DebugBorderDrawQuad::MaterialCast(
+ const DrawQuad* quad) {
+ DCHECK(quad->material == DrawQuad::DEBUG_BORDER);
+ return static_cast<const DebugBorderDrawQuad*>(quad);
+}
+
+void DebugBorderDrawQuad::ExtendValue(base::DictionaryValue* value) const {
+ value->SetInteger("color", color);
+ value->SetInteger("width", width);
+}
+
+} // namespace cc
diff --git a/chromium/cc/quads/debug_border_draw_quad.h b/chromium/cc/quads/debug_border_draw_quad.h
new file mode 100644
index 00000000000..5b369514b50
--- /dev/null
+++ b/chromium/cc/quads/debug_border_draw_quad.h
@@ -0,0 +1,47 @@
+// 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.
+
+#ifndef CC_QUADS_DEBUG_BORDER_DRAW_QUAD_H_
+#define CC_QUADS_DEBUG_BORDER_DRAW_QUAD_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/quads/draw_quad.h"
+#include "third_party/skia/include/core/SkColor.h"
+
+namespace cc {
+
+class CC_EXPORT DebugBorderDrawQuad : public DrawQuad {
+ public:
+ static scoped_ptr<DebugBorderDrawQuad> Create();
+
+ void SetNew(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ SkColor color,
+ int width);
+
+ void SetAll(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::Rect visible_rect,
+ bool needs_blending,
+ SkColor color,
+ int width);
+
+ SkColor color;
+ int width;
+
+ virtual void IterateResources(const ResourceIteratorCallback& callback)
+ OVERRIDE;
+
+ static const DebugBorderDrawQuad* MaterialCast(const DrawQuad*);
+
+ private:
+ DebugBorderDrawQuad();
+ virtual void ExtendValue(base::DictionaryValue* value) const OVERRIDE;
+};
+
+} // namespace cc
+
+#endif // CC_QUADS_DEBUG_BORDER_DRAW_QUAD_H_
diff --git a/chromium/cc/quads/draw_quad.cc b/chromium/cc/quads/draw_quad.cc
new file mode 100644
index 00000000000..0d021d92083
--- /dev/null
+++ b/chromium/cc/quads/draw_quad.cc
@@ -0,0 +1,141 @@
+// 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 "cc/quads/draw_quad.h"
+
+#include "base/logging.h"
+#include "base/values.h"
+#include "cc/base/math_util.h"
+#include "cc/debug/traced_value.h"
+#include "cc/quads/checkerboard_draw_quad.h"
+#include "cc/quads/debug_border_draw_quad.h"
+#include "cc/quads/io_surface_draw_quad.h"
+#include "cc/quads/picture_draw_quad.h"
+#include "cc/quads/render_pass_draw_quad.h"
+#include "cc/quads/solid_color_draw_quad.h"
+#include "cc/quads/stream_video_draw_quad.h"
+#include "cc/quads/texture_draw_quad.h"
+#include "cc/quads/tile_draw_quad.h"
+#include "cc/quads/yuv_video_draw_quad.h"
+#include "ui/gfx/quad_f.h"
+
+namespace {
+template<typename T> T* TypedCopy(const cc::DrawQuad* other) {
+ return new T(*T::MaterialCast(other));
+}
+} // namespace
+
+namespace cc {
+
+DrawQuad::DrawQuad()
+ : material(INVALID),
+ needs_blending(false),
+ shared_quad_state() {
+}
+
+void DrawQuad::SetAll(const SharedQuadState* shared_quad_state,
+ Material material,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::Rect visible_rect,
+ bool needs_blending) {
+ this->material = material;
+ this->rect = rect;
+ this->opaque_rect = opaque_rect;
+ this->visible_rect = visible_rect;
+ this->needs_blending = needs_blending;
+ this->shared_quad_state = shared_quad_state;
+
+ DCHECK(shared_quad_state);
+ DCHECK(material != INVALID);
+}
+
+DrawQuad::~DrawQuad() {
+}
+
+scoped_ptr<DrawQuad> DrawQuad::Copy(
+ const SharedQuadState* copied_shared_quad_state) const {
+ scoped_ptr<DrawQuad> copy_quad;
+ switch (material) {
+ case CHECKERBOARD:
+ copy_quad.reset(TypedCopy<CheckerboardDrawQuad>(this));
+ break;
+ case DEBUG_BORDER:
+ copy_quad.reset(TypedCopy<DebugBorderDrawQuad>(this));
+ break;
+ case IO_SURFACE_CONTENT:
+ copy_quad.reset(TypedCopy<IOSurfaceDrawQuad>(this));
+ break;
+ case PICTURE_CONTENT:
+ copy_quad.reset(TypedCopy<PictureDrawQuad>(this));
+ break;
+ case TEXTURE_CONTENT:
+ copy_quad.reset(TypedCopy<TextureDrawQuad>(this));
+ break;
+ case SOLID_COLOR:
+ copy_quad.reset(TypedCopy<SolidColorDrawQuad>(this));
+ break;
+ case TILED_CONTENT:
+ copy_quad.reset(TypedCopy<TileDrawQuad>(this));
+ break;
+ case STREAM_VIDEO_CONTENT:
+ copy_quad.reset(TypedCopy<StreamVideoDrawQuad>(this));
+ break;
+ case YUV_VIDEO_CONTENT:
+ copy_quad.reset(TypedCopy<YUVVideoDrawQuad>(this));
+ break;
+ case RENDER_PASS: // RenderPass quads have their own copy() method.
+ case INVALID:
+ LOG(FATAL) << "Invalid DrawQuad material " << material;
+ break;
+ }
+ copy_quad->shared_quad_state = copied_shared_quad_state;
+ return copy_quad.Pass();
+}
+
+scoped_ptr<base::Value> DrawQuad::AsValue() const {
+ scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue());
+ value->SetInteger("material", material);
+ value->Set("shared_state",
+ TracedValue::CreateIDRef(shared_quad_state).release());
+
+ value->Set("content_space_rect", MathUtil::AsValue(rect).release());
+ bool rect_is_clipped;
+ gfx::QuadF rect_as_target_space_quad = MathUtil::MapQuad(
+ shared_quad_state->content_to_target_transform,
+ gfx::QuadF(rect),
+ &rect_is_clipped);
+ value->Set("rect_as_target_space_quad",
+ MathUtil::AsValue(rect_as_target_space_quad).release());
+ value->SetBoolean("rect_is_clipped", rect_is_clipped);
+
+ value->Set("content_space_opaque_rect",
+ MathUtil::AsValue(opaque_rect).release());
+ bool opaque_rect_is_clipped;
+ gfx::QuadF opaque_rect_as_target_space_quad = MathUtil::MapQuad(
+ shared_quad_state->content_to_target_transform,
+ gfx::QuadF(opaque_rect),
+ &opaque_rect_is_clipped);
+ value->Set("opaque_rect_as_target_space_quad",
+ MathUtil::AsValue(opaque_rect_as_target_space_quad).release());
+ value->SetBoolean("opaque_rect_is_clipped", opaque_rect_is_clipped);
+
+ value->Set("content_space_visible_rect",
+ MathUtil::AsValue(visible_rect).release());
+ bool visible_rect_is_clipped;
+ gfx::QuadF visible_rect_as_target_space_quad = MathUtil::MapQuad(
+ shared_quad_state->content_to_target_transform,
+ gfx::QuadF(visible_rect),
+ &visible_rect_is_clipped);
+ value->Set("visible_rect_as_target_space_quad",
+ MathUtil::AsValue(visible_rect_as_target_space_quad).release());
+ value->SetBoolean("visible_rect_is_clipped", visible_rect_is_clipped);
+
+ value->SetBoolean("needs_blending", needs_blending);
+ value->SetBoolean("should_draw_with_blending", ShouldDrawWithBlending());
+ ExtendValue(value.get());
+ return value.PassAs<base::Value>();
+}
+
+} // namespace cc
diff --git a/chromium/cc/quads/draw_quad.h b/chromium/cc/quads/draw_quad.h
new file mode 100644
index 00000000000..c3696d6f301
--- /dev/null
+++ b/chromium/cc/quads/draw_quad.h
@@ -0,0 +1,143 @@
+// 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.
+
+#ifndef CC_QUADS_DRAW_QUAD_H_
+#define CC_QUADS_DRAW_QUAD_H_
+
+#include "base/callback.h"
+#include "cc/base/cc_export.h"
+#include "cc/quads/shared_quad_state.h"
+#include "cc/resources/resource_provider.h"
+
+namespace base {
+class Value;
+class DictionaryValue;
+}
+
+namespace cc {
+
+// DrawQuad is a bag of data used for drawing a quad. Because different
+// materials need different bits of per-quad data to render, classes that derive
+// from DrawQuad store additional data in their derived instance. The Material
+// enum is used to "safely" downcast to the derived class.
+// Note: quads contain rects and sizes, which live in different spaces. There is
+// the "content space", which is the arbitrary space in which the quad's
+// geometry is defined (generally related to the layer that produced the quad,
+// e.g. the content space for TiledLayerImpls, or the geometry space for
+// PictureLayerImpls). There is also the "target space", which is the space, in
+// "physical" pixels, of the render target where the quads is drawn. The quad's
+// transform maps the content space to the target space.
+class CC_EXPORT DrawQuad {
+ public:
+ enum Material {
+ INVALID,
+ CHECKERBOARD,
+ DEBUG_BORDER,
+ IO_SURFACE_CONTENT,
+ PICTURE_CONTENT,
+ RENDER_PASS,
+ TEXTURE_CONTENT,
+ SOLID_COLOR,
+ TILED_CONTENT,
+ YUV_VIDEO_CONTENT,
+ STREAM_VIDEO_CONTENT,
+ };
+
+ virtual ~DrawQuad();
+
+ scoped_ptr<DrawQuad> Copy(
+ const SharedQuadState* copied_shared_quad_state) const;
+
+ // TODO(danakj): Chromify or remove these SharedQuadState helpers.
+ const gfx::Transform& quadTransform() const {
+ return shared_quad_state->content_to_target_transform;
+ }
+ gfx::Rect visibleContentRect() const {
+ return shared_quad_state->visible_content_rect;
+ }
+ gfx::Rect clipRect() const { return shared_quad_state->clip_rect; }
+ bool isClipped() const { return shared_quad_state->is_clipped; }
+ float opacity() const { return shared_quad_state->opacity; }
+
+ Material material;
+
+ // This rect, after applying the quad_transform(), gives the geometry that
+ // this quad should draw to. This rect lives in content space.
+ gfx::Rect rect;
+
+ // This specifies the region of the quad that is opaque. This rect lives in
+ // content space.
+ gfx::Rect opaque_rect;
+
+ // Allows changing the rect that gets drawn to make it smaller. This value
+ // should be clipped to |rect|. This rect lives in content space.
+ gfx::Rect visible_rect;
+
+ // By default blending is used when some part of the quad is not opaque.
+ // With this setting, it is possible to force blending on regardless of the
+ // opaque area.
+ bool needs_blending;
+
+ // Stores state common to a large bundle of quads; kept separate for memory
+ // efficiency. There is special treatment to reconstruct these pointers
+ // during serialization.
+ const SharedQuadState* shared_quad_state;
+
+ bool IsDebugQuad() const { return material == DEBUG_BORDER; }
+
+ bool ShouldDrawWithBlending() const {
+ if (needs_blending || shared_quad_state->opacity < 1.0f)
+ return true;
+ if (visible_rect.IsEmpty())
+ return false;
+ return !opaque_rect.Contains(visible_rect);
+ }
+
+ typedef ResourceProvider::ResourceId ResourceId;
+ typedef base::Callback<ResourceId(ResourceId)> ResourceIteratorCallback;
+ virtual void IterateResources(const ResourceIteratorCallback& callback) = 0;
+
+ // Is the left edge of this tile aligned with the originating layer's
+ // left edge?
+ bool IsLeftEdge() const { return !rect.x(); }
+
+ // Is the top edge of this tile aligned with the originating layer's
+ // top edge?
+ bool IsTopEdge() const { return !rect.y(); }
+
+ // Is the right edge of this tile aligned with the originating layer's
+ // right edge?
+ bool IsRightEdge() const {
+ return rect.right() == shared_quad_state->content_bounds.width();
+ }
+
+ // Is the bottom edge of this tile aligned with the originating layer's
+ // bottom edge?
+ bool IsBottomEdge() const {
+ return rect.bottom() == shared_quad_state->content_bounds.height();
+ }
+
+ // Is any edge of this tile aligned with the originating layer's
+ // corresponding edge?
+ bool IsEdge() const {
+ return IsLeftEdge() || IsTopEdge() || IsRightEdge() || IsBottomEdge();
+ }
+
+ scoped_ptr<base::Value> AsValue() const;
+
+ protected:
+ DrawQuad();
+
+ void SetAll(const SharedQuadState* shared_quad_state,
+ Material material,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::Rect visible_rect,
+ bool needs_blending);
+ virtual void ExtendValue(base::DictionaryValue* value) const = 0;
+};
+
+} // namespace cc
+
+#endif // CC_QUADS_DRAW_QUAD_H_
diff --git a/chromium/cc/quads/draw_quad_unittest.cc b/chromium/cc/quads/draw_quad_unittest.cc
new file mode 100644
index 00000000000..0ab0513ef01
--- /dev/null
+++ b/chromium/cc/quads/draw_quad_unittest.cc
@@ -0,0 +1,916 @@
+// 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 "cc/quads/draw_quad.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "cc/base/math_util.h"
+#include "cc/output/filter_operations.h"
+#include "cc/quads/checkerboard_draw_quad.h"
+#include "cc/quads/debug_border_draw_quad.h"
+#include "cc/quads/io_surface_draw_quad.h"
+#include "cc/quads/picture_draw_quad.h"
+#include "cc/quads/render_pass_draw_quad.h"
+#include "cc/quads/solid_color_draw_quad.h"
+#include "cc/quads/stream_video_draw_quad.h"
+#include "cc/quads/texture_draw_quad.h"
+#include "cc/quads/tile_draw_quad.h"
+#include "cc/quads/yuv_video_draw_quad.h"
+#include "cc/resources/picture_pile_impl.h"
+#include "cc/test/geometry_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/effects/SkBlurImageFilter.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+namespace {
+
+TEST(DrawQuadTest, CopySharedQuadState) {
+ gfx::Transform quad_transform = gfx::Transform(1.0, 0.0, 0.5, 1.0, 0.5, 0.0);
+ gfx::Size content_bounds(26, 28);
+ gfx::Rect visible_content_rect(10, 12, 14, 16);
+ gfx::Rect clip_rect(19, 21, 23, 25);
+ bool is_clipped = true;
+ float opacity = 0.25f;
+
+ scoped_ptr<SharedQuadState> state(SharedQuadState::Create());
+ state->SetAll(quad_transform,
+ content_bounds,
+ visible_content_rect,
+ clip_rect,
+ is_clipped,
+ opacity);
+
+ scoped_ptr<SharedQuadState> copy(state->Copy());
+ EXPECT_EQ(quad_transform, copy->content_to_target_transform);
+ EXPECT_RECT_EQ(visible_content_rect, copy->visible_content_rect);
+ EXPECT_EQ(opacity, copy->opacity);
+ EXPECT_RECT_EQ(clip_rect, copy->clip_rect);
+ EXPECT_EQ(is_clipped, copy->is_clipped);
+}
+
+scoped_ptr<SharedQuadState> CreateSharedQuadState() {
+ gfx::Transform quad_transform = gfx::Transform(1.0, 0.0, 0.5, 1.0, 0.5, 0.0);
+ gfx::Size content_bounds(26, 28);
+ gfx::Rect visible_content_rect(10, 12, 14, 16);
+ gfx::Rect clip_rect(19, 21, 23, 25);
+ bool is_clipped = false;
+ float opacity = 1.f;
+
+ scoped_ptr<SharedQuadState> state(SharedQuadState::Create());
+ state->SetAll(quad_transform,
+ content_bounds,
+ visible_content_rect,
+ clip_rect,
+ is_clipped,
+ opacity);
+ return state.Pass();
+}
+
+void CompareDrawQuad(DrawQuad* quad,
+ DrawQuad* copy,
+ SharedQuadState* copy_shared_state) {
+ EXPECT_EQ(quad->material, copy->material);
+ EXPECT_RECT_EQ(quad->rect, copy->rect);
+ EXPECT_RECT_EQ(quad->visible_rect, copy->visible_rect);
+ EXPECT_RECT_EQ(quad->opaque_rect, copy->opaque_rect);
+ EXPECT_EQ(quad->needs_blending, copy->needs_blending);
+ EXPECT_EQ(copy_shared_state, copy->shared_quad_state);
+}
+
+#define CREATE_SHARED_STATE() \
+ scoped_ptr<SharedQuadState> shared_state(CreateSharedQuadState()); \
+ scoped_ptr<SharedQuadState> copy_shared_state(shared_state->Copy()); \
+
+#define QUAD_DATA \
+ gfx::Rect quad_rect(30, 40, 50, 60); \
+ gfx::Rect quad_visible_rect(40, 50, 30, 20); \
+ gfx::Rect ALLOW_UNUSED quad_opaque_rect(60, 55, 10, 10); \
+ bool ALLOW_UNUSED needs_blending = true;
+
+#define SETUP_AND_COPY_QUAD_NEW(Type, quad) \
+ scoped_ptr<DrawQuad> copy_new(quad_new->Copy(copy_shared_state.get())); \
+ CompareDrawQuad(quad_new.get(), copy_new.get(), copy_shared_state.get()); \
+ const Type* ALLOW_UNUSED copy_quad = Type::MaterialCast(copy_new.get());
+
+#define SETUP_AND_COPY_QUAD_ALL(Type, quad) \
+ scoped_ptr<DrawQuad> copy_all(quad_all->Copy(copy_shared_state.get())); \
+ CompareDrawQuad(quad_all.get(), copy_all.get(), copy_shared_state.get()); \
+ copy_quad = Type::MaterialCast(copy_all.get());
+
+#define SETUP_AND_COPY_QUAD_NEW_1(Type, quad, a) \
+ scoped_ptr<DrawQuad> copy_new(quad_new->Copy(copy_shared_state.get(), a)); \
+ CompareDrawQuad(quad_new.get(), copy_new.get(), copy_shared_state.get()); \
+ const Type* ALLOW_UNUSED copy_quad = Type::MaterialCast(copy_new.get());
+
+#define SETUP_AND_COPY_QUAD_ALL_1(Type, quad, a) \
+ scoped_ptr<DrawQuad> copy_all(quad_all->Copy(copy_shared_state.get(), a)); \
+ CompareDrawQuad(quad_all.get(), copy_all.get(), copy_shared_state.get()); \
+ copy_quad = Type::MaterialCast(copy_all.get());
+
+#define CREATE_QUAD_1_NEW(Type, a) \
+ scoped_ptr<Type> quad_new(Type::Create()); \
+ { \
+ QUAD_DATA \
+ quad_new->SetNew(shared_state.get(), quad_rect, a); \
+ } \
+ SETUP_AND_COPY_QUAD_NEW(Type, quad_new);
+
+#define CREATE_QUAD_1_ALL(Type, a) \
+ scoped_ptr<Type> quad_all(Type::Create()); \
+ { \
+ QUAD_DATA \
+ quad_all->SetAll(shared_state.get(), quad_rect, quad_opaque_rect, \
+ quad_visible_rect, needs_blending, a); \
+ } \
+ SETUP_AND_COPY_QUAD_ALL(Type, quad_all);
+
+#define CREATE_QUAD_2_NEW(Type, a, b) \
+ scoped_ptr<Type> quad_new(Type::Create()); \
+ { \
+ QUAD_DATA \
+ quad_new->SetNew(shared_state.get(), quad_rect, a, b); \
+ } \
+ SETUP_AND_COPY_QUAD_NEW(Type, quad_new);
+
+#define CREATE_QUAD_2_ALL(Type, a, b) \
+ scoped_ptr<Type> quad_all(Type::Create()); \
+ { \
+ QUAD_DATA \
+ quad_all->SetAll(shared_state.get(), quad_rect, quad_opaque_rect, \
+ quad_visible_rect, needs_blending, a, b); \
+ } \
+ SETUP_AND_COPY_QUAD_ALL(Type, quad_all);
+
+#define CREATE_QUAD_3_NEW(Type, a, b, c) \
+ scoped_ptr<Type> quad_new(Type::Create()); \
+ { \
+ QUAD_DATA \
+ quad_new->SetNew(shared_state.get(), quad_rect, a, b, c); \
+ } \
+ SETUP_AND_COPY_QUAD_NEW(Type, quad_new);
+
+#define CREATE_QUAD_3_ALL(Type, a, b, c) \
+ scoped_ptr<Type> quad_all(Type::Create()); \
+ { \
+ QUAD_DATA \
+ quad_all->SetAll(shared_state.get(), quad_rect, quad_opaque_rect, \
+ quad_visible_rect, needs_blending, a, b, c); \
+ } \
+ SETUP_AND_COPY_QUAD_ALL(Type, quad_all);
+
+#define CREATE_QUAD_4_NEW(Type, a, b, c, d) \
+ scoped_ptr<Type> quad_new(Type::Create()); \
+ { \
+ QUAD_DATA \
+ quad_new->SetNew(shared_state.get(), quad_rect, a, b, c, d); \
+ } \
+ SETUP_AND_COPY_QUAD_NEW(Type, quad_new);
+
+#define CREATE_QUAD_4_ALL(Type, a, b, c, d) \
+ scoped_ptr<Type> quad_all(Type::Create()); \
+ { \
+ QUAD_DATA \
+ quad_all->SetAll(shared_state.get(), quad_rect, quad_opaque_rect, \
+ quad_visible_rect, needs_blending, a, b, c, d); \
+ } \
+ SETUP_AND_COPY_QUAD_ALL(Type, quad_all);
+
+#define CREATE_QUAD_5_NEW(Type, a, b, c, d, e) \
+ scoped_ptr<Type> quad_new(Type::Create()); \
+ { \
+ QUAD_DATA \
+ quad_new->SetNew(shared_state.get(), quad_rect, a, b, c, d, e); \
+ } \
+ SETUP_AND_COPY_QUAD_NEW(Type, quad_new);
+
+#define CREATE_QUAD_5_ALL(Type, a, b, c, d, e) \
+ scoped_ptr<Type> quad_all(Type::Create()); \
+ { \
+ QUAD_DATA \
+ quad_all->SetAll(shared_state.get(), quad_rect, quad_opaque_rect, \
+ quad_visible_rect, needs_blending, a, b, c, d, e); \
+ } \
+ SETUP_AND_COPY_QUAD_ALL(Type, quad_all);
+
+#define CREATE_QUAD_5_NEW_1(Type, a, b, c, d, e, copy_a) \
+ scoped_ptr<Type> quad_new(Type::Create()); \
+ { \
+ QUAD_DATA \
+ quad_new->SetNew(shared_state.get(), quad_rect, a, b, c, d, e); \
+ } \
+ SETUP_AND_COPY_QUAD_NEW_1(Type, quad_new, copy_a);
+
+#define CREATE_QUAD_5_ALL_1(Type, a, b, c, d, e, copy_a) \
+ scoped_ptr<Type> quad_all(Type::Create()); \
+ { \
+ QUAD_DATA \
+ quad_all->SetAll(shared_state.get(), quad_rect, quad_opaque_rect, \
+ quad_visible_rect, needs_blending, a, b, c, d, e); \
+ } \
+ SETUP_AND_COPY_QUAD_ALL_1(Type, quad_all, copy_a);
+
+#define CREATE_QUAD_6_NEW(Type, a, b, c, d, e, f) \
+ scoped_ptr<Type> quad_new(Type::Create()); \
+ { \
+ QUAD_DATA \
+ quad_new->SetNew(shared_state.get(), quad_rect, a, b, c, d, e, f); \
+ } \
+ SETUP_AND_COPY_QUAD_NEW(Type, quad_new);
+
+#define CREATE_QUAD_6_ALL(Type, a, b, c, d, e, f) \
+ scoped_ptr<Type> quad_all(Type::Create()); \
+ { \
+ QUAD_DATA \
+ quad_all->SetAll(shared_state.get(), quad_rect, quad_opaque_rect, \
+ quad_visible_rect, needs_blending, a, b, c, d, e, f); \
+ } \
+ SETUP_AND_COPY_QUAD_ALL(Type, quad_all);
+
+#define CREATE_QUAD_7_NEW(Type, a, b, c, d, e, f, g) \
+ scoped_ptr<Type> quad_new(Type::Create()); \
+ { \
+ QUAD_DATA \
+ quad_new->SetNew(shared_state.get(), quad_rect, a, b, c, d, e, f, g); \
+ } \
+ SETUP_AND_COPY_QUAD_NEW(Type, quad_new);
+
+#define CREATE_QUAD_7_ALL(Type, a, b, c, d, e, f, g) \
+ scoped_ptr<Type> quad_all(Type::Create()); \
+ { \
+ QUAD_DATA \
+ quad_all->SetAll(shared_state.get(), quad_rect, quad_opaque_rect, \
+ quad_visible_rect, needs_blending, \
+ a, b, c, d, e, f, g); \
+ } \
+ SETUP_AND_COPY_QUAD_ALL(Type, quad_all);
+
+#define CREATE_QUAD_8_NEW(Type, a, b, c, d, e, f, g, h) \
+ scoped_ptr<Type> quad_new(Type::Create()); \
+ { \
+ QUAD_DATA \
+ quad_new->SetNew(shared_state.get(), quad_rect, a, b, c, d, e, f, g, h); \
+ } \
+ SETUP_AND_COPY_QUAD_NEW(Type, quad_new);
+
+#define CREATE_QUAD_8_ALL(Type, a, b, c, d, e, f, g, h) \
+ scoped_ptr<Type> quad_all(Type::Create()); \
+ { \
+ QUAD_DATA \
+ quad_all->SetAll(shared_state.get(), quad_rect, quad_opaque_rect, \
+ quad_visible_rect, needs_blending, \
+ a, b, c, d, e, f, g, h); \
+ } \
+ SETUP_AND_COPY_QUAD_ALL(Type, quad_all);
+
+#define CREATE_QUAD_8_NEW_1(Type, a, b, c, d, e, f, g, h, copy_a) \
+ scoped_ptr<Type> quad_new(Type::Create()); \
+ { \
+ QUAD_DATA \
+ quad_new->SetNew(shared_state.get(), quad_rect, a, b, c, d, e, f, g, h); \
+ } \
+ SETUP_AND_COPY_QUAD_NEW_1(Type, quad_new, copy_a);
+
+#define CREATE_QUAD_8_ALL_1(Type, a, b, c, d, e, f, g, h, copy_a) \
+ scoped_ptr<Type> quad_all(Type::Create()); \
+ { \
+ QUAD_DATA \
+ quad_all->SetAll(shared_state.get(), quad_rect, quad_opaque_rect, \
+ quad_visible_rect, needs_blending, \
+ a, b, c, d, e, f, g, h); \
+ } \
+ SETUP_AND_COPY_QUAD_ALL_1(Type, quad_all, copy_a);
+
+#define CREATE_QUAD_9_NEW(Type, a, b, c, d, e, f, g, h, i) \
+ scoped_ptr<Type> quad_new(Type::Create()); \
+ { \
+ QUAD_DATA \
+ quad_new->SetNew(shared_state.get(), quad_rect, \
+ a, b, c, d, e, f, g, h, i); \
+ } \
+ SETUP_AND_COPY_QUAD_NEW(Type, quad_new);
+
+#define CREATE_QUAD_9_ALL(Type, a, b, c, d, e, f, g, h, i) \
+ scoped_ptr<Type> quad_all(Type::Create()); \
+ { \
+ QUAD_DATA \
+ quad_all->SetAll(shared_state.get(), quad_rect, quad_opaque_rect, \
+ quad_visible_rect, needs_blending, \
+ a, b, c, d, e, f, g, h, i); \
+ } \
+ SETUP_AND_COPY_QUAD_ALL(Type, quad_all);
+
+TEST(DrawQuadTest, CopyCheckerboardDrawQuad) {
+ SkColor color = 0xfabb0011;
+ CREATE_SHARED_STATE();
+
+ CREATE_QUAD_1_NEW(CheckerboardDrawQuad, color);
+ EXPECT_EQ(DrawQuad::CHECKERBOARD, copy_quad->material);
+ EXPECT_EQ(color, copy_quad->color);
+
+ CREATE_QUAD_1_ALL(CheckerboardDrawQuad, color);
+ EXPECT_EQ(DrawQuad::CHECKERBOARD, copy_quad->material);
+ EXPECT_EQ(color, copy_quad->color);
+}
+
+TEST(DrawQuadTest, CopyDebugBorderDrawQuad) {
+ SkColor color = 0xfabb0011;
+ int width = 99;
+ CREATE_SHARED_STATE();
+
+ CREATE_QUAD_2_NEW(DebugBorderDrawQuad, color, width);
+ EXPECT_EQ(DrawQuad::DEBUG_BORDER, copy_quad->material);
+ EXPECT_EQ(color, copy_quad->color);
+ EXPECT_EQ(width, copy_quad->width);
+
+ CREATE_QUAD_2_ALL(DebugBorderDrawQuad, color, width);
+ EXPECT_EQ(DrawQuad::DEBUG_BORDER, copy_quad->material);
+ EXPECT_EQ(color, copy_quad->color);
+ EXPECT_EQ(width, copy_quad->width);
+}
+
+TEST(DrawQuadTest, CopyIOSurfaceDrawQuad) {
+ gfx::Rect opaque_rect(3, 7, 10, 12);
+ gfx::Size size(58, 95);
+ ResourceProvider::ResourceId resource_id = 72;
+ IOSurfaceDrawQuad::Orientation orientation = IOSurfaceDrawQuad::UNFLIPPED;
+ CREATE_SHARED_STATE();
+
+ CREATE_QUAD_4_NEW(
+ IOSurfaceDrawQuad, opaque_rect, size, resource_id, orientation);
+ EXPECT_EQ(DrawQuad::IO_SURFACE_CONTENT, copy_quad->material);
+ EXPECT_RECT_EQ(opaque_rect, copy_quad->opaque_rect);
+ EXPECT_EQ(size, copy_quad->io_surface_size);
+ EXPECT_EQ(resource_id, copy_quad->io_surface_resource_id);
+ EXPECT_EQ(orientation, copy_quad->orientation);
+
+ CREATE_QUAD_3_ALL(IOSurfaceDrawQuad, size, resource_id, orientation);
+ EXPECT_EQ(DrawQuad::IO_SURFACE_CONTENT, copy_quad->material);
+ EXPECT_EQ(size, copy_quad->io_surface_size);
+ EXPECT_EQ(resource_id, copy_quad->io_surface_resource_id);
+ EXPECT_EQ(orientation, copy_quad->orientation);
+}
+
+TEST(DrawQuadTest, CopyRenderPassDrawQuad) {
+ RenderPass::Id render_pass_id(22, 64);
+ bool is_replica = true;
+ ResourceProvider::ResourceId mask_resource_id = 78;
+ gfx::Rect contents_changed_since_last_frame(42, 11, 74, 24);
+ gfx::RectF mask_u_v_rect(-45.f, -21.f, 33.f, 19.f);
+ FilterOperations filters;
+ filters.Append(FilterOperation::CreateBlurFilter(1.f));
+ FilterOperations background_filters;
+ background_filters.Append(
+ FilterOperation::CreateGrayscaleFilter(1.f));
+ skia::RefPtr<SkImageFilter> filter =
+ skia::AdoptRef(new SkBlurImageFilter(SK_Scalar1, SK_Scalar1));
+
+ RenderPass::Id copied_render_pass_id(235, 11);
+ CREATE_SHARED_STATE();
+
+ CREATE_QUAD_8_NEW_1(RenderPassDrawQuad,
+ render_pass_id,
+ is_replica,
+ mask_resource_id,
+ contents_changed_since_last_frame,
+ mask_u_v_rect,
+ filters,
+ filter,
+ background_filters,
+ copied_render_pass_id);
+ EXPECT_EQ(DrawQuad::RENDER_PASS, copy_quad->material);
+ EXPECT_EQ(copied_render_pass_id, copy_quad->render_pass_id);
+ EXPECT_EQ(is_replica, copy_quad->is_replica);
+ EXPECT_EQ(mask_resource_id, copy_quad->mask_resource_id);
+ EXPECT_RECT_EQ(contents_changed_since_last_frame,
+ copy_quad->contents_changed_since_last_frame);
+ EXPECT_EQ(mask_u_v_rect.ToString(), copy_quad->mask_uv_rect.ToString());
+ EXPECT_EQ(filters, copy_quad->filters);
+ EXPECT_EQ(filter, copy_quad->filter);
+ EXPECT_EQ(background_filters, copy_quad->background_filters);
+
+ CREATE_QUAD_8_ALL_1(RenderPassDrawQuad,
+ render_pass_id,
+ is_replica,
+ mask_resource_id,
+ contents_changed_since_last_frame,
+ mask_u_v_rect,
+ filters,
+ filter,
+ background_filters,
+ copied_render_pass_id);
+ EXPECT_EQ(DrawQuad::RENDER_PASS, copy_quad->material);
+ EXPECT_EQ(copied_render_pass_id, copy_quad->render_pass_id);
+ EXPECT_EQ(is_replica, copy_quad->is_replica);
+ EXPECT_EQ(mask_resource_id, copy_quad->mask_resource_id);
+ EXPECT_RECT_EQ(contents_changed_since_last_frame,
+ copy_quad->contents_changed_since_last_frame);
+ EXPECT_EQ(mask_u_v_rect.ToString(), copy_quad->mask_uv_rect.ToString());
+ EXPECT_EQ(filters, copy_quad->filters);
+ EXPECT_EQ(filter, copy_quad->filter);
+ EXPECT_EQ(background_filters, copy_quad->background_filters);
+}
+
+TEST(DrawQuadTest, CopySolidColorDrawQuad) {
+ SkColor color = 0x49494949;
+ bool force_anti_aliasing_off = false;
+ CREATE_SHARED_STATE();
+
+ CREATE_QUAD_2_NEW(SolidColorDrawQuad, color, force_anti_aliasing_off);
+ EXPECT_EQ(DrawQuad::SOLID_COLOR, copy_quad->material);
+ EXPECT_EQ(color, copy_quad->color);
+ EXPECT_EQ(force_anti_aliasing_off, copy_quad->force_anti_aliasing_off);
+
+ CREATE_QUAD_2_ALL(SolidColorDrawQuad, color, force_anti_aliasing_off);
+ EXPECT_EQ(DrawQuad::SOLID_COLOR, copy_quad->material);
+ EXPECT_EQ(color, copy_quad->color);
+ EXPECT_EQ(force_anti_aliasing_off, copy_quad->force_anti_aliasing_off);
+}
+
+TEST(DrawQuadTest, CopyStreamVideoDrawQuad) {
+ gfx::Rect opaque_rect(3, 7, 10, 12);
+ ResourceProvider::ResourceId resource_id = 64;
+ gfx::Transform matrix = gfx::Transform(0.5, 0.25, 1, 0.75, 0, 1);
+ CREATE_SHARED_STATE();
+
+ CREATE_QUAD_3_NEW(StreamVideoDrawQuad, opaque_rect, resource_id, matrix);
+ EXPECT_EQ(DrawQuad::STREAM_VIDEO_CONTENT, copy_quad->material);
+ EXPECT_RECT_EQ(opaque_rect, copy_quad->opaque_rect);
+ EXPECT_EQ(resource_id, copy_quad->resource_id);
+ EXPECT_EQ(matrix, copy_quad->matrix);
+
+ CREATE_QUAD_2_ALL(StreamVideoDrawQuad, resource_id, matrix);
+ EXPECT_EQ(DrawQuad::STREAM_VIDEO_CONTENT, copy_quad->material);
+ EXPECT_EQ(resource_id, copy_quad->resource_id);
+ EXPECT_EQ(matrix, copy_quad->matrix);
+}
+
+TEST(DrawQuadTest, CopyTextureDrawQuad) {
+ gfx::Rect opaque_rect(3, 7, 10, 12);
+ unsigned resource_id = 82;
+ bool premultiplied_alpha = true;
+ gfx::PointF uv_top_left(0.5f, 224.f);
+ gfx::PointF uv_bottom_right(51.5f, 260.f);
+ const float vertex_opacity[] = { 1.0f, 1.0f, 1.0f, 1.0f };
+ bool flipped = true;
+ CREATE_SHARED_STATE();
+
+ CREATE_QUAD_8_NEW(TextureDrawQuad,
+ opaque_rect,
+ resource_id,
+ premultiplied_alpha,
+ uv_top_left,
+ uv_bottom_right,
+ SK_ColorTRANSPARENT,
+ vertex_opacity,
+ flipped);
+ EXPECT_EQ(DrawQuad::TEXTURE_CONTENT, copy_quad->material);
+ EXPECT_RECT_EQ(opaque_rect, copy_quad->opaque_rect);
+ EXPECT_EQ(resource_id, copy_quad->resource_id);
+ EXPECT_EQ(premultiplied_alpha, copy_quad->premultiplied_alpha);
+ EXPECT_EQ(uv_top_left, copy_quad->uv_top_left);
+ EXPECT_EQ(uv_bottom_right, copy_quad->uv_bottom_right);
+ EXPECT_FLOAT_ARRAY_EQ(vertex_opacity, copy_quad->vertex_opacity, 4);
+ EXPECT_EQ(flipped, copy_quad->flipped);
+
+ CREATE_QUAD_7_ALL(TextureDrawQuad,
+ resource_id,
+ premultiplied_alpha,
+ uv_top_left,
+ uv_bottom_right,
+ SK_ColorTRANSPARENT,
+ vertex_opacity,
+ flipped);
+ EXPECT_EQ(DrawQuad::TEXTURE_CONTENT, copy_quad->material);
+ EXPECT_EQ(resource_id, copy_quad->resource_id);
+ EXPECT_EQ(premultiplied_alpha, copy_quad->premultiplied_alpha);
+ EXPECT_EQ(uv_top_left, copy_quad->uv_top_left);
+ EXPECT_EQ(uv_bottom_right, copy_quad->uv_bottom_right);
+ EXPECT_FLOAT_ARRAY_EQ(vertex_opacity, copy_quad->vertex_opacity, 4);
+ EXPECT_EQ(flipped, copy_quad->flipped);
+}
+
+TEST(DrawQuadTest, ClipTextureDrawQuad) {
+ gfx::Rect opaque_rect(3, 7, 10, 12);
+ unsigned resource_id = 82;
+ bool premultiplied_alpha = true;
+ bool flipped = true;
+ CREATE_SHARED_STATE();
+ // The original quad position is (30, 40) its size is 50*60.
+ shared_state->content_to_target_transform =
+ gfx::Transform(1.f, 0.f, 0.f, 1.f, 10.f, 20.f);
+ // After transformation, the quad position is (40, 60) its size is 50*60.
+ shared_state->clip_rect = gfx::Rect(50, 70, 30, 20);
+
+ // The original quad is 'ABCD', the clipped quad is 'abcd':
+ // 40 50 90
+ // B--:-------C 60
+ // | b----c -|-70
+ // | | | |
+ // | a----d -|-90
+ // | |
+ // A----------D 120
+ // UV and vertex opacity are stored per vertex on the parent rectangle 'ABCD'.
+
+ // This is the UV value for vertex 'B'.
+ gfx::PointF uv_top_left(0.1f, 0.2f);
+ // This is the UV value for vertex 'D'.
+ gfx::PointF uv_bottom_right(0.9f, 0.8f);
+ // This the vertex opacity for the vertices 'ABCD'.
+ const float vertex_opacity[] = { 0.3f, 0.4f, 0.7f, 0.8f };
+ {
+ CREATE_QUAD_8_NEW(TextureDrawQuad,
+ opaque_rect,
+ resource_id,
+ premultiplied_alpha,
+ uv_top_left,
+ uv_bottom_right,
+ SK_ColorTRANSPARENT,
+ vertex_opacity,
+ flipped);
+ CREATE_QUAD_7_ALL(TextureDrawQuad,
+ resource_id,
+ premultiplied_alpha,
+ uv_top_left,
+ uv_bottom_right,
+ SK_ColorTRANSPARENT,
+ vertex_opacity,
+ flipped);
+ EXPECT_TRUE(quad_all->PerformClipping());
+
+ // This is the expected UV value for vertex 'b'.
+ // uv(b) = uv(B) + (Bb / BD) * (uv(D) - uv(B))
+ // 0.3 = 0.2 + (10 / 60) * (0.8 - 0.2)
+ gfx::PointF uv_top_left_clipped(0.26f, 0.3f);
+ // This is the expected UV value for vertex 'd'.
+ // uv(d) = uv(B) + (Bd / BD) * (uv(D) - uv(B))
+ gfx::PointF uv_bottom_right_clipped(0.74f, 0.5f);
+ // This the expected vertex opacity for the vertices 'abcd'.
+ // They are computed with a bilinear interpolation of the corner values.
+ const float vertex_opacity_clipped[] = { 0.43f, 0.45f, 0.65f, 0.67f };
+
+ EXPECT_EQ(uv_top_left_clipped, quad_all->uv_top_left);
+ EXPECT_EQ(uv_bottom_right_clipped, quad_all->uv_bottom_right);
+ EXPECT_FLOAT_ARRAY_EQ(vertex_opacity_clipped, quad_all->vertex_opacity, 4);
+ }
+
+ uv_top_left = gfx::PointF(0.8f, 0.7f);
+ uv_bottom_right = gfx::PointF(0.2f, 0.1f);
+ {
+ CREATE_QUAD_8_NEW(TextureDrawQuad,
+ opaque_rect,
+ resource_id,
+ premultiplied_alpha,
+ uv_top_left,
+ uv_bottom_right,
+ SK_ColorTRANSPARENT,
+ vertex_opacity,
+ flipped);
+ CREATE_QUAD_7_ALL(TextureDrawQuad,
+ resource_id,
+ premultiplied_alpha,
+ uv_top_left,
+ uv_bottom_right,
+ SK_ColorTRANSPARENT,
+ vertex_opacity,
+ flipped);
+ EXPECT_TRUE(quad_all->PerformClipping());
+
+ // This is the expected UV value for vertex 'b'.
+ gfx::PointF uv_top_left_clipped(0.68f, 0.6f);
+ // This is the expected UV value for vertex 'd'.
+ gfx::PointF uv_bottom_right_clipped(0.32f, 0.4f);
+
+ EXPECT_EQ(uv_top_left_clipped, quad_all->uv_top_left);
+ EXPECT_EQ(uv_bottom_right_clipped, quad_all->uv_bottom_right);
+ }
+}
+
+TEST(DrawQuadTest, CopyTileDrawQuad) {
+ gfx::Rect opaque_rect(33, 44, 22, 33);
+ unsigned resource_id = 104;
+ gfx::RectF tex_coord_rect(31.f, 12.f, 54.f, 20.f);
+ gfx::Size texture_size(85, 32);
+ bool swizzle_contents = true;
+ CREATE_SHARED_STATE();
+
+ CREATE_QUAD_5_NEW(TileDrawQuad,
+ opaque_rect,
+ resource_id,
+ tex_coord_rect,
+ texture_size,
+ swizzle_contents);
+ EXPECT_EQ(DrawQuad::TILED_CONTENT, copy_quad->material);
+ EXPECT_RECT_EQ(opaque_rect, copy_quad->opaque_rect);
+ EXPECT_EQ(resource_id, copy_quad->resource_id);
+ EXPECT_EQ(tex_coord_rect, copy_quad->tex_coord_rect);
+ EXPECT_EQ(texture_size, copy_quad->texture_size);
+ EXPECT_EQ(swizzle_contents, copy_quad->swizzle_contents);
+
+ CREATE_QUAD_4_ALL(TileDrawQuad,
+ resource_id,
+ tex_coord_rect,
+ texture_size,
+ swizzle_contents);
+ EXPECT_EQ(DrawQuad::TILED_CONTENT, copy_quad->material);
+ EXPECT_EQ(resource_id, copy_quad->resource_id);
+ EXPECT_EQ(tex_coord_rect, copy_quad->tex_coord_rect);
+ EXPECT_EQ(texture_size, copy_quad->texture_size);
+ EXPECT_EQ(swizzle_contents, copy_quad->swizzle_contents);
+}
+
+TEST(DrawQuadTest, CopyYUVVideoDrawQuad) {
+ gfx::Rect opaque_rect(3, 7, 10, 12);
+ gfx::SizeF tex_scale(0.75f, 0.5f);
+ ResourceProvider::ResourceId y_plane_resource_id = 45;
+ ResourceProvider::ResourceId u_plane_resource_id = 532;
+ ResourceProvider::ResourceId v_plane_resource_id = 4;
+ ResourceProvider::ResourceId a_plane_resource_id = 63;
+ CREATE_SHARED_STATE();
+
+ CREATE_QUAD_6_NEW(YUVVideoDrawQuad,
+ opaque_rect,
+ tex_scale,
+ y_plane_resource_id,
+ u_plane_resource_id,
+ v_plane_resource_id,
+ a_plane_resource_id);
+ EXPECT_EQ(DrawQuad::YUV_VIDEO_CONTENT, copy_quad->material);
+ EXPECT_RECT_EQ(opaque_rect, copy_quad->opaque_rect);
+ EXPECT_EQ(tex_scale, copy_quad->tex_scale);
+ EXPECT_EQ(y_plane_resource_id, copy_quad->y_plane_resource_id);
+ EXPECT_EQ(u_plane_resource_id, copy_quad->u_plane_resource_id);
+ EXPECT_EQ(v_plane_resource_id, copy_quad->v_plane_resource_id);
+ EXPECT_EQ(a_plane_resource_id, copy_quad->a_plane_resource_id);
+
+ CREATE_QUAD_5_ALL(YUVVideoDrawQuad,
+ tex_scale,
+ y_plane_resource_id,
+ u_plane_resource_id,
+ v_plane_resource_id,
+ a_plane_resource_id);
+ EXPECT_EQ(DrawQuad::YUV_VIDEO_CONTENT, copy_quad->material);
+ EXPECT_EQ(tex_scale, copy_quad->tex_scale);
+ EXPECT_EQ(y_plane_resource_id, copy_quad->y_plane_resource_id);
+ EXPECT_EQ(u_plane_resource_id, copy_quad->u_plane_resource_id);
+ EXPECT_EQ(v_plane_resource_id, copy_quad->v_plane_resource_id);
+ EXPECT_EQ(a_plane_resource_id, copy_quad->a_plane_resource_id);
+}
+
+TEST(DrawQuadTest, CopyPictureDrawQuad) {
+ gfx::Rect opaque_rect(33, 44, 22, 33);
+ gfx::RectF tex_coord_rect(31.f, 12.f, 54.f, 20.f);
+ gfx::Size texture_size(85, 32);
+ bool swizzle_contents = true;
+ gfx::Rect content_rect(30, 40, 20, 30);
+ float contents_scale = 3.141592f;
+ bool can_draw_direct_to_backbuffer = true;
+ scoped_refptr<PicturePileImpl> picture_pile = PicturePileImpl::Create();
+ CREATE_SHARED_STATE();
+
+ CREATE_QUAD_8_NEW(PictureDrawQuad,
+ opaque_rect,
+ tex_coord_rect,
+ texture_size,
+ swizzle_contents,
+ content_rect,
+ contents_scale,
+ can_draw_direct_to_backbuffer,
+ picture_pile);
+ EXPECT_EQ(DrawQuad::PICTURE_CONTENT, copy_quad->material);
+ EXPECT_RECT_EQ(opaque_rect, copy_quad->opaque_rect);
+ EXPECT_EQ(tex_coord_rect, copy_quad->tex_coord_rect);
+ EXPECT_EQ(texture_size, copy_quad->texture_size);
+ EXPECT_EQ(swizzle_contents, copy_quad->swizzle_contents);
+ EXPECT_RECT_EQ(content_rect, copy_quad->content_rect);
+ EXPECT_EQ(contents_scale, copy_quad->contents_scale);
+ EXPECT_EQ(can_draw_direct_to_backbuffer,
+ copy_quad->can_draw_direct_to_backbuffer);
+ EXPECT_EQ(picture_pile, copy_quad->picture_pile);
+
+ CREATE_QUAD_7_ALL(PictureDrawQuad,
+ tex_coord_rect,
+ texture_size,
+ swizzle_contents,
+ content_rect,
+ contents_scale,
+ can_draw_direct_to_backbuffer,
+ picture_pile);
+ EXPECT_EQ(DrawQuad::PICTURE_CONTENT, copy_quad->material);
+ EXPECT_EQ(tex_coord_rect, copy_quad->tex_coord_rect);
+ EXPECT_EQ(texture_size, copy_quad->texture_size);
+ EXPECT_EQ(swizzle_contents, copy_quad->swizzle_contents);
+ EXPECT_RECT_EQ(content_rect, copy_quad->content_rect);
+ EXPECT_EQ(contents_scale, copy_quad->contents_scale);
+ EXPECT_EQ(can_draw_direct_to_backbuffer,
+ copy_quad->can_draw_direct_to_backbuffer);
+ EXPECT_EQ(picture_pile, copy_quad->picture_pile);
+}
+
+class DrawQuadIteratorTest : public testing::Test {
+ protected:
+ ResourceProvider::ResourceId IncrementResourceId(
+ ResourceProvider::ResourceId id) {
+ ++num_resources_;
+ return id + 1;
+ }
+
+ int IterateAndCount(DrawQuad* quad) {
+ num_resources_ = 0;
+ quad->IterateResources(base::Bind(
+ &DrawQuadIteratorTest::IncrementResourceId, base::Unretained(this)));
+ return num_resources_;
+ }
+
+ private:
+ int num_resources_;
+};
+
+TEST_F(DrawQuadIteratorTest, CheckerboardDrawQuad) {
+ SkColor color = 0xfabb0011;
+
+ CREATE_SHARED_STATE();
+ CREATE_QUAD_1_NEW(CheckerboardDrawQuad, color);
+ EXPECT_EQ(0, IterateAndCount(quad_new.get()));
+}
+
+TEST_F(DrawQuadIteratorTest, DebugBorderDrawQuad) {
+ SkColor color = 0xfabb0011;
+ int width = 99;
+
+ CREATE_SHARED_STATE();
+ CREATE_QUAD_2_NEW(DebugBorderDrawQuad, color, width);
+ EXPECT_EQ(0, IterateAndCount(quad_new.get()));
+}
+
+TEST_F(DrawQuadIteratorTest, IOSurfaceDrawQuad) {
+ gfx::Rect opaque_rect(3, 7, 10, 12);
+ gfx::Size size(58, 95);
+ ResourceProvider::ResourceId resource_id = 72;
+ IOSurfaceDrawQuad::Orientation orientation = IOSurfaceDrawQuad::UNFLIPPED;
+
+ CREATE_SHARED_STATE();
+ CREATE_QUAD_4_NEW(
+ IOSurfaceDrawQuad, opaque_rect, size, resource_id, orientation);
+ EXPECT_EQ(resource_id, quad_new->io_surface_resource_id);
+ EXPECT_EQ(1, IterateAndCount(quad_new.get()));
+ EXPECT_EQ(resource_id + 1, quad_new->io_surface_resource_id);
+}
+
+TEST_F(DrawQuadIteratorTest, RenderPassDrawQuad) {
+ RenderPass::Id render_pass_id(22, 64);
+ bool is_replica = true;
+ ResourceProvider::ResourceId mask_resource_id = 78;
+ gfx::Rect contents_changed_since_last_frame(42, 11, 74, 24);
+ gfx::RectF mask_u_v_rect(-45.f, -21.f, 33.f, 19.f);
+ FilterOperations filters;
+ filters.Append(FilterOperation::CreateBlurFilter(1.f));
+ FilterOperations background_filters;
+ background_filters.Append(
+ FilterOperation::CreateGrayscaleFilter(1.f));
+ skia::RefPtr<SkImageFilter> filter =
+ skia::AdoptRef(new SkBlurImageFilter(SK_Scalar1, SK_Scalar1));
+
+ RenderPass::Id copied_render_pass_id(235, 11);
+
+ CREATE_SHARED_STATE();
+ CREATE_QUAD_8_NEW_1(RenderPassDrawQuad,
+ render_pass_id,
+ is_replica,
+ mask_resource_id,
+ contents_changed_since_last_frame,
+ mask_u_v_rect,
+ filters,
+ filter,
+ background_filters,
+ copied_render_pass_id);
+ EXPECT_EQ(mask_resource_id, quad_new->mask_resource_id);
+ EXPECT_EQ(1, IterateAndCount(quad_new.get()));
+ EXPECT_EQ(mask_resource_id + 1, quad_new->mask_resource_id);
+ quad_new->mask_resource_id = 0;
+ EXPECT_EQ(0, IterateAndCount(quad_new.get()));
+ EXPECT_EQ(0u, quad_new->mask_resource_id);
+}
+
+TEST_F(DrawQuadIteratorTest, SolidColorDrawQuad) {
+ SkColor color = 0x49494949;
+ bool force_anti_aliasing_off = false;
+
+ CREATE_SHARED_STATE();
+ CREATE_QUAD_2_NEW(SolidColorDrawQuad, color, force_anti_aliasing_off);
+ EXPECT_EQ(0, IterateAndCount(quad_new.get()));
+}
+
+TEST_F(DrawQuadIteratorTest, StreamVideoDrawQuad) {
+ gfx::Rect opaque_rect(3, 7, 10, 12);
+ ResourceProvider::ResourceId resource_id = 64;
+ gfx::Transform matrix = gfx::Transform(0.5, 0.25, 1, 0.75, 0, 1);
+
+ CREATE_SHARED_STATE();
+ CREATE_QUAD_3_NEW(StreamVideoDrawQuad, opaque_rect, resource_id, matrix);
+ EXPECT_EQ(resource_id, quad_new->resource_id);
+ EXPECT_EQ(1, IterateAndCount(quad_new.get()));
+ EXPECT_EQ(resource_id + 1, quad_new->resource_id);
+}
+
+TEST_F(DrawQuadIteratorTest, TextureDrawQuad) {
+ gfx::Rect opaque_rect(3, 7, 10, 12);
+ unsigned resource_id = 82;
+ bool premultiplied_alpha = true;
+ gfx::PointF uv_top_left(0.5f, 224.f);
+ gfx::PointF uv_bottom_right(51.5f, 260.f);
+ const float vertex_opacity[] = { 1.0f, 1.0f, 1.0f, 1.0f };
+ bool flipped = true;
+
+ CREATE_SHARED_STATE();
+ CREATE_QUAD_8_NEW(TextureDrawQuad,
+ opaque_rect,
+ resource_id,
+ premultiplied_alpha,
+ uv_top_left,
+ uv_bottom_right,
+ SK_ColorTRANSPARENT,
+ vertex_opacity,
+ flipped);
+ EXPECT_EQ(resource_id, quad_new->resource_id);
+ EXPECT_EQ(1, IterateAndCount(quad_new.get()));
+ EXPECT_EQ(resource_id + 1, quad_new->resource_id);
+}
+
+TEST_F(DrawQuadIteratorTest, TileDrawQuad) {
+ gfx::Rect opaque_rect(33, 44, 22, 33);
+ unsigned resource_id = 104;
+ gfx::RectF tex_coord_rect(31.f, 12.f, 54.f, 20.f);
+ gfx::Size texture_size(85, 32);
+ bool swizzle_contents = true;
+
+ CREATE_SHARED_STATE();
+ CREATE_QUAD_5_NEW(TileDrawQuad,
+ opaque_rect,
+ resource_id,
+ tex_coord_rect,
+ texture_size,
+ swizzle_contents);
+ EXPECT_EQ(resource_id, quad_new->resource_id);
+ EXPECT_EQ(1, IterateAndCount(quad_new.get()));
+ EXPECT_EQ(resource_id + 1, quad_new->resource_id);
+}
+
+TEST_F(DrawQuadIteratorTest, YUVVideoDrawQuad) {
+ gfx::Rect opaque_rect(3, 7, 10, 12);
+ gfx::SizeF tex_scale(0.75f, 0.5f);
+ ResourceProvider::ResourceId y_plane_resource_id = 45;
+ ResourceProvider::ResourceId u_plane_resource_id = 532;
+ ResourceProvider::ResourceId v_plane_resource_id = 4;
+ ResourceProvider::ResourceId a_plane_resource_id = 63;
+
+ CREATE_SHARED_STATE();
+ CREATE_QUAD_6_NEW(YUVVideoDrawQuad,
+ opaque_rect,
+ tex_scale,
+ y_plane_resource_id,
+ u_plane_resource_id,
+ v_plane_resource_id,
+ a_plane_resource_id);
+ EXPECT_EQ(DrawQuad::YUV_VIDEO_CONTENT, copy_quad->material);
+ EXPECT_EQ(y_plane_resource_id, quad_new->y_plane_resource_id);
+ EXPECT_EQ(u_plane_resource_id, quad_new->u_plane_resource_id);
+ EXPECT_EQ(v_plane_resource_id, quad_new->v_plane_resource_id);
+ EXPECT_EQ(a_plane_resource_id, quad_new->a_plane_resource_id);
+ EXPECT_EQ(4, IterateAndCount(quad_new.get()));
+ EXPECT_EQ(y_plane_resource_id + 1, quad_new->y_plane_resource_id);
+ EXPECT_EQ(u_plane_resource_id + 1, quad_new->u_plane_resource_id);
+ EXPECT_EQ(v_plane_resource_id + 1, quad_new->v_plane_resource_id);
+ EXPECT_EQ(a_plane_resource_id + 1, quad_new->a_plane_resource_id);
+}
+
+// Disabled until picture draw quad is supported for ubercomp: crbug.com/231715
+TEST_F(DrawQuadIteratorTest, DISABLED_PictureDrawQuad) {
+ gfx::Rect opaque_rect(33, 44, 22, 33);
+ gfx::RectF tex_coord_rect(31.f, 12.f, 54.f, 20.f);
+ gfx::Size texture_size(85, 32);
+ bool swizzle_contents = true;
+ gfx::Rect content_rect(30, 40, 20, 30);
+ float contents_scale = 3.141592f;
+ bool can_draw_direct_to_backbuffer = true;
+ scoped_refptr<PicturePileImpl> picture_pile = PicturePileImpl::Create();
+
+ CREATE_SHARED_STATE();
+ CREATE_QUAD_8_NEW(PictureDrawQuad,
+ opaque_rect,
+ tex_coord_rect,
+ texture_size,
+ swizzle_contents,
+ content_rect,
+ contents_scale,
+ can_draw_direct_to_backbuffer,
+ picture_pile);
+ EXPECT_EQ(0, IterateAndCount(quad_new.get()));
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/quads/io_surface_draw_quad.cc b/chromium/cc/quads/io_surface_draw_quad.cc
new file mode 100644
index 00000000000..2c986dd3a23
--- /dev/null
+++ b/chromium/cc/quads/io_surface_draw_quad.cc
@@ -0,0 +1,79 @@
+// 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 "cc/quads/io_surface_draw_quad.h"
+
+#include "base/logging.h"
+#include "base/values.h"
+#include "cc/base/math_util.h"
+
+namespace cc {
+
+IOSurfaceDrawQuad::IOSurfaceDrawQuad()
+ : io_surface_resource_id(0),
+ orientation(FLIPPED) {
+}
+
+scoped_ptr<IOSurfaceDrawQuad> IOSurfaceDrawQuad::Create() {
+ return make_scoped_ptr(new IOSurfaceDrawQuad);
+}
+
+void IOSurfaceDrawQuad::SetNew(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::Size io_surface_size,
+ unsigned io_surface_resource_id,
+ Orientation orientation) {
+ gfx::Rect visible_rect = rect;
+ bool needs_blending = false;
+ DrawQuad::SetAll(shared_quad_state, DrawQuad::IO_SURFACE_CONTENT, rect,
+ opaque_rect, visible_rect, needs_blending);
+ this->io_surface_size = io_surface_size;
+ this->io_surface_resource_id = io_surface_resource_id;
+ this->orientation = orientation;
+}
+
+void IOSurfaceDrawQuad::SetAll(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::Rect visible_rect,
+ bool needs_blending,
+ gfx::Size io_surface_size,
+ unsigned io_surface_resource_id,
+ Orientation orientation) {
+ DrawQuad::SetAll(shared_quad_state, DrawQuad::IO_SURFACE_CONTENT, rect,
+ opaque_rect, visible_rect, needs_blending);
+ this->io_surface_size = io_surface_size;
+ this->io_surface_resource_id = io_surface_resource_id;
+ this->orientation = orientation;
+}
+
+void IOSurfaceDrawQuad::IterateResources(
+ const ResourceIteratorCallback& callback) {
+ io_surface_resource_id = callback.Run(io_surface_resource_id);
+}
+
+const IOSurfaceDrawQuad* IOSurfaceDrawQuad::MaterialCast(
+ const DrawQuad* quad) {
+ DCHECK(quad->material == DrawQuad::IO_SURFACE_CONTENT);
+ return static_cast<const IOSurfaceDrawQuad*>(quad);
+}
+
+void IOSurfaceDrawQuad::ExtendValue(base::DictionaryValue* value) const {
+ value->Set("io_surface_size", MathUtil::AsValue(io_surface_size).release());
+ value->SetInteger("io_surface_resource_id", io_surface_resource_id);
+ const char* orientation_string = NULL;
+ switch (orientation) {
+ case FLIPPED:
+ orientation_string = "flipped";
+ break;
+ case UNFLIPPED:
+ orientation_string = "unflipped";
+ break;
+ }
+
+ value->SetString("orientation", orientation_string);
+}
+
+} // namespace cc
diff --git a/chromium/cc/quads/io_surface_draw_quad.h b/chromium/cc/quads/io_surface_draw_quad.h
new file mode 100644
index 00000000000..fc8b5019402
--- /dev/null
+++ b/chromium/cc/quads/io_surface_draw_quad.h
@@ -0,0 +1,56 @@
+// 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.
+
+#ifndef CC_QUADS_IO_SURFACE_DRAW_QUAD_H_
+#define CC_QUADS_IO_SURFACE_DRAW_QUAD_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/quads/draw_quad.h"
+#include "ui/gfx/size.h"
+
+namespace cc {
+
+class CC_EXPORT IOSurfaceDrawQuad : public DrawQuad {
+ public:
+ enum Orientation {
+ FLIPPED,
+ UNFLIPPED
+ };
+
+ static scoped_ptr<IOSurfaceDrawQuad> Create();
+
+ void SetNew(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::Size io_surface_size,
+ unsigned io_surface_resource_id,
+ Orientation orientation);
+
+ void SetAll(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::Rect visible_rect,
+ bool needs_blending,
+ gfx::Size io_surface_size,
+ unsigned io_surface_resource_id,
+ Orientation orientation);
+
+ gfx::Size io_surface_size;
+ unsigned io_surface_resource_id;
+ Orientation orientation;
+
+ virtual void IterateResources(const ResourceIteratorCallback& callback)
+ OVERRIDE;
+
+ static const IOSurfaceDrawQuad* MaterialCast(const DrawQuad*);
+
+ private:
+ IOSurfaceDrawQuad();
+ virtual void ExtendValue(base::DictionaryValue* value) const OVERRIDE;
+};
+
+} // namespace cc
+
+#endif // CC_QUADS_IO_SURFACE_DRAW_QUAD_H_
diff --git a/chromium/cc/quads/picture_draw_quad.cc b/chromium/cc/quads/picture_draw_quad.cc
new file mode 100644
index 00000000000..0494764d8aa
--- /dev/null
+++ b/chromium/cc/quads/picture_draw_quad.cc
@@ -0,0 +1,83 @@
+// Copyright 2013 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 "cc/quads/picture_draw_quad.h"
+
+#include "base/values.h"
+#include "cc/base/math_util.h"
+
+namespace cc {
+
+PictureDrawQuad::PictureDrawQuad() {
+}
+
+PictureDrawQuad::~PictureDrawQuad() {
+}
+
+scoped_ptr<PictureDrawQuad> PictureDrawQuad::Create() {
+ return make_scoped_ptr(new PictureDrawQuad);
+}
+
+void PictureDrawQuad::SetNew(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ const gfx::RectF& tex_coord_rect,
+ gfx::Size texture_size,
+ bool swizzle_contents,
+ gfx::Rect content_rect,
+ float contents_scale,
+ bool can_draw_direct_to_backbuffer,
+ scoped_refptr<PicturePileImpl> picture_pile) {
+ ContentDrawQuadBase::SetNew(shared_quad_state, DrawQuad::PICTURE_CONTENT,
+ rect, opaque_rect, tex_coord_rect, texture_size,
+ swizzle_contents);
+ this->content_rect = content_rect;
+ this->contents_scale = contents_scale;
+ this->can_draw_direct_to_backbuffer = can_draw_direct_to_backbuffer;
+ this->picture_pile = picture_pile;
+}
+
+void PictureDrawQuad::SetAll(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::Rect visible_rect,
+ bool needs_blending,
+ const gfx::RectF& tex_coord_rect,
+ gfx::Size texture_size,
+ bool swizzle_contents,
+ gfx::Rect content_rect,
+ float contents_scale,
+ bool can_draw_direct_to_backbuffer,
+ scoped_refptr<PicturePileImpl> picture_pile) {
+ ContentDrawQuadBase::SetAll(shared_quad_state,
+ DrawQuad::PICTURE_CONTENT, rect, opaque_rect,
+ visible_rect, needs_blending, tex_coord_rect,
+ texture_size, swizzle_contents);
+ this->content_rect = content_rect;
+ this->contents_scale = contents_scale;
+ this->can_draw_direct_to_backbuffer = can_draw_direct_to_backbuffer;
+ this->picture_pile = picture_pile;
+}
+
+void PictureDrawQuad::IterateResources(
+ const ResourceIteratorCallback& callback) {
+ // TODO(danakj): Convert to TextureDrawQuad?
+ NOTIMPLEMENTED();
+}
+
+const PictureDrawQuad* PictureDrawQuad::MaterialCast(const DrawQuad* quad) {
+ DCHECK(quad->material == DrawQuad::PICTURE_CONTENT);
+ return static_cast<const PictureDrawQuad*>(quad);
+}
+
+void PictureDrawQuad::ExtendValue(base::DictionaryValue* value) const {
+ ContentDrawQuadBase::ExtendValue(value);
+ value->Set("content_rect", MathUtil::AsValue(content_rect).release());
+ value->SetDouble("contents_scale", contents_scale);
+ value->SetBoolean("can_draw_direct_to_backbuffer",
+ can_draw_direct_to_backbuffer);
+ // TODO(piman): picture_pile?
+}
+
+} // namespace cc
diff --git a/chromium/cc/quads/picture_draw_quad.h b/chromium/cc/quads/picture_draw_quad.h
new file mode 100644
index 00000000000..beec88ce3f3
--- /dev/null
+++ b/chromium/cc/quads/picture_draw_quad.h
@@ -0,0 +1,66 @@
+// Copyright 2013 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.
+
+#ifndef CC_QUADS_PICTURE_DRAW_QUAD_H_
+#define CC_QUADS_PICTURE_DRAW_QUAD_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/quads/content_draw_quad_base.h"
+#include "cc/resources/picture_pile_impl.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/rect_f.h"
+#include "ui/gfx/size.h"
+
+namespace cc {
+
+// Used for on-demand tile rasterization.
+class CC_EXPORT PictureDrawQuad : public ContentDrawQuadBase {
+ public:
+ static scoped_ptr<PictureDrawQuad> Create();
+ virtual ~PictureDrawQuad();
+
+ void SetNew(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ const gfx::RectF& tex_coord_rect,
+ gfx::Size texture_size,
+ bool swizzle_contents,
+ gfx::Rect content_rect,
+ float contents_scale,
+ bool can_draw_direct_to_backbuffer,
+ scoped_refptr<PicturePileImpl> picture_pile);
+
+ void SetAll(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::Rect visible_rect,
+ bool needs_blending,
+ const gfx::RectF& tex_coord_rect,
+ gfx::Size texture_size,
+ bool swizzle_contents,
+ gfx::Rect content_rect,
+ float contents_scale,
+ bool can_draw_direct_to_backbuffer,
+ scoped_refptr<PicturePileImpl> picture_pile);
+
+ gfx::Rect content_rect;
+ float contents_scale;
+ bool can_draw_direct_to_backbuffer;
+ scoped_refptr<PicturePileImpl> picture_pile;
+
+ virtual void IterateResources(const ResourceIteratorCallback& callback)
+ OVERRIDE;
+
+ static const PictureDrawQuad* MaterialCast(const DrawQuad* quad);
+
+ private:
+ PictureDrawQuad();
+ virtual void ExtendValue(base::DictionaryValue* value) const OVERRIDE;
+};
+
+} // namespace cc
+
+#endif // CC_QUADS_PICTURE_DRAW_QUAD_H_
diff --git a/chromium/cc/quads/render_pass.cc b/chromium/cc/quads/render_pass.cc
new file mode 100644
index 00000000000..50d502884b4
--- /dev/null
+++ b/chromium/cc/quads/render_pass.cc
@@ -0,0 +1,109 @@
+// Copyright 2011 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 "cc/quads/render_pass.h"
+
+#include "base/values.h"
+#include "cc/base/math_util.h"
+#include "cc/debug/traced_value.h"
+#include "cc/output/copy_output_request.h"
+#include "cc/quads/draw_quad.h"
+#include "cc/quads/shared_quad_state.h"
+
+namespace cc {
+
+void* RenderPass::Id::AsTracingId() const {
+ COMPILE_ASSERT(sizeof(size_t) <= sizeof(void*), size_t_bigger_than_pointer);
+ return reinterpret_cast<void*>(base::HashPair(layer_id, index));
+}
+
+scoped_ptr<RenderPass> RenderPass::Create() {
+ return make_scoped_ptr(new RenderPass);
+}
+
+RenderPass::RenderPass()
+ : id(Id(-1, -1)),
+ has_transparent_background(true),
+ has_occlusion_from_outside_target_surface(false) {}
+
+RenderPass::~RenderPass() {
+ TRACE_EVENT_OBJECT_DELETED_WITH_ID(
+ TRACE_DISABLED_BY_DEFAULT("cc.debug.quads"),
+ "cc::RenderPass", id.AsTracingId());
+}
+
+scoped_ptr<RenderPass> RenderPass::Copy(Id new_id) const {
+ scoped_ptr<RenderPass> copy_pass(Create());
+ copy_pass->SetAll(new_id,
+ output_rect,
+ damage_rect,
+ transform_to_root_target,
+ has_transparent_background,
+ has_occlusion_from_outside_target_surface);
+ return copy_pass.Pass();
+}
+
+void RenderPass::SetNew(Id id,
+ gfx::Rect output_rect,
+ gfx::RectF damage_rect,
+ const gfx::Transform& transform_to_root_target) {
+ DCHECK_GT(id.layer_id, 0);
+ DCHECK_GE(id.index, 0);
+
+ this->id = id;
+ this->output_rect = output_rect;
+ this->damage_rect = damage_rect;
+ this->transform_to_root_target = transform_to_root_target;
+
+ DCHECK(quad_list.empty());
+ DCHECK(shared_quad_state_list.empty());
+}
+
+void RenderPass::SetAll(Id id,
+ gfx::Rect output_rect,
+ gfx::RectF damage_rect,
+ const gfx::Transform& transform_to_root_target,
+ bool has_transparent_background,
+ bool has_occlusion_from_outside_target_surface) {
+ DCHECK_GT(id.layer_id, 0);
+ DCHECK_GE(id.index, 0);
+
+ this->id = id;
+ this->output_rect = output_rect;
+ this->damage_rect = damage_rect;
+ this->transform_to_root_target = transform_to_root_target;
+ this->has_transparent_background = has_transparent_background;
+ this->has_occlusion_from_outside_target_surface =
+ has_occlusion_from_outside_target_surface;
+
+ DCHECK(quad_list.empty());
+ DCHECK(shared_quad_state_list.empty());
+}
+
+scoped_ptr<base::Value> RenderPass::AsValue() const {
+ scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue());
+ value->Set("output_rect", MathUtil::AsValue(output_rect).release());
+ value->Set("damage_rect", MathUtil::AsValue(damage_rect).release());
+ value->SetBoolean("has_transparent_background", has_transparent_background);
+ value->SetBoolean("has_occlusion_from_outside_target_surface",
+ has_occlusion_from_outside_target_surface);
+ value->SetInteger("copy_requests", copy_requests.size());
+ scoped_ptr<base::ListValue> shared_states_value(new base::ListValue());
+ for (size_t i = 0; i < shared_quad_state_list.size(); ++i) {
+ shared_states_value->Append(shared_quad_state_list[i]->AsValue().release());
+ }
+ value->Set("shared_quad_state_list", shared_states_value.release());
+ scoped_ptr<base::ListValue> quad_list_value(new base::ListValue());
+ for (size_t i = 0; i < quad_list.size(); ++i) {
+ quad_list_value->Append(quad_list[i]->AsValue().release());
+ }
+ value->Set("quad_list", quad_list_value.release());
+
+ TracedValue::MakeDictIntoImplicitSnapshotWithCategory(
+ TRACE_DISABLED_BY_DEFAULT("cc.debug.quads"),
+ value.get(), "cc::RenderPass", id.AsTracingId());
+ return value.PassAs<base::Value>();
+}
+
+} // namespace cc
diff --git a/chromium/cc/quads/render_pass.h b/chromium/cc/quads/render_pass.h
new file mode 100644
index 00000000000..224a0506f03
--- /dev/null
+++ b/chromium/cc/quads/render_pass.h
@@ -0,0 +1,147 @@
+// Copyright 2011 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.
+
+#ifndef CC_QUADS_RENDER_PASS_H_
+#define CC_QUADS_RENDER_PASS_H_
+
+#include <utility>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/containers/hash_tables.h"
+#include "cc/base/cc_export.h"
+#include "cc/base/scoped_ptr_hash_map.h"
+#include "cc/base/scoped_ptr_vector.h"
+#include "skia/ext/refptr.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/skia/include/core/SkImageFilter.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/rect_f.h"
+#include "ui/gfx/transform.h"
+
+namespace base {
+class Value;
+};
+
+namespace cc {
+
+class DrawQuad;
+class CopyOutputRequest;
+class SharedQuadState;
+
+// A list of DrawQuad objects, sorted internally in front-to-back order.
+class QuadList : public ScopedPtrVector<DrawQuad> {
+ public:
+ typedef reverse_iterator BackToFrontIterator;
+ typedef const_reverse_iterator ConstBackToFrontIterator;
+
+ inline BackToFrontIterator BackToFrontBegin() { return rbegin(); }
+ inline BackToFrontIterator BackToFrontEnd() { return rend(); }
+ inline ConstBackToFrontIterator BackToFrontBegin() const { return rbegin(); }
+ inline ConstBackToFrontIterator BackToFrontEnd() const { return rend(); }
+};
+
+typedef ScopedPtrVector<SharedQuadState> SharedQuadStateList;
+
+class CC_EXPORT RenderPass {
+ public:
+ struct Id {
+ int layer_id;
+ int index;
+
+ Id(int layer_id, int index) : layer_id(layer_id), index(index) {}
+ void* AsTracingId() const;
+
+ bool operator==(const Id& other) const {
+ return layer_id == other.layer_id && index == other.index;
+ }
+ bool operator!=(const Id& other) const {
+ return !(*this == other);
+ }
+ bool operator<(const Id& other) const {
+ return layer_id < other.layer_id ||
+ (layer_id == other.layer_id && index < other.index);
+ }
+ };
+
+ ~RenderPass();
+
+ static scoped_ptr<RenderPass> Create();
+
+ // A shallow copy of the render pass, which does not include its quads.
+ scoped_ptr<RenderPass> Copy(Id new_id) const;
+
+ void SetNew(Id id,
+ gfx::Rect output_rect,
+ gfx::RectF damage_rect,
+ const gfx::Transform& transform_to_root_target);
+
+ void SetAll(Id id,
+ gfx::Rect output_rect,
+ gfx::RectF damage_rect,
+ const gfx::Transform& transform_to_root_target,
+ bool has_transparent_background,
+ bool has_occlusion_from_outside_target_surface);
+
+ scoped_ptr<base::Value> AsValue() const;
+
+ // Uniquely identifies the render pass in the compositor's current frame.
+ Id id;
+
+ // These are in the space of the render pass' physical pixels.
+ gfx::Rect output_rect;
+ gfx::RectF damage_rect;
+
+ // Transforms from the origin of the |output_rect| to the origin of the root
+ // render pass' |output_rect|.
+ gfx::Transform transform_to_root_target;
+
+ // If false, the pixels in the render pass' texture are all opaque.
+ bool has_transparent_background;
+
+ // If true, then there may be pixels in the render pass' texture that are not
+ // complete, since they are occluded.
+ bool has_occlusion_from_outside_target_surface;
+
+ // If non-empty, the renderer should produce a copy of the render pass'
+ // contents as a bitmap, and give a copy of the bitmap to each callback in
+ // this list. This property should not be serialized between compositors, as
+ // it only makes sense in the root compositor.
+ ScopedPtrVector<CopyOutputRequest> copy_requests;
+
+ QuadList quad_list;
+ SharedQuadStateList shared_quad_state_list;
+
+ protected:
+ RenderPass();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RenderPass);
+};
+
+} // namespace cc
+
+namespace BASE_HASH_NAMESPACE {
+#if defined(COMPILER_MSVC)
+inline size_t hash_value(const cc::RenderPass::Id& key) {
+ return base::HashPair(key.layer_id, key.index);
+}
+#elif defined(COMPILER_GCC)
+template<>
+struct hash<cc::RenderPass::Id> {
+ size_t operator()(cc::RenderPass::Id key) const {
+ return base::HashPair(key.layer_id, key.index);
+ }
+};
+#else
+#error define a hash function for your compiler
+#endif // COMPILER
+} // namespace BASE_HASH_NAMESPACE
+
+namespace cc {
+typedef ScopedPtrVector<RenderPass> RenderPassList;
+typedef base::hash_map<RenderPass::Id, RenderPass*> RenderPassIdHashMap;
+} // namespace cc
+
+#endif // CC_QUADS_RENDER_PASS_H_
diff --git a/chromium/cc/quads/render_pass_draw_quad.cc b/chromium/cc/quads/render_pass_draw_quad.cc
new file mode 100644
index 00000000000..0528cd5e34f
--- /dev/null
+++ b/chromium/cc/quads/render_pass_draw_quad.cc
@@ -0,0 +1,115 @@
+// Copyright 2011 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 "cc/quads/render_pass_draw_quad.h"
+
+#include "base/values.h"
+#include "cc/base/math_util.h"
+#include "cc/debug/traced_value.h"
+
+namespace cc {
+
+RenderPassDrawQuad::RenderPassDrawQuad()
+ : render_pass_id(RenderPass::Id(-1, -1)),
+ is_replica(false),
+ mask_resource_id(-1) {
+}
+
+RenderPassDrawQuad::~RenderPassDrawQuad() {
+}
+
+scoped_ptr<RenderPassDrawQuad> RenderPassDrawQuad::Create() {
+ return make_scoped_ptr(new RenderPassDrawQuad);
+}
+
+scoped_ptr<RenderPassDrawQuad> RenderPassDrawQuad::Copy(
+ const SharedQuadState* copied_shared_quad_state,
+ RenderPass::Id copied_render_pass_id) const {
+ scoped_ptr<RenderPassDrawQuad> copy_quad(
+ new RenderPassDrawQuad(*MaterialCast(this)));
+ copy_quad->shared_quad_state = copied_shared_quad_state;
+ copy_quad->render_pass_id = copied_render_pass_id;
+ return copy_quad.Pass();
+}
+
+void RenderPassDrawQuad::SetNew(
+ const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ RenderPass::Id render_pass_id,
+ bool is_replica,
+ ResourceProvider::ResourceId mask_resource_id,
+ gfx::Rect contents_changed_since_last_frame,
+ gfx::RectF mask_uv_rect,
+ const FilterOperations& filters,
+ skia::RefPtr<SkImageFilter> filter,
+ const FilterOperations& background_filters) {
+ DCHECK_GT(render_pass_id.layer_id, 0);
+ DCHECK_GE(render_pass_id.index, 0);
+
+ gfx::Rect opaque_rect;
+ gfx::Rect visible_rect = rect;
+ bool needs_blending = false;
+ SetAll(shared_quad_state, rect, opaque_rect, visible_rect, needs_blending,
+ render_pass_id, is_replica, mask_resource_id,
+ contents_changed_since_last_frame, mask_uv_rect, filters, filter,
+ background_filters);
+}
+
+void RenderPassDrawQuad::SetAll(
+ const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::Rect visible_rect,
+ bool needs_blending,
+ RenderPass::Id render_pass_id,
+ bool is_replica,
+ ResourceProvider::ResourceId mask_resource_id,
+ gfx::Rect contents_changed_since_last_frame,
+ gfx::RectF mask_uv_rect,
+ const FilterOperations& filters,
+ skia::RefPtr<SkImageFilter> filter,
+ const FilterOperations& background_filters) {
+ DCHECK_GT(render_pass_id.layer_id, 0);
+ DCHECK_GE(render_pass_id.index, 0);
+
+ DrawQuad::SetAll(shared_quad_state, DrawQuad::RENDER_PASS, rect, opaque_rect,
+ visible_rect, needs_blending);
+ this->render_pass_id = render_pass_id;
+ this->is_replica = is_replica;
+ this->mask_resource_id = mask_resource_id;
+ this->contents_changed_since_last_frame = contents_changed_since_last_frame;
+ this->mask_uv_rect = mask_uv_rect;
+ this->filters = filters;
+ this->filter = filter;
+ this->background_filters = background_filters;
+}
+
+void RenderPassDrawQuad::IterateResources(
+ const ResourceIteratorCallback& callback) {
+ if (mask_resource_id)
+ mask_resource_id = callback.Run(mask_resource_id);
+}
+
+const RenderPassDrawQuad* RenderPassDrawQuad::MaterialCast(
+ const DrawQuad* quad) {
+ DCHECK_EQ(quad->material, DrawQuad::RENDER_PASS);
+ return static_cast<const RenderPassDrawQuad*>(quad);
+}
+
+void RenderPassDrawQuad::ExtendValue(base::DictionaryValue* value) const {
+ value->Set("render_pass_id",
+ TracedValue::CreateIDRef(render_pass_id.AsTracingId()).release());
+ value->SetBoolean("is_replica", is_replica);
+ value->SetInteger("mask_resource_id", mask_resource_id);
+ value->Set("contents_changed_since_last_frame",
+ MathUtil::AsValue(contents_changed_since_last_frame).release());
+ value->Set("mask_uv_rect", MathUtil::AsValue(mask_uv_rect).release());
+ value->Set("filters", filters.AsValue().release());
+ // TODO(piman): dump SkImageFilters rather than just indicating if there are
+ // any or not.
+ value->SetBoolean("has_filter", !!filter);
+ value->Set("background_filters", background_filters.AsValue().release());
+}
+
+} // namespace cc
diff --git a/chromium/cc/quads/render_pass_draw_quad.h b/chromium/cc/quads/render_pass_draw_quad.h
new file mode 100644
index 00000000000..e253dec430b
--- /dev/null
+++ b/chromium/cc/quads/render_pass_draw_quad.h
@@ -0,0 +1,80 @@
+// Copyright 2011 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.
+
+#ifndef CC_QUADS_RENDER_PASS_DRAW_QUAD_H_
+#define CC_QUADS_RENDER_PASS_DRAW_QUAD_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/output/filter_operations.h"
+#include "cc/quads/draw_quad.h"
+#include "cc/quads/render_pass.h"
+#include "cc/resources/resource_provider.h"
+
+namespace cc {
+
+class CC_EXPORT RenderPassDrawQuad : public DrawQuad {
+ public:
+ static scoped_ptr<RenderPassDrawQuad> Create();
+ virtual ~RenderPassDrawQuad();
+
+ void SetNew(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ RenderPass::Id render_pass_id,
+ bool is_replica,
+ ResourceProvider::ResourceId mask_resource_id,
+ gfx::Rect contents_changed_since_last_frame,
+ gfx::RectF mask_uv_rect,
+ const FilterOperations& filters,
+ skia::RefPtr<SkImageFilter> filter,
+ const FilterOperations& background_filters);
+
+ void SetAll(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::Rect visible_rect,
+ bool needs_blending,
+ RenderPass::Id render_pass_id,
+ bool is_replica,
+ ResourceProvider::ResourceId mask_resource_id,
+ gfx::Rect contents_changed_since_last_frame,
+ gfx::RectF mask_uv_rect,
+ const FilterOperations& filters,
+ skia::RefPtr<SkImageFilter> filter,
+ const FilterOperations& background_filters);
+
+ scoped_ptr<RenderPassDrawQuad> Copy(
+ const SharedQuadState* copied_shared_quad_state,
+ RenderPass::Id copied_render_pass_id) const;
+
+ RenderPass::Id render_pass_id;
+ bool is_replica;
+ ResourceProvider::ResourceId mask_resource_id;
+ gfx::Rect contents_changed_since_last_frame;
+ gfx::RectF mask_uv_rect;
+
+ // Deprecated post-processing filters, applied to the pixels in the render
+ // pass' texture.
+ FilterOperations filters;
+ // Post-processing filter applied to the pixels in the render pass' texture.
+ skia::RefPtr<SkImageFilter> filter;
+
+ // Post-processing filters, applied to the pixels showing through the
+ // background of the render pass, from behind it.
+ FilterOperations background_filters;
+
+ virtual void IterateResources(const ResourceIteratorCallback& callback)
+ OVERRIDE;
+
+ static const RenderPassDrawQuad* MaterialCast(const DrawQuad*);
+
+ private:
+ RenderPassDrawQuad();
+ virtual void ExtendValue(base::DictionaryValue* value) const OVERRIDE;
+};
+
+} // namespace cc
+
+#endif // CC_QUADS_RENDER_PASS_DRAW_QUAD_H_
diff --git a/chromium/cc/quads/render_pass_unittest.cc b/chromium/cc/quads/render_pass_unittest.cc
new file mode 100644
index 00000000000..b12faa89840
--- /dev/null
+++ b/chromium/cc/quads/render_pass_unittest.cc
@@ -0,0 +1,86 @@
+// 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 "cc/quads/render_pass.h"
+
+#include "cc/base/math_util.h"
+#include "cc/base/scoped_ptr_vector.h"
+#include "cc/output/copy_output_request.h"
+#include "cc/quads/checkerboard_draw_quad.h"
+#include "cc/test/geometry_test_utils.h"
+#include "cc/test/render_pass_test_common.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/effects/SkBlurImageFilter.h"
+#include "ui/gfx/transform.h"
+
+using cc::TestRenderPass;
+
+namespace cc {
+namespace {
+
+struct RenderPassSize {
+ // If you add a new field to this class, make sure to add it to the
+ // Copy() tests.
+ RenderPass::Id id;
+ QuadList quad_list;
+ SharedQuadStateList shared_quad_state_list;
+ gfx::Transform transform_to_root_target;
+ gfx::Rect output_rect;
+ gfx::RectF damage_rect;
+ bool has_transparent_background;
+ bool has_occlusion_from_outside_target_surface;
+ ScopedPtrVector<CopyOutputRequest> copy_callbacks;
+};
+
+TEST(RenderPassTest, CopyShouldBeIdenticalExceptIdAndQuads) {
+ RenderPass::Id id(3, 2);
+ gfx::Rect output_rect(45, 22, 120, 13);
+ gfx::Transform transform_to_root =
+ gfx::Transform(1.0, 0.5, 0.5, -0.5, -1.0, 0.0);
+ gfx::Rect damage_rect(56, 123, 19, 43);
+ bool has_transparent_background = true;
+ bool has_occlusion_from_outside_target_surface = true;
+
+ scoped_ptr<TestRenderPass> pass = TestRenderPass::Create();
+ pass->SetAll(id,
+ output_rect,
+ damage_rect,
+ transform_to_root,
+ has_transparent_background,
+ has_occlusion_from_outside_target_surface);
+ pass->copy_requests.push_back(CopyOutputRequest::CreateEmptyRequest());
+
+ // Stick a quad in the pass, this should not get copied.
+ scoped_ptr<SharedQuadState> shared_state = SharedQuadState::Create();
+ shared_state->SetAll(
+ gfx::Transform(), gfx::Size(), gfx::Rect(), gfx::Rect(), false, 1);
+ pass->AppendSharedQuadState(shared_state.Pass());
+
+ scoped_ptr<CheckerboardDrawQuad> checkerboard_quad =
+ CheckerboardDrawQuad::Create();
+ checkerboard_quad->SetNew(
+ pass->shared_quad_state_list.back(), gfx::Rect(), SkColor());
+ pass->quad_list.push_back(checkerboard_quad.PassAs<DrawQuad>());
+
+ RenderPass::Id new_id(63, 4);
+
+ scoped_ptr<RenderPass> copy = pass->Copy(new_id);
+ EXPECT_EQ(new_id, copy->id);
+ EXPECT_RECT_EQ(pass->output_rect, copy->output_rect);
+ EXPECT_EQ(pass->transform_to_root_target, copy->transform_to_root_target);
+ EXPECT_RECT_EQ(pass->damage_rect, copy->damage_rect);
+ EXPECT_EQ(pass->has_transparent_background, copy->has_transparent_background);
+ EXPECT_EQ(pass->has_occlusion_from_outside_target_surface,
+ copy->has_occlusion_from_outside_target_surface);
+ EXPECT_EQ(0u, copy->quad_list.size());
+
+ // The copy request should not be copied/duplicated.
+ EXPECT_EQ(1u, pass->copy_requests.size());
+ EXPECT_EQ(0u, copy->copy_requests.size());
+
+ EXPECT_EQ(sizeof(RenderPassSize), sizeof(RenderPass));
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/quads/shared_quad_state.cc b/chromium/cc/quads/shared_quad_state.cc
new file mode 100644
index 00000000000..6a53d9f6a9f
--- /dev/null
+++ b/chromium/cc/quads/shared_quad_state.cc
@@ -0,0 +1,61 @@
+// 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 "cc/quads/shared_quad_state.h"
+
+#include "base/values.h"
+#include "cc/base/math_util.h"
+#include "cc/debug/traced_value.h"
+
+namespace cc {
+
+SharedQuadState::SharedQuadState() : is_clipped(false), opacity(0.f) {}
+
+SharedQuadState::~SharedQuadState() {
+ TRACE_EVENT_OBJECT_DELETED_WITH_ID(
+ TRACE_DISABLED_BY_DEFAULT("cc.debug.quads"),
+ "cc::SharedQuadState", this);
+}
+
+scoped_ptr<SharedQuadState> SharedQuadState::Create() {
+ return make_scoped_ptr(new SharedQuadState);
+}
+
+scoped_ptr<SharedQuadState> SharedQuadState::Copy() const {
+ return make_scoped_ptr(new SharedQuadState(*this));
+}
+
+void SharedQuadState::SetAll(
+ const gfx::Transform& content_to_target_transform,
+ gfx::Size content_bounds,
+ gfx::Rect visible_content_rect,
+ gfx::Rect clip_rect,
+ bool is_clipped,
+ float opacity) {
+ this->content_to_target_transform = content_to_target_transform;
+ this->content_bounds = content_bounds;
+ this->visible_content_rect = visible_content_rect;
+ this->clip_rect = clip_rect;
+ this->is_clipped = is_clipped;
+ this->opacity = opacity;
+}
+
+scoped_ptr<base::Value> SharedQuadState::AsValue() const {
+ scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue());
+ value->Set("transform",
+ MathUtil::AsValue(content_to_target_transform).release());
+ value->Set("layer_content_bounds",
+ MathUtil::AsValue(content_bounds).release());
+ value->Set("layer_visible_content_rect",
+ MathUtil::AsValue(visible_content_rect).release());
+ value->SetBoolean("is_clipped", is_clipped);
+ value->Set("clip_rect", MathUtil::AsValue(clip_rect).release());
+ value->SetDouble("opacity", opacity);
+ TracedValue::MakeDictIntoImplicitSnapshotWithCategory(
+ TRACE_DISABLED_BY_DEFAULT("cc.debug.quads"),
+ value.get(), "cc::SharedQuadState", this);
+ return value.PassAs<base::Value>();
+}
+
+} // namespace cc
diff --git a/chromium/cc/quads/shared_quad_state.h b/chromium/cc/quads/shared_quad_state.h
new file mode 100644
index 00000000000..79bd09b60f7
--- /dev/null
+++ b/chromium/cc/quads/shared_quad_state.h
@@ -0,0 +1,51 @@
+// 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.
+
+#ifndef CC_QUADS_SHARED_QUAD_STATE_H_
+#define CC_QUADS_SHARED_QUAD_STATE_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/transform.h"
+
+namespace base {
+class Value;
+}
+
+namespace cc {
+
+class CC_EXPORT SharedQuadState {
+ public:
+ static scoped_ptr<SharedQuadState> Create();
+ ~SharedQuadState();
+
+ scoped_ptr<SharedQuadState> Copy() const;
+
+ void SetAll(const gfx::Transform& content_to_target_transform,
+ gfx::Size content_bounds,
+ gfx::Rect visible_content_rect,
+ gfx::Rect clip_rect,
+ bool is_clipped,
+ float opacity);
+ scoped_ptr<base::Value> AsValue() const;
+
+ // Transforms from quad's original content space to its target content space.
+ gfx::Transform content_to_target_transform;
+ // This size lives in the content space for the quad's originating layer.
+ gfx::Size content_bounds;
+ // This rect lives in the content space for the quad's originating layer.
+ gfx::Rect visible_content_rect;
+ // This rect lives in the target content space.
+ gfx::Rect clip_rect;
+ bool is_clipped;
+ float opacity;
+
+ private:
+ SharedQuadState();
+};
+
+} // namespace cc
+
+#endif // CC_QUADS_SHARED_QUAD_STATE_H_
diff --git a/chromium/cc/quads/solid_color_draw_quad.cc b/chromium/cc/quads/solid_color_draw_quad.cc
new file mode 100644
index 00000000000..ae3b9142209
--- /dev/null
+++ b/chromium/cc/quads/solid_color_draw_quad.cc
@@ -0,0 +1,59 @@
+// 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 "cc/quads/solid_color_draw_quad.h"
+
+#include "base/logging.h"
+#include "base/values.h"
+
+namespace cc {
+
+SolidColorDrawQuad::SolidColorDrawQuad()
+ : color(0), force_anti_aliasing_off(false) {}
+
+scoped_ptr<SolidColorDrawQuad> SolidColorDrawQuad::Create() {
+ return make_scoped_ptr(new SolidColorDrawQuad);
+}
+
+void SolidColorDrawQuad::SetNew(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ SkColor color,
+ bool force_anti_aliasing_off) {
+ gfx::Rect opaque_rect = SkColorGetA(color) == 255 ? rect : gfx::Rect();
+ gfx::Rect visible_rect = rect;
+ bool needs_blending = false;
+ DrawQuad::SetAll(shared_quad_state, DrawQuad::SOLID_COLOR, rect, opaque_rect,
+ visible_rect, needs_blending);
+ this->color = color;
+ this->force_anti_aliasing_off = force_anti_aliasing_off;
+}
+
+void SolidColorDrawQuad::SetAll(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::Rect visible_rect,
+ bool needs_blending,
+ SkColor color,
+ bool force_anti_aliasing_off) {
+ DrawQuad::SetAll(shared_quad_state, DrawQuad::SOLID_COLOR, rect, opaque_rect,
+ visible_rect, needs_blending);
+ this->color = color;
+ this->force_anti_aliasing_off = force_anti_aliasing_off;
+}
+
+void SolidColorDrawQuad::IterateResources(
+ const ResourceIteratorCallback& callback) {}
+
+const SolidColorDrawQuad* SolidColorDrawQuad::MaterialCast(
+ const DrawQuad* quad) {
+ DCHECK(quad->material == DrawQuad::SOLID_COLOR);
+ return static_cast<const SolidColorDrawQuad*>(quad);
+}
+
+void SolidColorDrawQuad::ExtendValue(base::DictionaryValue* value) const {
+ value->SetInteger("color", color);
+ value->SetBoolean("force_anti_aliasing_off", force_anti_aliasing_off);
+}
+
+} // namespace cc
diff --git a/chromium/cc/quads/solid_color_draw_quad.h b/chromium/cc/quads/solid_color_draw_quad.h
new file mode 100644
index 00000000000..2c41243409e
--- /dev/null
+++ b/chromium/cc/quads/solid_color_draw_quad.h
@@ -0,0 +1,47 @@
+// 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.
+
+#ifndef CC_QUADS_SOLID_COLOR_DRAW_QUAD_H_
+#define CC_QUADS_SOLID_COLOR_DRAW_QUAD_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/quads/draw_quad.h"
+#include "third_party/skia/include/core/SkColor.h"
+
+namespace cc {
+
+class CC_EXPORT SolidColorDrawQuad : public DrawQuad {
+ public:
+ static scoped_ptr<SolidColorDrawQuad> Create();
+
+ void SetNew(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ SkColor color,
+ bool force_anti_aliasing_off);
+
+ void SetAll(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::Rect visible_rect,
+ bool needs_blending,
+ SkColor color,
+ bool force_anti_aliasing_off);
+
+ SkColor color;
+ bool force_anti_aliasing_off;
+
+ virtual void IterateResources(const ResourceIteratorCallback& callback)
+ OVERRIDE;
+
+ static const SolidColorDrawQuad* MaterialCast(const DrawQuad*);
+
+ private:
+ SolidColorDrawQuad();
+ virtual void ExtendValue(base::DictionaryValue* value) const OVERRIDE;
+};
+
+} // namespace cc
+
+#endif // CC_QUADS_SOLID_COLOR_DRAW_QUAD_H_
diff --git a/chromium/cc/quads/stream_video_draw_quad.cc b/chromium/cc/quads/stream_video_draw_quad.cc
new file mode 100644
index 00000000000..c239256ac51
--- /dev/null
+++ b/chromium/cc/quads/stream_video_draw_quad.cc
@@ -0,0 +1,61 @@
+// 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 "cc/quads/stream_video_draw_quad.h"
+
+#include "base/logging.h"
+#include "base/values.h"
+#include "cc/base/math_util.h"
+
+namespace cc {
+
+StreamVideoDrawQuad::StreamVideoDrawQuad() : resource_id(0) {}
+
+scoped_ptr<StreamVideoDrawQuad> StreamVideoDrawQuad::Create() {
+ return make_scoped_ptr(new StreamVideoDrawQuad);
+}
+
+void StreamVideoDrawQuad::SetNew(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ unsigned resource_id,
+ const gfx::Transform& matrix) {
+ gfx::Rect visible_rect = rect;
+ bool needs_blending = false;
+ DrawQuad::SetAll(shared_quad_state, DrawQuad::STREAM_VIDEO_CONTENT, rect,
+ opaque_rect, visible_rect, needs_blending);
+ this->resource_id = resource_id;
+ this->matrix = matrix;
+}
+
+void StreamVideoDrawQuad::SetAll(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::Rect visible_rect,
+ bool needs_blending,
+ unsigned resource_id,
+ const gfx::Transform& matrix) {
+ DrawQuad::SetAll(shared_quad_state, DrawQuad::STREAM_VIDEO_CONTENT, rect,
+ opaque_rect, visible_rect, needs_blending);
+ this->resource_id = resource_id;
+ this->matrix = matrix;
+}
+
+void StreamVideoDrawQuad::IterateResources(
+ const ResourceIteratorCallback& callback) {
+ resource_id = callback.Run(resource_id);
+}
+
+const StreamVideoDrawQuad* StreamVideoDrawQuad::MaterialCast(
+ const DrawQuad* quad) {
+ DCHECK(quad->material == DrawQuad::STREAM_VIDEO_CONTENT);
+ return static_cast<const StreamVideoDrawQuad*>(quad);
+}
+
+void StreamVideoDrawQuad::ExtendValue(base::DictionaryValue* value) const {
+ value->SetInteger("resource_id", resource_id);
+ value->Set("matrix", MathUtil::AsValue(matrix).release());
+}
+
+} // namespace cc
diff --git a/chromium/cc/quads/stream_video_draw_quad.h b/chromium/cc/quads/stream_video_draw_quad.h
new file mode 100644
index 00000000000..e610f4333ce
--- /dev/null
+++ b/chromium/cc/quads/stream_video_draw_quad.h
@@ -0,0 +1,48 @@
+// 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.
+
+#ifndef CC_QUADS_STREAM_VIDEO_DRAW_QUAD_H_
+#define CC_QUADS_STREAM_VIDEO_DRAW_QUAD_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/quads/draw_quad.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+
+class CC_EXPORT StreamVideoDrawQuad : public DrawQuad {
+ public:
+ static scoped_ptr<StreamVideoDrawQuad> Create();
+
+ void SetNew(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ unsigned resource_id,
+ const gfx::Transform& matrix);
+
+ void SetAll(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::Rect visible_rect,
+ bool needs_blending,
+ unsigned resource_id,
+ const gfx::Transform& matrix);
+
+ unsigned resource_id;
+ gfx::Transform matrix;
+
+ virtual void IterateResources(const ResourceIteratorCallback& callback)
+ OVERRIDE;
+
+ static const StreamVideoDrawQuad* MaterialCast(const DrawQuad*);
+
+ private:
+ StreamVideoDrawQuad();
+ virtual void ExtendValue(base::DictionaryValue* value) const OVERRIDE;
+};
+
+} // namespace cc
+
+#endif // CC_QUADS_STREAM_VIDEO_DRAW_QUAD_H_
diff --git a/chromium/cc/quads/texture_draw_quad.cc b/chromium/cc/quads/texture_draw_quad.cc
new file mode 100644
index 00000000000..ae7cfd6e238
--- /dev/null
+++ b/chromium/cc/quads/texture_draw_quad.cc
@@ -0,0 +1,174 @@
+// 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 "cc/quads/texture_draw_quad.h"
+
+#include "base/logging.h"
+#include "base/values.h"
+#include "cc/base/math_util.h"
+#include "ui/gfx/vector2d_f.h"
+
+namespace cc {
+
+TextureDrawQuad::TextureDrawQuad()
+ : resource_id(0),
+ premultiplied_alpha(false),
+ background_color(SK_ColorTRANSPARENT),
+ flipped(false) {
+ this->vertex_opacity[0] = 0.f;
+ this->vertex_opacity[1] = 0.f;
+ this->vertex_opacity[2] = 0.f;
+ this->vertex_opacity[3] = 0.f;
+}
+
+scoped_ptr<TextureDrawQuad> TextureDrawQuad::Create() {
+ return make_scoped_ptr(new TextureDrawQuad);
+}
+
+void TextureDrawQuad::SetNew(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect, gfx::Rect opaque_rect,
+ unsigned resource_id, bool premultiplied_alpha,
+ gfx::PointF uv_top_left,
+ gfx::PointF uv_bottom_right,
+ SkColor background_color,
+ const float vertex_opacity[4],
+ bool flipped) {
+ gfx::Rect visible_rect = rect;
+ bool needs_blending = vertex_opacity[0] != 1.0f || vertex_opacity[1] != 1.0f
+ || vertex_opacity[2] != 1.0f || vertex_opacity[3] != 1.0f;
+ DrawQuad::SetAll(shared_quad_state, DrawQuad::TEXTURE_CONTENT, rect,
+ opaque_rect, visible_rect, needs_blending);
+ this->resource_id = resource_id;
+ this->premultiplied_alpha = premultiplied_alpha;
+ this->uv_top_left = uv_top_left;
+ this->uv_bottom_right = uv_bottom_right;
+ this->background_color = background_color;
+ this->vertex_opacity[0] = vertex_opacity[0];
+ this->vertex_opacity[1] = vertex_opacity[1];
+ this->vertex_opacity[2] = vertex_opacity[2];
+ this->vertex_opacity[3] = vertex_opacity[3];
+ this->flipped = flipped;
+}
+
+void TextureDrawQuad::SetAll(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect, gfx::Rect opaque_rect,
+ gfx::Rect visible_rect, bool needs_blending,
+ unsigned resource_id, bool premultiplied_alpha,
+ gfx::PointF uv_top_left,
+ gfx::PointF uv_bottom_right,
+ SkColor background_color,
+ const float vertex_opacity[4],
+ bool flipped) {
+ DrawQuad::SetAll(shared_quad_state, DrawQuad::TEXTURE_CONTENT, rect,
+ opaque_rect, visible_rect, needs_blending);
+ this->resource_id = resource_id;
+ this->premultiplied_alpha = premultiplied_alpha;
+ this->uv_top_left = uv_top_left;
+ this->uv_bottom_right = uv_bottom_right;
+ this->background_color = background_color;
+ this->vertex_opacity[0] = vertex_opacity[0];
+ this->vertex_opacity[1] = vertex_opacity[1];
+ this->vertex_opacity[2] = vertex_opacity[2];
+ this->vertex_opacity[3] = vertex_opacity[3];
+ this->flipped = flipped;
+}
+
+void TextureDrawQuad::IterateResources(
+ const ResourceIteratorCallback& callback) {
+ resource_id = callback.Run(resource_id);
+}
+
+const TextureDrawQuad* TextureDrawQuad::MaterialCast(const DrawQuad* quad) {
+ DCHECK(quad->material == DrawQuad::TEXTURE_CONTENT);
+ return static_cast<const TextureDrawQuad*>(quad);
+}
+
+bool TextureDrawQuad::PerformClipping() {
+ // This only occurs if the rect is only scaled and translated (and thus still
+ // axis aligned).
+ if (!quadTransform().IsPositiveScaleOrTranslation())
+ return false;
+
+ // Grab our scale and make sure it's positive.
+ float x_scale = static_cast<float>(quadTransform().matrix().getDouble(0, 0));
+ float y_scale = static_cast<float>(quadTransform().matrix().getDouble(1, 1));
+
+ // Grab our offset.
+ gfx::Vector2dF offset(
+ static_cast<float>(quadTransform().matrix().getDouble(0, 3)),
+ static_cast<float>(quadTransform().matrix().getDouble(1, 3)));
+
+ // Transform the rect by the scale and offset.
+ gfx::RectF rect_f = rect;
+ rect_f.Scale(x_scale, y_scale);
+ rect_f += offset;
+
+ // Perform clipping and check to see if the result is empty.
+ gfx::RectF clipped_rect = IntersectRects(rect_f, clipRect());
+ if (clipped_rect.IsEmpty()) {
+ rect = gfx::Rect();
+ uv_top_left = gfx::PointF();
+ uv_bottom_right = gfx::PointF();
+ return true;
+ }
+
+ // Create a new uv-rect by clipping the old one to the new bounds.
+ gfx::Vector2dF uv_scale(uv_bottom_right - uv_top_left);
+ uv_scale.Scale(1.f / rect_f.width(), 1.f / rect_f.height());
+ uv_bottom_right = uv_top_left +
+ gfx::ScaleVector2d(
+ clipped_rect.bottom_right() - rect_f.origin(),
+ uv_scale.x(),
+ uv_scale.y());
+ uv_top_left = uv_top_left +
+ gfx::ScaleVector2d(
+ clipped_rect.origin() - rect_f.origin(),
+ uv_scale.x(),
+ uv_scale.y());
+
+ // Indexing according to the quad vertex generation:
+ // 1--2
+ // | |
+ // 0--3
+ if (vertex_opacity[0] != vertex_opacity[1]
+ || vertex_opacity[0] != vertex_opacity[2]
+ || vertex_opacity[0] != vertex_opacity[3]) {
+ const float x1 = (clipped_rect.x() - rect_f.x()) / rect_f.width();
+ const float y1 = (clipped_rect.y() - rect_f.y()) / rect_f.height();
+ const float x3 = (clipped_rect.right() - rect_f.x()) / rect_f.width();
+ const float y3 = (clipped_rect.bottom() - rect_f.y()) / rect_f.height();
+ const float x1y1 = x1 * vertex_opacity[2] + (1.0f - x1) * vertex_opacity[1];
+ const float x1y3 = x1 * vertex_opacity[3] + (1.0f - x1) * vertex_opacity[0];
+ const float x3y1 = x3 * vertex_opacity[2] + (1.0f - x3) * vertex_opacity[1];
+ const float x3y3 = x3 * vertex_opacity[3] + (1.0f - x3) * vertex_opacity[0];
+ vertex_opacity[0] = y3 * x1y3 + (1.0f - y3) * x1y1;
+ vertex_opacity[1] = y1 * x1y3 + (1.0f - y1) * x1y1;
+ vertex_opacity[2] = y1 * x3y3 + (1.0f - y1) * x3y1;
+ vertex_opacity[3] = y3 * x3y3 + (1.0f - y3) * x3y1;
+ }
+
+ // Move the clipped rectangle back into its space.
+ clipped_rect -= offset;
+ clipped_rect.Scale(1.0f / x_scale, 1.0f / y_scale);
+ rect = gfx::Rect(static_cast<int>(clipped_rect.x() + 0.5f),
+ static_cast<int>(clipped_rect.y() + 0.5f),
+ static_cast<int>(clipped_rect.width() + 0.5f),
+ static_cast<int>(clipped_rect.height() + 0.5f));
+ return true;
+}
+
+void TextureDrawQuad::ExtendValue(base::DictionaryValue* value) const {
+ value->SetInteger("resource_id", resource_id);
+ value->SetBoolean("premultiplied_alpha", premultiplied_alpha);
+ value->Set("uv_top_left", MathUtil::AsValue(uv_top_left).release());
+ value->Set("uv_bottom_right", MathUtil::AsValue(uv_bottom_right).release());
+ value->SetInteger("background_color", background_color);
+ scoped_ptr<ListValue> vertex_opacity_value(new ListValue);
+ for (size_t i = 0; i < 4; ++i)
+ vertex_opacity_value->AppendDouble(vertex_opacity[i]);
+ value->Set("vertex_opacity", vertex_opacity_value.release());
+ value->SetBoolean("flipped", flipped);
+}
+
+} // namespace cc
diff --git a/chromium/cc/quads/texture_draw_quad.h b/chromium/cc/quads/texture_draw_quad.h
new file mode 100644
index 00000000000..7f066188484
--- /dev/null
+++ b/chromium/cc/quads/texture_draw_quad.h
@@ -0,0 +1,65 @@
+// 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.
+
+#ifndef CC_QUADS_TEXTURE_DRAW_QUAD_H_
+#define CC_QUADS_TEXTURE_DRAW_QUAD_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/quads/draw_quad.h"
+#include "ui/gfx/rect_f.h"
+
+namespace cc {
+
+class CC_EXPORT TextureDrawQuad : public DrawQuad {
+ public:
+ static scoped_ptr<TextureDrawQuad> Create();
+
+ void SetNew(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ unsigned resource_id,
+ bool premultiplied_alpha,
+ gfx::PointF uv_top_left,
+ gfx::PointF uv_bottom_right,
+ SkColor background_color,
+ const float vertex_opacity[4],
+ bool flipped);
+
+ void SetAll(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::Rect visible_rect,
+ bool needs_blending,
+ unsigned resource_id,
+ bool premultiplied_alpha,
+ gfx::PointF uv_top_left,
+ gfx::PointF uv_bottom_right,
+ SkColor background_color,
+ const float vertex_opacity[4],
+ bool flipped);
+
+ unsigned resource_id;
+ bool premultiplied_alpha;
+ gfx::PointF uv_top_left;
+ gfx::PointF uv_bottom_right;
+ SkColor background_color;
+ float vertex_opacity[4];
+ bool flipped;
+
+ virtual void IterateResources(const ResourceIteratorCallback& callback)
+ OVERRIDE;
+
+ static const TextureDrawQuad* MaterialCast(const DrawQuad*);
+
+ bool PerformClipping();
+
+ private:
+ TextureDrawQuad();
+ virtual void ExtendValue(base::DictionaryValue* value) const OVERRIDE;
+};
+
+} // namespace cc
+
+#endif // CC_QUADS_TEXTURE_DRAW_QUAD_H_
diff --git a/chromium/cc/quads/tile_draw_quad.cc b/chromium/cc/quads/tile_draw_quad.cc
new file mode 100644
index 00000000000..3ff98095e9f
--- /dev/null
+++ b/chromium/cc/quads/tile_draw_quad.cc
@@ -0,0 +1,67 @@
+// 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 "cc/quads/tile_draw_quad.h"
+
+#include "base/logging.h"
+#include "base/values.h"
+#include "third_party/khronos/GLES2/gl2.h"
+
+namespace cc {
+
+TileDrawQuad::TileDrawQuad()
+ : resource_id(0) {
+}
+
+TileDrawQuad::~TileDrawQuad() {
+}
+
+scoped_ptr<TileDrawQuad> TileDrawQuad::Create() {
+ return make_scoped_ptr(new TileDrawQuad);
+}
+
+void TileDrawQuad::SetNew(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ unsigned resource_id,
+ const gfx::RectF& tex_coord_rect,
+ gfx::Size texture_size,
+ bool swizzle_contents) {
+ ContentDrawQuadBase::SetNew(shared_quad_state, DrawQuad::TILED_CONTENT, rect,
+ opaque_rect, tex_coord_rect, texture_size,
+ swizzle_contents);
+ this->resource_id = resource_id;
+}
+
+void TileDrawQuad::SetAll(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::Rect visible_rect,
+ bool needs_blending,
+ unsigned resource_id,
+ const gfx::RectF& tex_coord_rect,
+ gfx::Size texture_size,
+ bool swizzle_contents) {
+ ContentDrawQuadBase::SetAll(shared_quad_state, DrawQuad::TILED_CONTENT, rect,
+ opaque_rect, visible_rect, needs_blending,
+ tex_coord_rect, texture_size, swizzle_contents);
+ this->resource_id = resource_id;
+}
+
+void TileDrawQuad::IterateResources(
+ const ResourceIteratorCallback& callback) {
+ resource_id = callback.Run(resource_id);
+}
+
+const TileDrawQuad* TileDrawQuad::MaterialCast(const DrawQuad* quad) {
+ DCHECK(quad->material == DrawQuad::TILED_CONTENT);
+ return static_cast<const TileDrawQuad*>(quad);
+}
+
+void TileDrawQuad::ExtendValue(base::DictionaryValue* value) const {
+ ContentDrawQuadBase::ExtendValue(value);
+ value->SetInteger("resource_id", resource_id);
+}
+
+} // namespace cc
diff --git a/chromium/cc/quads/tile_draw_quad.h b/chromium/cc/quads/tile_draw_quad.h
new file mode 100644
index 00000000000..6da1e54a952
--- /dev/null
+++ b/chromium/cc/quads/tile_draw_quad.h
@@ -0,0 +1,49 @@
+// 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.
+
+#ifndef CC_QUADS_TILE_DRAW_QUAD_H_
+#define CC_QUADS_TILE_DRAW_QUAD_H_
+
+#include "cc/quads/content_draw_quad_base.h"
+
+namespace cc {
+
+class CC_EXPORT TileDrawQuad : public ContentDrawQuadBase {
+ public:
+ static scoped_ptr<TileDrawQuad> Create();
+ virtual ~TileDrawQuad();
+
+ void SetNew(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ unsigned resource_id,
+ const gfx::RectF& tex_coord_rect,
+ gfx::Size texture_size,
+ bool swizzle_contents);
+
+ void SetAll(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::Rect visible_rect,
+ bool needs_blending,
+ unsigned resource_id,
+ const gfx::RectF& tex_coord_rect,
+ gfx::Size texture_size,
+ bool swizzle_contents);
+
+ unsigned resource_id;
+
+ virtual void IterateResources(const ResourceIteratorCallback& callback)
+ OVERRIDE;
+
+ static const TileDrawQuad* MaterialCast(const DrawQuad*);
+
+ private:
+ TileDrawQuad();
+ virtual void ExtendValue(base::DictionaryValue* value) const OVERRIDE;
+};
+
+} // namespace cc
+
+#endif // CC_QUADS_TILE_DRAW_QUAD_H_
diff --git a/chromium/cc/quads/yuv_video_draw_quad.cc b/chromium/cc/quads/yuv_video_draw_quad.cc
new file mode 100644
index 00000000000..ecdc78fdcb3
--- /dev/null
+++ b/chromium/cc/quads/yuv_video_draw_quad.cc
@@ -0,0 +1,85 @@
+// 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 "cc/quads/yuv_video_draw_quad.h"
+
+#include "base/logging.h"
+#include "base/values.h"
+#include "cc/base/math_util.h"
+
+namespace cc {
+
+YUVVideoDrawQuad::YUVVideoDrawQuad()
+ : y_plane_resource_id(0),
+ u_plane_resource_id(0),
+ v_plane_resource_id(0),
+ a_plane_resource_id(0) {}
+YUVVideoDrawQuad::~YUVVideoDrawQuad() {}
+
+scoped_ptr<YUVVideoDrawQuad> YUVVideoDrawQuad::Create() {
+ return make_scoped_ptr(new YUVVideoDrawQuad);
+}
+
+void YUVVideoDrawQuad::SetNew(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::SizeF tex_scale,
+ unsigned y_plane_resource_id,
+ unsigned u_plane_resource_id,
+ unsigned v_plane_resource_id,
+ unsigned a_plane_resource_id) {
+ gfx::Rect visible_rect = rect;
+ bool needs_blending = false;
+ DrawQuad::SetAll(shared_quad_state, DrawQuad::YUV_VIDEO_CONTENT, rect,
+ opaque_rect, visible_rect, needs_blending);
+ this->tex_scale = tex_scale;
+ this->y_plane_resource_id = y_plane_resource_id;
+ this->u_plane_resource_id = u_plane_resource_id;
+ this->v_plane_resource_id = v_plane_resource_id;
+ this->a_plane_resource_id = a_plane_resource_id;
+}
+
+void YUVVideoDrawQuad::SetAll(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::Rect visible_rect,
+ bool needs_blending,
+ gfx::SizeF tex_scale,
+ unsigned y_plane_resource_id,
+ unsigned u_plane_resource_id,
+ unsigned v_plane_resource_id,
+ unsigned a_plane_resource_id) {
+ DrawQuad::SetAll(shared_quad_state, DrawQuad::YUV_VIDEO_CONTENT, rect,
+ opaque_rect, visible_rect, needs_blending);
+ this->tex_scale = tex_scale;
+ this->y_plane_resource_id = y_plane_resource_id;
+ this->u_plane_resource_id = u_plane_resource_id;
+ this->v_plane_resource_id = v_plane_resource_id;
+ this->a_plane_resource_id = a_plane_resource_id;
+}
+
+void YUVVideoDrawQuad::IterateResources(
+ const ResourceIteratorCallback& callback) {
+ y_plane_resource_id = callback.Run(y_plane_resource_id);
+ u_plane_resource_id = callback.Run(u_plane_resource_id);
+ v_plane_resource_id = callback.Run(v_plane_resource_id);
+ if (a_plane_resource_id)
+ a_plane_resource_id = callback.Run(a_plane_resource_id);
+}
+
+const YUVVideoDrawQuad* YUVVideoDrawQuad::MaterialCast(
+ const DrawQuad* quad) {
+ DCHECK(quad->material == DrawQuad::YUV_VIDEO_CONTENT);
+ return static_cast<const YUVVideoDrawQuad*>(quad);
+}
+
+void YUVVideoDrawQuad::ExtendValue(base::DictionaryValue* value) const {
+ value->Set("tex_scale", MathUtil::AsValue(tex_scale).release());
+ value->SetInteger("y_plane_resource_id", y_plane_resource_id);
+ value->SetInteger("u_plane_resource_id", u_plane_resource_id);
+ value->SetInteger("v_plane_resource_id", v_plane_resource_id);
+ value->SetInteger("a_plane_resource_id", a_plane_resource_id);
+}
+
+} // namespace cc
diff --git a/chromium/cc/quads/yuv_video_draw_quad.h b/chromium/cc/quads/yuv_video_draw_quad.h
new file mode 100644
index 00000000000..aa750fa8ba9
--- /dev/null
+++ b/chromium/cc/quads/yuv_video_draw_quad.h
@@ -0,0 +1,60 @@
+// 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.
+
+#ifndef CC_QUADS_YUV_VIDEO_DRAW_QUAD_H_
+#define CC_QUADS_YUV_VIDEO_DRAW_QUAD_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/layers/video_layer_impl.h"
+#include "cc/quads/draw_quad.h"
+
+namespace cc {
+
+class CC_EXPORT YUVVideoDrawQuad : public DrawQuad {
+ public:
+ virtual ~YUVVideoDrawQuad();
+
+ static scoped_ptr<YUVVideoDrawQuad> Create();
+
+ void SetNew(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::SizeF tex_scale,
+ unsigned y_plane_resource_id,
+ unsigned u_plane_resource_id,
+ unsigned v_plane_resource_id,
+ unsigned a_plane_resource_id);
+
+ void SetAll(const SharedQuadState* shared_quad_state,
+ gfx::Rect rect,
+ gfx::Rect opaque_rect,
+ gfx::Rect visible_rect,
+ bool needs_blending,
+ gfx::SizeF tex_scale,
+ unsigned y_plane_resource_id,
+ unsigned u_plane_resource_id,
+ unsigned v_plane_resource_id,
+ unsigned a_plane_resource_id);
+
+ gfx::SizeF tex_scale;
+ unsigned y_plane_resource_id;
+ unsigned u_plane_resource_id;
+ unsigned v_plane_resource_id;
+ unsigned a_plane_resource_id;
+
+ virtual void IterateResources(const ResourceIteratorCallback& callback)
+ OVERRIDE;
+
+ static const YUVVideoDrawQuad* MaterialCast(const DrawQuad*);
+
+ private:
+ YUVVideoDrawQuad();
+ virtual void ExtendValue(base::DictionaryValue* value) const OVERRIDE;
+};
+
+} // namespace cc
+
+#endif // CC_QUADS_YUV_VIDEO_DRAW_QUAD_H_
diff --git a/chromium/cc/resources/bitmap_content_layer_updater.cc b/chromium/cc/resources/bitmap_content_layer_updater.cc
new file mode 100644
index 00000000000..3d90eb0a4d5
--- /dev/null
+++ b/chromium/cc/resources/bitmap_content_layer_updater.cc
@@ -0,0 +1,118 @@
+// Copyright 2011 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 "cc/resources/bitmap_content_layer_updater.h"
+
+#include "cc/debug/devtools_instrumentation.h"
+#include "cc/debug/rendering_stats_instrumentation.h"
+#include "cc/resources/layer_painter.h"
+#include "cc/resources/prioritized_resource.h"
+#include "cc/resources/resource_update.h"
+#include "cc/resources/resource_update_queue.h"
+#include "skia/ext/platform_canvas.h"
+
+namespace cc {
+
+BitmapContentLayerUpdater::Resource::Resource(
+ BitmapContentLayerUpdater* updater,
+ scoped_ptr<PrioritizedResource> texture)
+ : LayerUpdater::Resource(texture.Pass()), updater_(updater) {}
+
+BitmapContentLayerUpdater::Resource::~Resource() {}
+
+void BitmapContentLayerUpdater::Resource::Update(ResourceUpdateQueue* queue,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset,
+ bool partial_update) {
+ updater_->UpdateTexture(
+ queue, texture(), source_rect, dest_offset, partial_update);
+}
+
+scoped_refptr<BitmapContentLayerUpdater> BitmapContentLayerUpdater::Create(
+ scoped_ptr<LayerPainter> painter,
+ RenderingStatsInstrumentation* stats_instrumentation,
+ int layer_id) {
+ return make_scoped_refptr(
+ new BitmapContentLayerUpdater(painter.Pass(),
+ stats_instrumentation,
+ layer_id));
+}
+
+BitmapContentLayerUpdater::BitmapContentLayerUpdater(
+ scoped_ptr<LayerPainter> painter,
+ RenderingStatsInstrumentation* stats_instrumentation,
+ int layer_id)
+ : ContentLayerUpdater(painter.Pass(), stats_instrumentation, layer_id),
+ opaque_(false) {}
+
+BitmapContentLayerUpdater::~BitmapContentLayerUpdater() {}
+
+scoped_ptr<LayerUpdater::Resource> BitmapContentLayerUpdater::CreateResource(
+ PrioritizedResourceManager* manager) {
+ return scoped_ptr<LayerUpdater::Resource>(
+ new Resource(this, PrioritizedResource::Create(manager)));
+}
+
+void BitmapContentLayerUpdater::PrepareToUpdate(
+ gfx::Rect content_rect,
+ gfx::Size tile_size,
+ float contents_width_scale,
+ float contents_height_scale,
+ gfx::Rect* resulting_opaque_rect) {
+ devtools_instrumentation::ScopedLayerTask paint_layer(
+ devtools_instrumentation::kPaintLayer, layer_id_);
+ if (canvas_size_ != content_rect.size()) {
+ devtools_instrumentation::ScopedLayerTask paint_setup(
+ devtools_instrumentation::kPaintSetup, layer_id_);
+ canvas_size_ = content_rect.size();
+ canvas_ = skia::AdoptRef(skia::CreateBitmapCanvas(
+ canvas_size_.width(), canvas_size_.height(), opaque_));
+ }
+
+ base::TimeTicks start_time =
+ rendering_stats_instrumentation_->StartRecording();
+ PaintContents(canvas_.get(),
+ content_rect,
+ contents_width_scale,
+ contents_height_scale,
+ resulting_opaque_rect);
+ base::TimeDelta duration =
+ rendering_stats_instrumentation_->EndRecording(start_time);
+ rendering_stats_instrumentation_->AddPaint(
+ duration,
+ content_rect.width() * content_rect.height());
+}
+
+void BitmapContentLayerUpdater::UpdateTexture(ResourceUpdateQueue* queue,
+ PrioritizedResource* texture,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset,
+ bool partial_update) {
+ CHECK(canvas_);
+ ResourceUpdate upload =
+ ResourceUpdate::CreateFromCanvas(texture,
+ canvas_,
+ content_rect(),
+ source_rect,
+ dest_offset);
+ if (partial_update)
+ queue->AppendPartialUpload(upload);
+ else
+ queue->AppendFullUpload(upload);
+}
+
+void BitmapContentLayerUpdater::ReduceMemoryUsage() {
+ canvas_.clear();
+ canvas_size_ = gfx::Size();
+}
+
+void BitmapContentLayerUpdater::SetOpaque(bool opaque) {
+ if (opaque != opaque_) {
+ canvas_.clear();
+ canvas_size_ = gfx::Size();
+ }
+ opaque_ = opaque;
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/bitmap_content_layer_updater.h b/chromium/cc/resources/bitmap_content_layer_updater.h
new file mode 100644
index 00000000000..2a417f06dfa
--- /dev/null
+++ b/chromium/cc/resources/bitmap_content_layer_updater.h
@@ -0,0 +1,78 @@
+// Copyright 2011 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.
+
+#ifndef CC_RESOURCES_BITMAP_CONTENT_LAYER_UPDATER_H_
+#define CC_RESOURCES_BITMAP_CONTENT_LAYER_UPDATER_H_
+
+#include "cc/base/cc_export.h"
+#include "cc/resources/content_layer_updater.h"
+#include "skia/ext/refptr.h"
+
+class SkCanvas;
+
+namespace cc {
+
+class LayerPainter;
+class RenderingStatsInstrumenation;
+
+// This class rasterizes the content_rect into a skia bitmap canvas. It then
+// updates textures by copying from the canvas into the texture, using
+// MapSubImage if possible.
+class CC_EXPORT BitmapContentLayerUpdater : public ContentLayerUpdater {
+ public:
+ class Resource : public LayerUpdater::Resource {
+ public:
+ Resource(BitmapContentLayerUpdater* updater,
+ scoped_ptr<PrioritizedResource> resource);
+ virtual ~Resource();
+
+ virtual void Update(ResourceUpdateQueue* queue,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset,
+ bool partial_update) OVERRIDE;
+
+ private:
+ BitmapContentLayerUpdater* updater_;
+
+ DISALLOW_COPY_AND_ASSIGN(Resource);
+ };
+
+ static scoped_refptr<BitmapContentLayerUpdater> Create(
+ scoped_ptr<LayerPainter> painter,
+ RenderingStatsInstrumentation* stats_instrumenation,
+ int layer_id);
+
+ virtual scoped_ptr<LayerUpdater::Resource> CreateResource(
+ PrioritizedResourceManager* manager) OVERRIDE;
+ virtual void PrepareToUpdate(gfx::Rect content_rect,
+ gfx::Size tile_size,
+ float contents_width_scale,
+ float contents_height_scale,
+ gfx::Rect* resulting_opaque_rect) OVERRIDE;
+ void UpdateTexture(ResourceUpdateQueue* queue,
+ PrioritizedResource* resource,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset,
+ bool partial_update);
+ virtual void SetOpaque(bool opaque) OVERRIDE;
+ virtual void ReduceMemoryUsage() OVERRIDE;
+
+ protected:
+ BitmapContentLayerUpdater(
+ scoped_ptr<LayerPainter> painter,
+ RenderingStatsInstrumentation* stats_instrumenation,
+ int layer_id);
+ virtual ~BitmapContentLayerUpdater();
+
+ skia::RefPtr<SkCanvas> canvas_;
+ gfx::Size canvas_size_;
+ bool opaque_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BitmapContentLayerUpdater);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_BITMAP_CONTENT_LAYER_UPDATER_H_
diff --git a/chromium/cc/resources/bitmap_skpicture_content_layer_updater.cc b/chromium/cc/resources/bitmap_skpicture_content_layer_updater.cc
new file mode 100644
index 00000000000..2c00099de1c
--- /dev/null
+++ b/chromium/cc/resources/bitmap_skpicture_content_layer_updater.cc
@@ -0,0 +1,89 @@
+// Copyright 2011 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 "cc/resources/bitmap_skpicture_content_layer_updater.h"
+
+#include "base/time/time.h"
+#include "cc/debug/rendering_stats_instrumentation.h"
+#include "cc/resources/layer_painter.h"
+#include "cc/resources/prioritized_resource.h"
+#include "cc/resources/resource_update_queue.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkDevice.h"
+
+namespace cc {
+
+BitmapSkPictureContentLayerUpdater::Resource::Resource(
+ BitmapSkPictureContentLayerUpdater* updater,
+ scoped_ptr<PrioritizedResource> texture)
+ : ContentLayerUpdater::Resource(texture.Pass()), updater_(updater) {}
+
+void BitmapSkPictureContentLayerUpdater::Resource::Update(
+ ResourceUpdateQueue* queue,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset,
+ bool partial_update) {
+ bitmap_.setConfig(
+ SkBitmap::kARGB_8888_Config, source_rect.width(), source_rect.height());
+ bitmap_.allocPixels();
+ bitmap_.setIsOpaque(updater_->layer_is_opaque());
+ SkDevice device(bitmap_);
+ SkCanvas canvas(&device);
+ updater_->PaintContentsRect(&canvas, source_rect);
+
+ ResourceUpdate upload = ResourceUpdate::Create(
+ texture(), &bitmap_, source_rect, source_rect, dest_offset);
+ if (partial_update)
+ queue->AppendPartialUpload(upload);
+ else
+ queue->AppendFullUpload(upload);
+}
+
+scoped_refptr<BitmapSkPictureContentLayerUpdater>
+BitmapSkPictureContentLayerUpdater::Create(
+ scoped_ptr<LayerPainter> painter,
+ RenderingStatsInstrumentation* stats_instrumentation,
+ int layer_id) {
+ return make_scoped_refptr(
+ new BitmapSkPictureContentLayerUpdater(painter.Pass(),
+ stats_instrumentation,
+ layer_id));
+}
+
+BitmapSkPictureContentLayerUpdater::BitmapSkPictureContentLayerUpdater(
+ scoped_ptr<LayerPainter> painter,
+ RenderingStatsInstrumentation* stats_instrumentation,
+ int layer_id)
+ : SkPictureContentLayerUpdater(painter.Pass(),
+ stats_instrumentation,
+ layer_id) {}
+
+BitmapSkPictureContentLayerUpdater::~BitmapSkPictureContentLayerUpdater() {}
+
+scoped_ptr<LayerUpdater::Resource>
+BitmapSkPictureContentLayerUpdater::CreateResource(
+ PrioritizedResourceManager* manager) {
+ return scoped_ptr<LayerUpdater::Resource>(
+ new Resource(this, PrioritizedResource::Create(manager)));
+}
+
+void BitmapSkPictureContentLayerUpdater::PaintContentsRect(
+ SkCanvas* canvas,
+ gfx::Rect source_rect) {
+ // Translate the origin of content_rect to that of source_rect.
+ canvas->translate(content_rect().x() - source_rect.x(),
+ content_rect().y() - source_rect.y());
+ base::TimeTicks start_time =
+ rendering_stats_instrumentation_->StartRecording();
+ DrawPicture(canvas);
+ base::TimeDelta duration =
+ rendering_stats_instrumentation_->EndRecording(start_time);
+ rendering_stats_instrumentation_->AddRaster(
+ duration,
+ duration,
+ source_rect.width() * source_rect.height(),
+ false);
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/bitmap_skpicture_content_layer_updater.h b/chromium/cc/resources/bitmap_skpicture_content_layer_updater.h
new file mode 100644
index 00000000000..e03f56d853d
--- /dev/null
+++ b/chromium/cc/resources/bitmap_skpicture_content_layer_updater.h
@@ -0,0 +1,57 @@
+// Copyright 2011 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.
+
+#ifndef CC_RESOURCES_BITMAP_SKPICTURE_CONTENT_LAYER_UPDATER_H_
+#define CC_RESOURCES_BITMAP_SKPICTURE_CONTENT_LAYER_UPDATER_H_
+
+#include "cc/resources/skpicture_content_layer_updater.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+namespace cc {
+
+// This class records the content_rect into an SkPicture, then software
+// rasterizes the SkPicture into bitmaps for each tile. This implements
+// LayerTreeSettingSettings::per_tile_painting_enabled.
+class BitmapSkPictureContentLayerUpdater : public SkPictureContentLayerUpdater {
+ public:
+ class Resource : public ContentLayerUpdater::Resource {
+ public:
+ Resource(BitmapSkPictureContentLayerUpdater* updater,
+ scoped_ptr<PrioritizedResource> texture);
+
+ virtual void Update(ResourceUpdateQueue* queue,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset,
+ bool partial_update) OVERRIDE;
+
+ private:
+ SkBitmap bitmap_;
+ BitmapSkPictureContentLayerUpdater* updater_;
+
+ DISALLOW_COPY_AND_ASSIGN(Resource);
+ };
+
+ static scoped_refptr<BitmapSkPictureContentLayerUpdater> Create(
+ scoped_ptr<LayerPainter> painter,
+ RenderingStatsInstrumentation* stats_instrumentation,
+ int layer_id);
+
+ virtual scoped_ptr<LayerUpdater::Resource> CreateResource(
+ PrioritizedResourceManager* manager) OVERRIDE;
+ void PaintContentsRect(SkCanvas* canvas,
+ gfx::Rect source_rect);
+
+ private:
+ BitmapSkPictureContentLayerUpdater(
+ scoped_ptr<LayerPainter> painter,
+ RenderingStatsInstrumentation* stats_instrumentation,
+ int layer_id);
+ virtual ~BitmapSkPictureContentLayerUpdater();
+
+ DISALLOW_COPY_AND_ASSIGN(BitmapSkPictureContentLayerUpdater);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_BITMAP_SKPICTURE_CONTENT_LAYER_UPDATER_H_
diff --git a/chromium/cc/resources/caching_bitmap_content_layer_updater.cc b/chromium/cc/resources/caching_bitmap_content_layer_updater.cc
new file mode 100644
index 00000000000..f6edb40ccdf
--- /dev/null
+++ b/chromium/cc/resources/caching_bitmap_content_layer_updater.cc
@@ -0,0 +1,61 @@
+// 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 "cc/resources/caching_bitmap_content_layer_updater.h"
+
+#include "base/logging.h"
+#include "cc/resources/layer_painter.h"
+#include "skia/ext/platform_canvas.h"
+
+namespace cc {
+
+scoped_refptr<CachingBitmapContentLayerUpdater>
+CachingBitmapContentLayerUpdater::Create(
+ scoped_ptr<LayerPainter> painter,
+ RenderingStatsInstrumentation* stats_instrumentation,
+ int layer_id) {
+ return make_scoped_refptr(
+ new CachingBitmapContentLayerUpdater(painter.Pass(),
+ stats_instrumentation,
+ layer_id));
+}
+
+CachingBitmapContentLayerUpdater::CachingBitmapContentLayerUpdater(
+ scoped_ptr<LayerPainter> painter,
+ RenderingStatsInstrumentation* stats_instrumentation,
+ int layer_id)
+ : BitmapContentLayerUpdater(painter.Pass(),
+ stats_instrumentation,
+ layer_id),
+ pixels_did_change_(false) {}
+
+CachingBitmapContentLayerUpdater::~CachingBitmapContentLayerUpdater() {}
+
+void CachingBitmapContentLayerUpdater::PrepareToUpdate(
+ gfx::Rect content_rect,
+ gfx::Size tile_size,
+ float contents_width_scale,
+ float contents_height_scale,
+ gfx::Rect* resulting_opaque_rect) {
+ BitmapContentLayerUpdater::PrepareToUpdate(content_rect,
+ tile_size,
+ contents_width_scale,
+ contents_height_scale,
+ resulting_opaque_rect);
+
+ const SkBitmap& new_bitmap = canvas_->getDevice()->accessBitmap(false);
+ SkAutoLockPixels lock(new_bitmap);
+ DCHECK_GT(new_bitmap.bytesPerPixel(), 0);
+ pixels_did_change_ = new_bitmap.config() != cached_bitmap_.config() ||
+ new_bitmap.height() != cached_bitmap_.height() ||
+ new_bitmap.width() != cached_bitmap_.width() ||
+ memcmp(new_bitmap.getPixels(),
+ cached_bitmap_.getPixels(),
+ new_bitmap.getSafeSize());
+
+ if (pixels_did_change_)
+ new_bitmap.deepCopyTo(&cached_bitmap_, new_bitmap.config());
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/caching_bitmap_content_layer_updater.h b/chromium/cc/resources/caching_bitmap_content_layer_updater.h
new file mode 100644
index 00000000000..8e9889025c4
--- /dev/null
+++ b/chromium/cc/resources/caching_bitmap_content_layer_updater.h
@@ -0,0 +1,46 @@
+// 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.
+
+#ifndef CC_RESOURCES_CACHING_BITMAP_CONTENT_LAYER_UPDATER_H_
+#define CC_RESOURCES_CACHING_BITMAP_CONTENT_LAYER_UPDATER_H_
+
+#include "base/compiler_specific.h"
+#include "cc/resources/bitmap_content_layer_updater.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+namespace cc {
+
+class CachingBitmapContentLayerUpdater : public BitmapContentLayerUpdater {
+ public:
+ static scoped_refptr<CachingBitmapContentLayerUpdater> Create(
+ scoped_ptr<LayerPainter>,
+ RenderingStatsInstrumentation* stats_instrumentation,
+ int layer_id);
+
+ virtual void PrepareToUpdate(gfx::Rect content_rect,
+ gfx::Size tile_size,
+ float contents_width_scale,
+ float contents_height_scale,
+ gfx::Rect* resulting_opaque_rect) OVERRIDE;
+
+ bool pixels_did_change() const {
+ return pixels_did_change_;
+ }
+
+ private:
+ CachingBitmapContentLayerUpdater(
+ scoped_ptr<LayerPainter> painter,
+ RenderingStatsInstrumentation* stats_instrumentation,
+ int layer_id);
+ virtual ~CachingBitmapContentLayerUpdater();
+
+ bool pixels_did_change_;
+ SkBitmap cached_bitmap_;
+
+ DISALLOW_COPY_AND_ASSIGN(CachingBitmapContentLayerUpdater);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_CACHING_BITMAP_CONTENT_LAYER_UPDATER_H_
diff --git a/chromium/cc/resources/content_layer_updater.cc b/chromium/cc/resources/content_layer_updater.cc
new file mode 100644
index 00000000000..c9c2eccf31c
--- /dev/null
+++ b/chromium/cc/resources/content_layer_updater.cc
@@ -0,0 +1,74 @@
+// Copyright 2011 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 "cc/resources/content_layer_updater.h"
+
+#include "base/debug/trace_event.h"
+#include "base/time/time.h"
+#include "cc/debug/rendering_stats_instrumentation.h"
+#include "cc/resources/layer_painter.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkPaint.h"
+#include "third_party/skia/include/core/SkRect.h"
+#include "third_party/skia/include/core/SkScalar.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/rect_f.h"
+
+namespace cc {
+
+ContentLayerUpdater::ContentLayerUpdater(
+ scoped_ptr<LayerPainter> painter,
+ RenderingStatsInstrumentation* stats_instrumentation,
+ int layer_id)
+ : rendering_stats_instrumentation_(stats_instrumentation),
+ layer_id_(layer_id),
+ painter_(painter.Pass()) {}
+
+ContentLayerUpdater::~ContentLayerUpdater() {}
+
+void ContentLayerUpdater::set_rendering_stats_instrumentation(
+ RenderingStatsInstrumentation* rsi) {
+ rendering_stats_instrumentation_ = rsi;
+}
+
+void ContentLayerUpdater::PaintContents(SkCanvas* canvas,
+ gfx::Rect content_rect,
+ float contents_width_scale,
+ float contents_height_scale,
+ gfx::Rect* resulting_opaque_rect) {
+ TRACE_EVENT0("cc", "ContentLayerUpdater::PaintContents");
+ canvas->save();
+ canvas->translate(SkFloatToScalar(-content_rect.x()),
+ SkFloatToScalar(-content_rect.y()));
+
+ gfx::Rect layer_rect = content_rect;
+
+ if (contents_width_scale != 1.f || contents_height_scale != 1.f) {
+ canvas->scale(SkFloatToScalar(contents_width_scale),
+ SkFloatToScalar(contents_height_scale));
+
+ layer_rect = gfx::ScaleToEnclosingRect(
+ content_rect, 1.f / contents_width_scale, 1.f / contents_height_scale);
+ }
+
+ SkPaint paint;
+ paint.setAntiAlias(false);
+ paint.setXfermodeMode(SkXfermode::kClear_Mode);
+ SkRect layer_sk_rect = SkRect::MakeXYWH(
+ layer_rect.x(), layer_rect.y(), layer_rect.width(), layer_rect.height());
+ canvas->drawRect(layer_sk_rect, paint);
+ canvas->clipRect(layer_sk_rect);
+
+ gfx::RectF opaque_layer_rect;
+ painter_->Paint(canvas, layer_rect, &opaque_layer_rect);
+ canvas->restore();
+
+ gfx::Rect opaque_content_rect = gfx::ToEnclosedRect(gfx::ScaleRect(
+ opaque_layer_rect, contents_width_scale, contents_height_scale));
+ *resulting_opaque_rect = opaque_content_rect;
+
+ content_rect_ = content_rect;
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/content_layer_updater.h b/chromium/cc/resources/content_layer_updater.h
new file mode 100644
index 00000000000..6c8dee3bba7
--- /dev/null
+++ b/chromium/cc/resources/content_layer_updater.h
@@ -0,0 +1,51 @@
+// Copyright 2011 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.
+
+#ifndef CC_RESOURCES_CONTENT_LAYER_UPDATER_H_
+#define CC_RESOURCES_CONTENT_LAYER_UPDATER_H_
+
+#include "cc/base/cc_export.h"
+#include "cc/resources/layer_updater.h"
+#include "ui/gfx/rect.h"
+
+class SkCanvas;
+
+namespace cc {
+
+class LayerPainter;
+class RenderingStatsInstrumentation;
+
+// Base class for BitmapContentLayerUpdater and
+// SkPictureContentLayerUpdater that reduces code duplication between
+// their respective PaintContents implementations.
+class CC_EXPORT ContentLayerUpdater : public LayerUpdater {
+ public:
+ void set_rendering_stats_instrumentation(RenderingStatsInstrumentation* rsi);
+
+ protected:
+ ContentLayerUpdater(scoped_ptr<LayerPainter> painter,
+ RenderingStatsInstrumentation* stats_instrumentation,
+ int layer_id);
+ virtual ~ContentLayerUpdater();
+
+ void PaintContents(SkCanvas* canvas,
+ gfx::Rect content_rect,
+ float contents_width_scale,
+ float contents_height_scale,
+ gfx::Rect* resulting_opaque_rect);
+ gfx::Rect content_rect() const { return content_rect_; }
+
+ RenderingStatsInstrumentation* rendering_stats_instrumentation_;
+ int layer_id_;
+
+ private:
+ gfx::Rect content_rect_;
+ scoped_ptr<LayerPainter> painter_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContentLayerUpdater);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_CONTENT_LAYER_UPDATER_H_
diff --git a/chromium/cc/resources/image_layer_updater.cc b/chromium/cc/resources/image_layer_updater.cc
new file mode 100644
index 00000000000..470f518b424
--- /dev/null
+++ b/chromium/cc/resources/image_layer_updater.cc
@@ -0,0 +1,68 @@
+// 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 "cc/resources/image_layer_updater.h"
+#include "cc/resources/prioritized_resource.h"
+#include "cc/resources/resource_update_queue.h"
+
+namespace cc {
+
+ImageLayerUpdater::Resource::Resource(ImageLayerUpdater* updater,
+ scoped_ptr<PrioritizedResource> texture)
+ : LayerUpdater::Resource(texture.Pass()), updater_(updater) {}
+
+ImageLayerUpdater::Resource::~Resource() {}
+
+void ImageLayerUpdater::Resource::Update(ResourceUpdateQueue* queue,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset,
+ bool partial_update) {
+ updater_->UpdateTexture(
+ queue, texture(), source_rect, dest_offset, partial_update);
+}
+
+// static
+scoped_refptr<ImageLayerUpdater> ImageLayerUpdater::Create() {
+ return make_scoped_refptr(new ImageLayerUpdater());
+}
+
+scoped_ptr<LayerUpdater::Resource> ImageLayerUpdater::CreateResource(
+ PrioritizedResourceManager* manager) {
+ return scoped_ptr<LayerUpdater::Resource>(
+ new Resource(this, PrioritizedResource::Create(manager)));
+}
+
+void ImageLayerUpdater::UpdateTexture(ResourceUpdateQueue* queue,
+ PrioritizedResource* texture,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset,
+ bool partial_update) {
+ // Source rect should never go outside the image pixels, even if this
+ // is requested because the texture extends outside the image.
+ gfx::Rect clipped_source_rect = source_rect;
+ gfx::Rect image_rect = gfx::Rect(0, 0, bitmap_.width(), bitmap_.height());
+ clipped_source_rect.Intersect(image_rect);
+
+ gfx::Vector2d clipped_dest_offset =
+ dest_offset +
+ gfx::Vector2d(clipped_source_rect.origin() - source_rect.origin());
+
+ ResourceUpdate upload = ResourceUpdate::Create(
+ texture, &bitmap_, image_rect, clipped_source_rect, clipped_dest_offset);
+ if (partial_update)
+ queue->AppendPartialUpload(upload);
+ else
+ queue->AppendFullUpload(upload);
+}
+
+void ImageLayerUpdater::SetBitmap(const SkBitmap& bitmap) {
+ DCHECK(bitmap.pixelRef());
+ bitmap_ = bitmap;
+}
+
+bool ImageLayerUpdater::UsingBitmap(const SkBitmap& bitmap) const {
+ return bitmap.pixelRef() == bitmap_.pixelRef();
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/image_layer_updater.h b/chromium/cc/resources/image_layer_updater.h
new file mode 100644
index 00000000000..44e541d77be
--- /dev/null
+++ b/chromium/cc/resources/image_layer_updater.h
@@ -0,0 +1,60 @@
+// 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.
+
+#ifndef CC_RESOURCES_IMAGE_LAYER_UPDATER_H_
+#define CC_RESOURCES_IMAGE_LAYER_UPDATER_H_
+
+#include "cc/base/cc_export.h"
+#include "cc/resources/layer_updater.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+namespace cc {
+
+class ResourceUpdateQueue;
+
+class CC_EXPORT ImageLayerUpdater : public LayerUpdater {
+ public:
+ class Resource : public LayerUpdater::Resource {
+ public:
+ Resource(ImageLayerUpdater* updater,
+ scoped_ptr<PrioritizedResource> texture);
+ virtual ~Resource();
+
+ virtual void Update(ResourceUpdateQueue* queue,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset,
+ bool partial_update) OVERRIDE;
+
+ private:
+ ImageLayerUpdater* updater_;
+
+ DISALLOW_COPY_AND_ASSIGN(Resource);
+ };
+
+ static scoped_refptr<ImageLayerUpdater> Create();
+
+ virtual scoped_ptr<LayerUpdater::Resource> CreateResource(
+ PrioritizedResourceManager*) OVERRIDE;
+
+ void UpdateTexture(ResourceUpdateQueue* queue,
+ PrioritizedResource* texture,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset,
+ bool partial_update);
+
+ void SetBitmap(const SkBitmap& bitmap);
+ bool UsingBitmap(const SkBitmap& bitmap) const;
+
+ private:
+ ImageLayerUpdater() {}
+ virtual ~ImageLayerUpdater() {}
+
+ SkBitmap bitmap_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImageLayerUpdater);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_IMAGE_LAYER_UPDATER_H_
diff --git a/chromium/cc/resources/image_raster_worker_pool.cc b/chromium/cc/resources/image_raster_worker_pool.cc
new file mode 100644
index 00000000000..408459c8f87
--- /dev/null
+++ b/chromium/cc/resources/image_raster_worker_pool.cc
@@ -0,0 +1,226 @@
+// Copyright 2013 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 "cc/resources/image_raster_worker_pool.h"
+
+#include "base/debug/trace_event.h"
+#include "base/values.h"
+#include "cc/debug/traced_value.h"
+#include "cc/resources/resource.h"
+#include "third_party/skia/include/core/SkDevice.h"
+
+namespace cc {
+
+namespace {
+
+class ImageWorkerPoolTaskImpl : public internal::WorkerPoolTask {
+ public:
+ typedef base::Callback<void(bool was_canceled)> Reply;
+
+ ImageWorkerPoolTaskImpl(internal::RasterWorkerPoolTask* task,
+ uint8_t* buffer,
+ int stride,
+ const Reply& reply)
+ : task_(task),
+ buffer_(buffer),
+ stride_(stride),
+ reply_(reply) {
+ }
+
+ // Overridden from internal::WorkerPoolTask:
+ virtual void RunOnWorkerThread(unsigned thread_index) OVERRIDE {
+ TRACE_EVENT0("cc", "ImageWorkerPoolTaskImpl::RunOnWorkerThread");
+ if (!buffer_)
+ return;
+
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config,
+ task_->resource()->size().width(),
+ task_->resource()->size().height(),
+ stride_);
+ bitmap.setPixels(buffer_);
+ SkDevice device(bitmap);
+ task_->RunOnWorkerThread(&device, thread_index);
+ }
+ virtual void CompleteOnOriginThread() OVERRIDE {
+ reply_.Run(!HasFinishedRunning());
+ }
+
+ private:
+ virtual ~ImageWorkerPoolTaskImpl() {}
+
+ scoped_refptr<internal::RasterWorkerPoolTask> task_;
+ uint8_t* buffer_;
+ int stride_;
+ const Reply reply_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImageWorkerPoolTaskImpl);
+};
+
+} // namespace
+
+ImageRasterWorkerPool::ImageRasterWorkerPool(
+ ResourceProvider* resource_provider, size_t num_threads)
+ : RasterWorkerPool(resource_provider, num_threads),
+ raster_tasks_pending_(false),
+ raster_tasks_required_for_activation_pending_(false) {
+}
+
+ImageRasterWorkerPool::~ImageRasterWorkerPool() {
+ DCHECK_EQ(0u, image_tasks_.size());
+}
+
+void ImageRasterWorkerPool::ScheduleTasks(RasterTask::Queue* queue) {
+ TRACE_EVENT0("cc", "ImageRasterWorkerPool::ScheduleTasks");
+
+ RasterWorkerPool::SetRasterTasks(queue);
+
+ if (!raster_tasks_pending_)
+ TRACE_EVENT_ASYNC_BEGIN0("cc", "ScheduledTasks", this);
+
+ raster_tasks_pending_ = true;
+ raster_tasks_required_for_activation_pending_ = true;
+
+ unsigned priority = 0u;
+ TaskGraph graph;
+
+ scoped_refptr<internal::WorkerPoolTask>
+ new_raster_required_for_activation_finished_task(
+ CreateRasterRequiredForActivationFinishedTask());
+ internal::GraphNode* raster_required_for_activation_finished_node =
+ CreateGraphNodeForTask(
+ new_raster_required_for_activation_finished_task.get(),
+ priority++,
+ &graph);
+
+ scoped_refptr<internal::WorkerPoolTask> new_raster_finished_task(
+ CreateRasterFinishedTask());
+ internal::GraphNode* raster_finished_node =
+ CreateGraphNodeForTask(new_raster_finished_task.get(),
+ priority++,
+ &graph);
+
+ for (RasterTaskVector::const_iterator it = raster_tasks().begin();
+ it != raster_tasks().end(); ++it) {
+ internal::RasterWorkerPoolTask* task = it->get();
+
+ TaskMap::iterator image_it = image_tasks_.find(task);
+ if (image_it != image_tasks_.end()) {
+ internal::WorkerPoolTask* image_task = image_it->second.get();
+ CreateGraphNodeForImageTask(
+ image_task,
+ task->dependencies(),
+ priority++,
+ IsRasterTaskRequiredForActivation(task),
+ raster_required_for_activation_finished_node,
+ raster_finished_node,
+ &graph);
+ continue;
+ }
+
+ // Acquire image for resource.
+ resource_provider()->AcquireImage(task->resource()->id());
+
+ // Map image for raster.
+ uint8* buffer = resource_provider()->MapImage(task->resource()->id());
+ int stride = resource_provider()->GetImageStride(task->resource()->id());
+
+ scoped_refptr<internal::WorkerPoolTask> new_image_task(
+ new ImageWorkerPoolTaskImpl(
+ task,
+ buffer,
+ stride,
+ base::Bind(&ImageRasterWorkerPool::OnRasterTaskCompleted,
+ base::Unretained(this),
+ make_scoped_refptr(task))));
+ image_tasks_[task] = new_image_task;
+ CreateGraphNodeForImageTask(
+ new_image_task.get(),
+ task->dependencies(),
+ priority++,
+ IsRasterTaskRequiredForActivation(task),
+ raster_required_for_activation_finished_node,
+ raster_finished_node,
+ &graph);
+ }
+
+ SetTaskGraph(&graph);
+
+ set_raster_finished_task(new_raster_finished_task);
+ set_raster_required_for_activation_finished_task(
+ new_raster_required_for_activation_finished_task);
+
+ TRACE_EVENT_ASYNC_STEP1(
+ "cc", "ScheduledTasks", this, "rasterizing",
+ "state", TracedValue::FromValue(StateAsValue().release()));
+}
+
+void ImageRasterWorkerPool::OnRasterTasksFinished() {
+ DCHECK(raster_tasks_pending_);
+ raster_tasks_pending_ = false;
+ TRACE_EVENT_ASYNC_END0("cc", "ScheduledTasks", this);
+ client()->DidFinishRunningTasks();
+}
+
+void ImageRasterWorkerPool::OnRasterTasksRequiredForActivationFinished() {
+ DCHECK(raster_tasks_required_for_activation_pending_);
+ raster_tasks_required_for_activation_pending_ = false;
+ TRACE_EVENT_ASYNC_STEP1(
+ "cc", "ScheduledTasks", this, "rasterizing",
+ "state", TracedValue::FromValue(StateAsValue().release()));
+ client()->DidFinishRunningTasksRequiredForActivation();
+}
+
+void ImageRasterWorkerPool::OnRasterTaskCompleted(
+ scoped_refptr<internal::RasterWorkerPoolTask> task,
+ bool was_canceled) {
+ TRACE_EVENT1("cc", "ImageRasterWorkerPool::OnRasterTaskCompleted",
+ "was_canceled", was_canceled);
+
+ DCHECK(image_tasks_.find(task.get()) != image_tasks_.end());
+
+ // Balanced with MapImage() call in ScheduleTasks().
+ resource_provider()->UnmapImage(task->resource()->id());
+
+ task->DidRun(was_canceled);
+ task->WillComplete();
+ task->CompleteOnOriginThread();
+ task->DidComplete();
+
+ image_tasks_.erase(task.get());
+}
+
+scoped_ptr<base::Value> ImageRasterWorkerPool::StateAsValue() const {
+ scoped_ptr<base::DictionaryValue> state(new base::DictionaryValue);
+
+ state->SetBoolean("tasks_required_for_activation_pending",
+ raster_tasks_required_for_activation_pending_);
+ state->Set("scheduled_state", ScheduledStateAsValue().release());
+ return state.PassAs<base::Value>();
+}
+
+// static
+void ImageRasterWorkerPool::CreateGraphNodeForImageTask(
+ internal::WorkerPoolTask* image_task,
+ const TaskVector& decode_tasks,
+ unsigned priority,
+ bool is_required_for_activation,
+ internal::GraphNode* raster_required_for_activation_finished_node,
+ internal::GraphNode* raster_finished_node,
+ TaskGraph* graph) {
+ internal::GraphNode* image_node = CreateGraphNodeForRasterTask(image_task,
+ decode_tasks,
+ priority,
+ graph);
+
+ if (is_required_for_activation) {
+ raster_required_for_activation_finished_node->add_dependency();
+ image_node->add_dependent(raster_required_for_activation_finished_node);
+ }
+
+ raster_finished_node->add_dependency();
+ image_node->add_dependent(raster_finished_node);
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/image_raster_worker_pool.h b/chromium/cc/resources/image_raster_worker_pool.h
new file mode 100644
index 00000000000..d4f4d74d120
--- /dev/null
+++ b/chromium/cc/resources/image_raster_worker_pool.h
@@ -0,0 +1,55 @@
+// Copyright 2013 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.
+
+#ifndef CC_RESOURCES_IMAGE_RASTER_WORKER_POOL_H_
+#define CC_RESOURCES_IMAGE_RASTER_WORKER_POOL_H_
+
+#include "cc/resources/raster_worker_pool.h"
+
+namespace cc {
+
+class CC_EXPORT ImageRasterWorkerPool : public RasterWorkerPool {
+ public:
+ virtual ~ImageRasterWorkerPool();
+
+ static scoped_ptr<RasterWorkerPool> Create(
+ ResourceProvider* resource_provider, size_t num_threads) {
+ return make_scoped_ptr<RasterWorkerPool>(
+ new ImageRasterWorkerPool(resource_provider, num_threads));
+ }
+
+ // Overridden from RasterWorkerPool:
+ virtual void ScheduleTasks(RasterTask::Queue* queue) OVERRIDE;
+ virtual void OnRasterTasksFinished() OVERRIDE;
+ virtual void OnRasterTasksRequiredForActivationFinished() OVERRIDE;
+
+ private:
+ ImageRasterWorkerPool(ResourceProvider* resource_provider,
+ size_t num_threads);
+
+ void OnRasterTaskCompleted(
+ scoped_refptr<internal::RasterWorkerPoolTask> task, bool was_canceled);
+
+ scoped_ptr<base::Value> StateAsValue() const;
+
+ static void CreateGraphNodeForImageTask(
+ internal::WorkerPoolTask* image_task,
+ const TaskVector& decode_tasks,
+ unsigned priority,
+ bool is_required_for_activation,
+ internal::GraphNode* raster_required_for_activation_finished_node,
+ internal::GraphNode* raster_finished_node,
+ TaskGraph* graph);
+
+ TaskMap image_tasks_;
+
+ bool raster_tasks_pending_;
+ bool raster_tasks_required_for_activation_pending_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImageRasterWorkerPool);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_IMAGE_RASTER_WORKER_POOL_H_
diff --git a/chromium/cc/resources/layer_painter.h b/chromium/cc/resources/layer_painter.h
new file mode 100644
index 00000000000..89a14243df3
--- /dev/null
+++ b/chromium/cc/resources/layer_painter.h
@@ -0,0 +1,29 @@
+// Copyright 2011 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.
+
+#ifndef CC_RESOURCES_LAYER_PAINTER_H_
+#define CC_RESOURCES_LAYER_PAINTER_H_
+
+#include "cc/base/cc_export.h"
+
+class SkCanvas;
+
+namespace gfx {
+class Rect;
+class RectF;
+}
+
+namespace cc {
+
+class CC_EXPORT LayerPainter {
+ public:
+ virtual ~LayerPainter() {}
+ virtual void Paint(SkCanvas* canvas,
+ gfx::Rect content_rect,
+ gfx::RectF* opaque) = 0;
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_LAYER_PAINTER_H_
diff --git a/chromium/cc/resources/layer_quad.cc b/chromium/cc/resources/layer_quad.cc
new file mode 100644
index 00000000000..3e1955d05d8
--- /dev/null
+++ b/chromium/cc/resources/layer_quad.cc
@@ -0,0 +1,67 @@
+// Copyright 2011 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 "cc/resources/layer_quad.h"
+
+#include "base/logging.h"
+#include "ui/gfx/quad_f.h"
+
+namespace cc {
+
+LayerQuad::Edge::Edge(gfx::PointF p, gfx::PointF q) {
+ DCHECK(p != q);
+
+ gfx::Vector2dF tangent(p.y() - q.y(), q.x() - p.x());
+ float cross2 = p.x() * q.y() - q.x() * p.y();
+
+ set(tangent.x(), tangent.y(), cross2);
+ scale(1.0f / tangent.Length());
+}
+
+LayerQuad::LayerQuad(const gfx::QuadF& quad) {
+ // Create edges.
+ left_ = Edge(quad.p4(), quad.p1());
+ right_ = Edge(quad.p2(), quad.p3());
+ top_ = Edge(quad.p1(), quad.p2());
+ bottom_ = Edge(quad.p3(), quad.p4());
+
+ float sign = quad.IsCounterClockwise() ? -1 : 1;
+ left_.scale(sign);
+ right_.scale(sign);
+ top_.scale(sign);
+ bottom_.scale(sign);
+}
+
+LayerQuad::LayerQuad(const Edge& left,
+ const Edge& top,
+ const Edge& right,
+ const Edge& bottom)
+ : left_(left),
+ top_(top),
+ right_(right),
+ bottom_(bottom) {}
+
+gfx::QuadF LayerQuad::ToQuadF() const {
+ return gfx::QuadF(left_.Intersect(top_),
+ top_.Intersect(right_),
+ right_.Intersect(bottom_),
+ bottom_.Intersect(left_));
+}
+
+void LayerQuad::ToFloatArray(float flattened[12]) const {
+ flattened[0] = left_.x();
+ flattened[1] = left_.y();
+ flattened[2] = left_.z();
+ flattened[3] = top_.x();
+ flattened[4] = top_.y();
+ flattened[5] = top_.z();
+ flattened[6] = right_.x();
+ flattened[7] = right_.y();
+ flattened[8] = right_.z();
+ flattened[9] = bottom_.x();
+ flattened[10] = bottom_.y();
+ flattened[11] = bottom_.z();
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/layer_quad.h b/chromium/cc/resources/layer_quad.h
new file mode 100644
index 00000000000..8098c209650
--- /dev/null
+++ b/chromium/cc/resources/layer_quad.h
@@ -0,0 +1,114 @@
+// Copyright 2011 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.
+
+
+#ifndef CC_RESOURCES_LAYER_QUAD_H_
+#define CC_RESOURCES_LAYER_QUAD_H_
+
+#include "base/basictypes.h"
+#include "cc/base/cc_export.h"
+#include "ui/gfx/point_f.h"
+
+namespace gfx {
+class QuadF;
+}
+
+static const float kAntiAliasingInflateDistance = 0.5f;
+
+namespace cc {
+
+class CC_EXPORT LayerQuad {
+ public:
+ class Edge {
+ public:
+ Edge() : x_(0), y_(0), z_(0) {}
+ Edge(gfx::PointF p, gfx::PointF q);
+
+ float x() const { return x_; }
+ float y() const { return y_; }
+ float z() const { return z_; }
+
+ void set_x(float x) { x_ = x; }
+ void set_y(float y) { y_ = y; }
+ void set_z(float z) { z_ = z; }
+ void set(float x, float y, float z) {
+ x_ = x;
+ y_ = y;
+ z_ = z;
+ }
+
+ void move_x(float dx) { x_ += dx; }
+ void move_y(float dy) { y_ += dy; }
+ void move_z(float dz) { z_ += dz; }
+ void move(float dx, float dy, float dz) {
+ x_ += dx;
+ y_ += dy;
+ z_ += dz;
+ }
+
+ void scale_x(float sx) { x_ *= sx; }
+ void scale_y(float sy) { y_ *= sy; }
+ void scale_z(float sz) { z_ *= sz; }
+ void scale(float sx, float sy, float sz) {
+ x_ *= sx;
+ y_ *= sy;
+ z_ *= sz;
+ }
+ void scale(float s) { scale(s, s, s); }
+
+ gfx::PointF Intersect(const Edge& e) const {
+ return gfx::PointF(
+ (y() * e.z() - e.y() * z()) / (x() * e.y() - e.x() * y()),
+ (x() * e.z() - e.x() * z()) / (e.x() * y() - x() * e.y()));
+ }
+
+ private:
+ float x_;
+ float y_;
+ float z_;
+ };
+
+ LayerQuad(const Edge& left,
+ const Edge& top,
+ const Edge& right,
+ const Edge& bottom);
+ explicit LayerQuad(const gfx::QuadF& quad);
+
+ Edge left() const { return left_; }
+ Edge top() const { return top_; }
+ Edge right() const { return right_; }
+ Edge bottom() const { return bottom_; }
+
+ void InflateX(float dx) {
+ left_.move_z(dx);
+ right_.move_z(dx);
+ }
+ void InflateY(float dy) {
+ top_.move_z(dy);
+ bottom_.move_z(dy);
+ }
+ void Inflate(float d) {
+ InflateX(d);
+ InflateY(d);
+ }
+ void InflateAntiAliasingDistance() {
+ Inflate(kAntiAliasingInflateDistance);
+ }
+
+ gfx::QuadF ToQuadF() const;
+
+ void ToFloatArray(float flattened[12]) const;
+
+ private:
+ Edge left_;
+ Edge top_;
+ Edge right_;
+ Edge bottom_;
+
+ DISALLOW_COPY_AND_ASSIGN(LayerQuad);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_LAYER_QUAD_H_
diff --git a/chromium/cc/resources/layer_quad_unittest.cc b/chromium/cc/resources/layer_quad_unittest.cc
new file mode 100644
index 00000000000..8d3909cb586
--- /dev/null
+++ b/chromium/cc/resources/layer_quad_unittest.cc
@@ -0,0 +1,42 @@
+// Copyright 2011 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 "cc/resources/layer_quad.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/quad_f.h"
+
+namespace cc {
+namespace {
+
+TEST(LayerQuadTest, QuadFConversion) {
+ gfx::PointF p1(-0.5f, -0.5f);
+ gfx::PointF p2(0.5f, -0.5f);
+ gfx::PointF p3(0.5f, 0.5f);
+ gfx::PointF p4(-0.5f, 0.5f);
+
+ gfx::QuadF quad_cw(p1, p2, p3, p4);
+ LayerQuad layer_quad_cw(quad_cw);
+ EXPECT_EQ(layer_quad_cw.ToQuadF(), quad_cw);
+
+ gfx::QuadF quad_ccw(p1, p4, p3, p2);
+ LayerQuad layer_quad_ccw(quad_ccw);
+ EXPECT_EQ(layer_quad_ccw.ToQuadF(), quad_ccw);
+}
+
+TEST(LayerQuadTest, Inflate) {
+ gfx::PointF p1(-0.5f, -0.5f);
+ gfx::PointF p2(0.5f, -0.5f);
+ gfx::PointF p3(0.5f, 0.5f);
+ gfx::PointF p4(-0.5f, 0.5f);
+
+ gfx::QuadF quad(p1, p2, p3, p4);
+ LayerQuad layer_quad(quad);
+ quad.Scale(2.f, 2.f);
+ layer_quad.Inflate(0.5f);
+ EXPECT_EQ(layer_quad.ToQuadF(), quad);
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/resources/layer_tiling_data.cc b/chromium/cc/resources/layer_tiling_data.cc
new file mode 100644
index 00000000000..6b332341b8f
--- /dev/null
+++ b/chromium/cc/resources/layer_tiling_data.cc
@@ -0,0 +1,135 @@
+// Copyright 2011 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 "cc/resources/layer_tiling_data.h"
+
+#include <vector>
+
+#include "base/logging.h"
+
+namespace cc {
+
+scoped_ptr<LayerTilingData> LayerTilingData::Create(gfx::Size tile_size,
+ BorderTexelOption border) {
+ return make_scoped_ptr(new LayerTilingData(tile_size, border));
+}
+
+LayerTilingData::LayerTilingData(gfx::Size tile_size, BorderTexelOption border)
+ : tiling_data_(tile_size, gfx::Size(), border == HAS_BORDER_TEXELS) {
+ SetTileSize(tile_size);
+}
+
+LayerTilingData::~LayerTilingData() {}
+
+void LayerTilingData::SetTileSize(gfx::Size size) {
+ if (tile_size() == size)
+ return;
+
+ reset();
+
+ tiling_data_.SetMaxTextureSize(size);
+}
+
+gfx::Size LayerTilingData::tile_size() const {
+ return tiling_data_.max_texture_size();
+}
+
+void LayerTilingData::SetBorderTexelOption(
+ BorderTexelOption border_texel_option) {
+ bool border_texels = border_texel_option == HAS_BORDER_TEXELS;
+ if (has_border_texels() == border_texels)
+ return;
+
+ reset();
+ tiling_data_.SetHasBorderTexels(border_texels);
+}
+
+const LayerTilingData& LayerTilingData::operator=
+ (const LayerTilingData & tiler) {
+ tiling_data_ = tiler.tiling_data_;
+
+ return *this;
+}
+
+void LayerTilingData::AddTile(scoped_ptr<Tile> tile, int i, int j) {
+ DCHECK(!TileAt(i, j));
+ tile->move_to(i, j);
+ tiles_.add(std::make_pair(i, j), tile.Pass());
+}
+
+scoped_ptr<LayerTilingData::Tile> LayerTilingData::TakeTile(int i, int j) {
+ return tiles_.take_and_erase(std::make_pair(i, j));
+}
+
+LayerTilingData::Tile* LayerTilingData::TileAt(int i, int j) const {
+ return tiles_.get(std::make_pair(i, j));
+}
+
+void LayerTilingData::ContentRectToTileIndices(gfx::Rect content_rect,
+ int* left,
+ int* top,
+ int* right,
+ int* bottom) const {
+ // An empty rect doesn't result in an empty set of tiles, so don't pass an
+ // empty rect.
+ // TODO(enne): Possibly we should fill a vector of tiles instead, since the
+ // normal use of this function is to enumerate some tiles.
+ DCHECK(!content_rect.IsEmpty());
+
+ *left = tiling_data_.TileXIndexFromSrcCoord(content_rect.x());
+ *top = tiling_data_.TileYIndexFromSrcCoord(content_rect.y());
+ *right = tiling_data_.TileXIndexFromSrcCoord(content_rect.right() - 1);
+ *bottom = tiling_data_.TileYIndexFromSrcCoord(content_rect.bottom() - 1);
+}
+
+gfx::Rect LayerTilingData::TileRect(const Tile* tile) const {
+ gfx::Rect tile_rect = tiling_data_.TileBoundsWithBorder(tile->i(), tile->j());
+ tile_rect.set_size(tile_size());
+ return tile_rect;
+}
+
+Region LayerTilingData::OpaqueRegionInContentRect(
+ gfx::Rect content_rect) const {
+ if (content_rect.IsEmpty())
+ return Region();
+
+ Region opaque_region;
+ int left, top, right, bottom;
+ ContentRectToTileIndices(content_rect, &left, &top, &right, &bottom);
+ for (int j = top; j <= bottom; ++j) {
+ for (int i = left; i <= right; ++i) {
+ Tile* tile = TileAt(i, j);
+ if (!tile)
+ continue;
+
+ gfx::Rect tile_opaque_rect =
+ gfx::IntersectRects(content_rect, tile->opaque_rect());
+ opaque_region.Union(tile_opaque_rect);
+ }
+ }
+ return opaque_region;
+}
+
+void LayerTilingData::SetBounds(gfx::Size size) {
+ tiling_data_.SetTotalSize(size);
+ if (size.IsEmpty()) {
+ tiles_.clear();
+ return;
+ }
+
+ // Any tiles completely outside our new bounds are invalid and should be
+ // dropped.
+ int left, top, right, bottom;
+ ContentRectToTileIndices(
+ gfx::Rect(size), &left, &top, &right, &bottom);
+ std::vector<TileMapKey> invalid_tile_keys;
+ for (TileMap::const_iterator it = tiles_.begin(); it != tiles_.end(); ++it) {
+ if (it->first.first > right || it->first.second > bottom)
+ invalid_tile_keys.push_back(it->first);
+ }
+ for (size_t i = 0; i < invalid_tile_keys.size(); ++i)
+ tiles_.erase(invalid_tile_keys[i]);
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/layer_tiling_data.h b/chromium/cc/resources/layer_tiling_data.h
new file mode 100644
index 00000000000..51565eba3b2
--- /dev/null
+++ b/chromium/cc/resources/layer_tiling_data.h
@@ -0,0 +1,107 @@
+// Copyright 2011 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.
+
+#ifndef CC_RESOURCES_LAYER_TILING_DATA_H_
+#define CC_RESOURCES_LAYER_TILING_DATA_H_
+
+#include <utility>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/base/region.h"
+#include "cc/base/scoped_ptr_hash_map.h"
+#include "cc/base/tiling_data.h"
+#include "ui/gfx/rect.h"
+
+namespace cc {
+
+class CC_EXPORT LayerTilingData {
+ public:
+ enum BorderTexelOption {
+ HAS_BORDER_TEXELS,
+ NO_BORDER_TEXELS
+ };
+
+ ~LayerTilingData();
+
+ static scoped_ptr<LayerTilingData> Create(gfx::Size tile_size,
+ BorderTexelOption option);
+
+ bool has_empty_bounds() const { return tiling_data_.has_empty_bounds(); }
+ int num_tiles_x() const { return tiling_data_.num_tiles_x(); }
+ int num_tiles_y() const { return tiling_data_.num_tiles_y(); }
+ gfx::Rect tile_bounds(int i, int j) const {
+ return tiling_data_.TileBounds(i, j);
+ }
+ gfx::Vector2d texture_offset(int x_index, int y_index) const {
+ return tiling_data_.TextureOffset(x_index, y_index);
+ }
+
+ // Change the tile size. This may invalidate all the existing tiles.
+ void SetTileSize(gfx::Size size);
+ gfx::Size tile_size() const;
+ // Change the border texel setting. This may invalidate all existing tiles.
+ void SetBorderTexelOption(BorderTexelOption option);
+ bool has_border_texels() const { return !!tiling_data_.border_texels(); }
+
+ bool is_empty() const { return has_empty_bounds() || !tiles().size(); }
+
+ const LayerTilingData& operator=(const LayerTilingData&);
+
+ class Tile {
+ public:
+ Tile() : i_(-1), j_(-1) {}
+ virtual ~Tile() {}
+
+ int i() const { return i_; }
+ int j() const { return j_; }
+ void move_to(int i, int j) {
+ i_ = i;
+ j_ = j;
+ }
+
+ gfx::Rect opaque_rect() const { return opaque_rect_; }
+ void set_opaque_rect(gfx::Rect opaque_rect) { opaque_rect_ = opaque_rect; }
+ private:
+ int i_;
+ int j_;
+ gfx::Rect opaque_rect_;
+ DISALLOW_COPY_AND_ASSIGN(Tile);
+ };
+ typedef std::pair<int, int> TileMapKey;
+ typedef ScopedPtrHashMap<TileMapKey, Tile> TileMap;
+
+ void AddTile(scoped_ptr<Tile> tile, int i, int j);
+ scoped_ptr<Tile> TakeTile(int i, int j);
+ Tile* TileAt(int i, int j) const;
+ const TileMap& tiles() const { return tiles_; }
+
+ void SetBounds(gfx::Size size);
+ gfx::Size bounds() const { return tiling_data_.total_size(); }
+
+ void ContentRectToTileIndices(gfx::Rect rect,
+ int* left,
+ int* top,
+ int* right,
+ int* bottom) const;
+ gfx::Rect TileRect(const Tile* tile) const;
+
+ Region OpaqueRegionInContentRect(gfx::Rect rect) const;
+
+ void reset() { tiles_.clear(); }
+
+ protected:
+ LayerTilingData(gfx::Size tile_size, BorderTexelOption option);
+
+ TileMap tiles_;
+ TilingData tiling_data_;
+
+ DISALLOW_COPY(LayerTilingData);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_LAYER_TILING_DATA_H_
diff --git a/chromium/cc/resources/layer_updater.cc b/chromium/cc/resources/layer_updater.cc
new file mode 100644
index 00000000000..aa7d1a4622e
--- /dev/null
+++ b/chromium/cc/resources/layer_updater.cc
@@ -0,0 +1,16 @@
+// 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 "cc/resources/layer_updater.h"
+
+#include "cc/resources/prioritized_resource.h"
+
+namespace cc {
+
+LayerUpdater::Resource::Resource(scoped_ptr<PrioritizedResource> texture)
+ : texture_(texture.Pass()) {}
+
+LayerUpdater::Resource::~Resource() {}
+
+} // namespace cc
diff --git a/chromium/cc/resources/layer_updater.h b/chromium/cc/resources/layer_updater.h
new file mode 100644
index 00000000000..5d8eb89ad4e
--- /dev/null
+++ b/chromium/cc/resources/layer_updater.h
@@ -0,0 +1,73 @@
+// Copyright 2011 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.
+
+#ifndef CC_RESOURCES_LAYER_UPDATER_H_
+#define CC_RESOURCES_LAYER_UPDATER_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/vector2d.h"
+
+namespace cc {
+
+class PrioritizedResource;
+class PrioritizedResourceManager;
+class ResourceUpdateQueue;
+class TextureManager;
+
+class CC_EXPORT LayerUpdater : public base::RefCounted<LayerUpdater> {
+ public:
+ // Allows updaters to store per-resource update properties.
+ class CC_EXPORT Resource {
+ public:
+ virtual ~Resource();
+
+ PrioritizedResource* texture() { return texture_.get(); }
+ // TODO(reveman): partial_update should be a property of this class
+ // instead of an argument passed to Update().
+ virtual void Update(ResourceUpdateQueue* queue,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset,
+ bool partial_update) = 0;
+ protected:
+ explicit Resource(scoped_ptr<PrioritizedResource> texture);
+
+ private:
+ scoped_ptr<PrioritizedResource> texture_;
+
+ DISALLOW_COPY_AND_ASSIGN(Resource);
+ };
+
+ LayerUpdater() {}
+
+ virtual scoped_ptr<Resource> CreateResource(
+ PrioritizedResourceManager* manager) = 0;
+ // The |resulting_opaque_rect| gives back a region of the layer that was
+ // painted opaque. If the layer is marked opaque in the updater, then this
+ // region should be ignored in preference for the entire layer's area.
+ virtual void PrepareToUpdate(gfx::Rect content_rect,
+ gfx::Size tile_size,
+ float contents_width_scale,
+ float contents_height_scale,
+ gfx::Rect* resulting_opaque_rect) {}
+ virtual void ReduceMemoryUsage() {}
+
+ // Set true by the layer when it is known that the entire output is going to
+ // be opaque.
+ virtual void SetOpaque(bool opaque) {}
+
+ protected:
+ virtual ~LayerUpdater() {}
+
+ private:
+ friend class base::RefCounted<LayerUpdater>;
+
+ DISALLOW_COPY_AND_ASSIGN(LayerUpdater);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_LAYER_UPDATER_H_
diff --git a/chromium/cc/resources/managed_tile_state.cc b/chromium/cc/resources/managed_tile_state.cc
new file mode 100644
index 00000000000..6f85331dbb2
--- /dev/null
+++ b/chromium/cc/resources/managed_tile_state.cc
@@ -0,0 +1,131 @@
+// Copyright 2013 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 "cc/resources/managed_tile_state.h"
+
+#include <limits>
+
+#include "cc/base/math_util.h"
+
+namespace cc {
+
+scoped_ptr<base::Value> ManagedTileBinAsValue(ManagedTileBin bin) {
+ switch (bin) {
+ case NOW_AND_READY_TO_DRAW_BIN:
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "NOW_AND_READY_TO_DRAW_BIN"));
+ case NOW_BIN:
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "NOW_BIN"));
+ case SOON_BIN:
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "SOON_BIN"));
+ case EVENTUALLY_AND_ACTIVE_BIN:
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "EVENTUALLY_AND_ACTIVE_BIN"));
+ case EVENTUALLY_BIN:
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "EVENTUALLY_BIN"));
+ case NEVER_AND_ACTIVE_BIN:
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "NEVER_AND_ACTIVE_BIN"));
+ case NEVER_BIN:
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "NEVER_BIN"));
+ default:
+ DCHECK(false) << "Unrecognized ManagedTileBin value " << bin;
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "<unknown ManagedTileBin value>"));
+ }
+}
+
+scoped_ptr<base::Value> ManagedTileBinPriorityAsValue(
+ ManagedTileBinPriority bin_priority) {
+ switch (bin_priority) {
+ case HIGH_PRIORITY_BIN:
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "HIGH_PRIORITY_BIN"));
+ case LOW_PRIORITY_BIN:
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "LOW_PRIORITY_BIN"));
+ default:
+ DCHECK(false) << "Unrecognized ManagedTileBinPriority value";
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "<unknown ManagedTileBinPriority value>"));
+ }
+}
+
+ManagedTileState::ManagedTileState()
+ : raster_mode(LOW_QUALITY_RASTER_MODE),
+ gpu_memmgr_stats_bin(NEVER_BIN),
+ resolution(NON_IDEAL_RESOLUTION),
+ required_for_activation(false),
+ time_to_needed_in_seconds(std::numeric_limits<float>::infinity()),
+ distance_to_visible_in_pixels(std::numeric_limits<float>::infinity()),
+ visible_and_ready_to_draw(false),
+ scheduled_priority(0) {
+ for (int i = 0; i < NUM_TREES; ++i) {
+ tree_bin[i] = NEVER_BIN;
+ bin[i] = NEVER_BIN;
+ }
+}
+
+ManagedTileState::TileVersion::TileVersion()
+ : mode_(RESOURCE_MODE),
+ has_text_(false) {
+}
+
+ManagedTileState::TileVersion::~TileVersion() {
+ DCHECK(!resource_);
+}
+
+bool ManagedTileState::TileVersion::IsReadyToDraw() const {
+ switch (mode_) {
+ case RESOURCE_MODE:
+ return !!resource_;
+ case SOLID_COLOR_MODE:
+ case PICTURE_PILE_MODE:
+ return true;
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+size_t ManagedTileState::TileVersion::GPUMemoryUsageInBytes() const {
+ if (!resource_)
+ return 0;
+ return resource_->bytes();
+}
+
+ManagedTileState::~ManagedTileState() {
+}
+
+scoped_ptr<base::Value> ManagedTileState::AsValue() const {
+ scoped_ptr<base::DictionaryValue> state(new base::DictionaryValue());
+ state->SetBoolean("has_resource",
+ tile_versions[raster_mode].resource_.get() != 0);
+ state->Set("bin.0", ManagedTileBinAsValue(bin[ACTIVE_TREE]).release());
+ state->Set("bin.1", ManagedTileBinAsValue(bin[PENDING_TREE]).release());
+ state->Set("gpu_memmgr_stats_bin",
+ ManagedTileBinAsValue(bin[ACTIVE_TREE]).release());
+ state->Set("resolution", TileResolutionAsValue(resolution).release());
+ state->Set("time_to_needed_in_seconds",
+ MathUtil::AsValueSafely(time_to_needed_in_seconds).release());
+ state->Set("distance_to_visible_in_pixels",
+ MathUtil::AsValueSafely(distance_to_visible_in_pixels).release());
+ state->SetBoolean("required_for_activation", required_for_activation);
+ state->SetBoolean(
+ "is_solid_color",
+ tile_versions[raster_mode].mode_ == TileVersion::SOLID_COLOR_MODE);
+ state->SetBoolean(
+ "is_transparent",
+ tile_versions[raster_mode].mode_ == TileVersion::SOLID_COLOR_MODE &&
+ !SkColorGetA(tile_versions[raster_mode].solid_color_));
+ state->SetInteger("scheduled_priority", scheduled_priority);
+ return state.PassAs<base::Value>();
+}
+
+} // namespace cc
+
diff --git a/chromium/cc/resources/managed_tile_state.h b/chromium/cc/resources/managed_tile_state.h
new file mode 100644
index 00000000000..c6065f97895
--- /dev/null
+++ b/chromium/cc/resources/managed_tile_state.h
@@ -0,0 +1,168 @@
+// Copyright 2013 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.
+
+#ifndef CC_RESOURCES_MANAGED_TILE_STATE_H_
+#define CC_RESOURCES_MANAGED_TILE_STATE_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/resources/platform_color.h"
+#include "cc/resources/raster_worker_pool.h"
+#include "cc/resources/resource_pool.h"
+#include "cc/resources/resource_provider.h"
+
+namespace cc {
+
+class TileManager;
+
+// Tile manager classifying tiles into a few basic bins:
+enum ManagedTileBin {
+ NOW_AND_READY_TO_DRAW_BIN = 0, // Ready to draw and within viewport.
+ NOW_BIN = 1, // Needed ASAP.
+ SOON_BIN = 2, // Impl-side version of prepainting.
+ EVENTUALLY_AND_ACTIVE_BIN = 3, // Nice to have, and has a task or resource.
+ EVENTUALLY_BIN = 4, // Nice to have, if we've got memory and time.
+ NEVER_AND_ACTIVE_BIN = 5, // Dont bother, but has a task or resource.
+ NEVER_BIN = 6, // Dont bother.
+ NUM_BINS = 7
+ // NOTE: Be sure to update ManagedTileBinAsValue and kBinPolicyMap when adding
+ // or reordering fields.
+};
+scoped_ptr<base::Value> ManagedTileBinAsValue(
+ ManagedTileBin bin);
+
+enum ManagedTileBinPriority {
+ HIGH_PRIORITY_BIN = 0,
+ LOW_PRIORITY_BIN = 1,
+ NUM_BIN_PRIORITIES = 2
+};
+scoped_ptr<base::Value> ManagedTileBinPriorityAsValue(
+ ManagedTileBinPriority bin);
+
+// This is state that is specific to a tile that is
+// managed by the TileManager.
+class CC_EXPORT ManagedTileState {
+ public:
+ class CC_EXPORT TileVersion {
+ public:
+ enum Mode {
+ RESOURCE_MODE,
+ SOLID_COLOR_MODE,
+ PICTURE_PILE_MODE,
+ NUM_MODES
+ };
+
+ TileVersion();
+ ~TileVersion();
+
+ Mode mode() const {
+ return mode_;
+ }
+
+ bool IsReadyToDraw() const;
+
+ ResourceProvider::ResourceId get_resource_id() const {
+ DCHECK(mode_ == RESOURCE_MODE);
+ DCHECK(resource_);
+
+ return resource_->id();
+ }
+
+ SkColor get_solid_color() const {
+ DCHECK(mode_ == SOLID_COLOR_MODE);
+
+ return solid_color_;
+ }
+
+ bool contents_swizzled() const {
+ DCHECK(resource_);
+ return !PlatformColor::SameComponentOrder(resource_->format());
+ }
+
+ bool requires_resource() const {
+ return mode_ == RESOURCE_MODE ||
+ mode_ == PICTURE_PILE_MODE;
+ }
+
+ size_t GPUMemoryUsageInBytes() const;
+
+ void SetResourceForTesting(scoped_ptr<ResourcePool::Resource> resource) {
+ resource_ = resource.Pass();
+ }
+ const ResourcePool::Resource* GetResourceForTesting() const {
+ return resource_.get();
+ }
+ void SetSolidColorForTesting(SkColor color) {
+ set_solid_color(color);
+ }
+ void SetHasTextForTesting(bool has_text) {
+ has_text_ = has_text;
+ }
+
+ private:
+ friend class TileManager;
+ friend class PrioritizedTileSet;
+ friend class Tile;
+ friend class ManagedTileState;
+
+ void set_use_resource() {
+ mode_ = RESOURCE_MODE;
+ }
+
+ void set_solid_color(const SkColor& color) {
+ mode_ = SOLID_COLOR_MODE;
+ solid_color_ = color;
+ }
+
+ void set_has_text(bool has_text) {
+ has_text_ = has_text;
+ }
+
+ void set_rasterize_on_demand() {
+ mode_ = PICTURE_PILE_MODE;
+ }
+
+ Mode mode_;
+ SkColor solid_color_;
+ bool has_text_;
+ scoped_ptr<ResourcePool::Resource> resource_;
+ RasterWorkerPool::RasterTask raster_task_;
+ };
+
+ ManagedTileState();
+ ~ManagedTileState();
+
+ scoped_ptr<base::Value> AsValue() const;
+
+ // Persisted state: valid all the time.
+ TileVersion tile_versions[NUM_RASTER_MODES];
+ RasterMode raster_mode;
+
+ // Ephemeral state, valid only during TileManager::ManageTiles.
+ bool is_in_never_bin_on_both_trees() const {
+ return (bin[HIGH_PRIORITY_BIN] == NEVER_BIN ||
+ bin[HIGH_PRIORITY_BIN] == NEVER_AND_ACTIVE_BIN) &&
+ (bin[LOW_PRIORITY_BIN] == NEVER_BIN ||
+ bin[LOW_PRIORITY_BIN] == NEVER_AND_ACTIVE_BIN);
+ }
+
+ ManagedTileBin bin[NUM_BIN_PRIORITIES];
+ ManagedTileBin tree_bin[NUM_TREES];
+
+ // The bin that the tile would have if the GPU memory manager had
+ // a maximally permissive policy, send to the GPU memory manager
+ // to determine policy.
+ ManagedTileBin gpu_memmgr_stats_bin;
+ TileResolution resolution;
+ bool required_for_activation;
+ float time_to_needed_in_seconds;
+ float distance_to_visible_in_pixels;
+ bool visible_and_ready_to_draw;
+
+ // Priority for this state from the last time we assigned memory.
+ unsigned scheduled_priority;
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_MANAGED_TILE_STATE_H_
diff --git a/chromium/cc/resources/memory_history.cc b/chromium/cc/resources/memory_history.cc
new file mode 100644
index 00000000000..a2f8b6ee113
--- /dev/null
+++ b/chromium/cc/resources/memory_history.cc
@@ -0,0 +1,39 @@
+// 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 "cc/resources/memory_history.h"
+
+#include <limits>
+
+namespace cc {
+
+// static
+scoped_ptr<MemoryHistory> MemoryHistory::Create() {
+ return make_scoped_ptr(new MemoryHistory());
+}
+
+MemoryHistory::MemoryHistory() {}
+
+void MemoryHistory::SaveEntry(const MemoryHistory::Entry& entry) {
+ ring_buffer_.SaveToBuffer(entry);
+}
+
+void MemoryHistory::GetMinAndMax(size_t* min, size_t* max) const {
+ *min = std::numeric_limits<size_t>::max();
+ *max = 0;
+
+ for (RingBufferType::Iterator it = ring_buffer_.Begin(); it; ++it) {
+ size_t bytes_total = it->bytes_total();
+
+ if (bytes_total < *min)
+ *min = bytes_total;
+ if (bytes_total > *max)
+ *max = bytes_total;
+ }
+
+ if (*min > *max)
+ *min = *max;
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/memory_history.h b/chromium/cc/resources/memory_history.h
new file mode 100644
index 00000000000..daca10f35a0
--- /dev/null
+++ b/chromium/cc/resources/memory_history.h
@@ -0,0 +1,55 @@
+// Copyright 2013 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.
+
+#ifndef CC_RESOURCES_MEMORY_HISTORY_H_
+#define CC_RESOURCES_MEMORY_HISTORY_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "cc/debug/ring_buffer.h"
+
+namespace cc {
+
+// Maintains a history of memory for each frame.
+class MemoryHistory {
+ public:
+ static scoped_ptr<MemoryHistory> Create();
+
+ size_t HistorySize() const { return ring_buffer_.BufferSize(); }
+
+ struct Entry {
+ Entry()
+ : total_budget_in_bytes(0),
+ bytes_allocated(0),
+ bytes_unreleasable(0),
+ bytes_over(0) {}
+
+ size_t total_budget_in_bytes;
+ size_t bytes_allocated;
+ size_t bytes_unreleasable;
+ size_t bytes_over;
+ size_t bytes_total() const {
+ return bytes_allocated + bytes_unreleasable + bytes_over;
+ }
+ };
+
+ void SaveEntry(const Entry& entry);
+ void GetMinAndMax(size_t* min, size_t* max) const;
+
+ typedef RingBuffer<Entry, 80> RingBufferType;
+ RingBufferType::Iterator Begin() const { return ring_buffer_.Begin(); }
+ RingBufferType::Iterator End() const { return ring_buffer_.End(); }
+
+ private:
+ MemoryHistory();
+
+ RingBufferType ring_buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(MemoryHistory);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_MEMORY_HISTORY_H_
diff --git a/chromium/cc/resources/picture.cc b/chromium/cc/resources/picture.cc
new file mode 100644
index 00000000000..8c5d8512ae3
--- /dev/null
+++ b/chromium/cc/resources/picture.cc
@@ -0,0 +1,472 @@
+// 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 "cc/resources/picture.h"
+
+#include <algorithm>
+#include <limits>
+#include <set>
+
+#include "base/base64.h"
+#include "base/debug/trace_event.h"
+#include "base/values.h"
+#include "cc/base/math_util.h"
+#include "cc/base/util.h"
+#include "cc/debug/benchmark_instrumentation.h"
+#include "cc/debug/rendering_stats_instrumentation.h"
+#include "cc/debug/traced_picture.h"
+#include "cc/debug/traced_value.h"
+#include "cc/layers/content_layer_client.h"
+#include "skia/ext/lazy_pixel_ref_utils.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkData.h"
+#include "third_party/skia/include/core/SkDrawFilter.h"
+#include "third_party/skia/include/core/SkPaint.h"
+#include "third_party/skia/include/core/SkStream.h"
+#include "third_party/skia/include/utils/SkPictureUtils.h"
+#include "ui/gfx/codec/jpeg_codec.h"
+#include "ui/gfx/codec/png_codec.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/skia_util.h"
+
+namespace cc {
+
+namespace {
+
+SkData* EncodeBitmap(size_t* offset, const SkBitmap& bm) {
+ const int kJpegQuality = 80;
+ std::vector<unsigned char> data;
+
+ // If bitmap is opaque, encode as JPEG.
+ // Otherwise encode as PNG.
+ bool encoding_succeeded = false;
+ if (bm.isOpaque()) {
+ SkAutoLockPixels lock_bitmap(bm);
+ if (bm.empty())
+ return NULL;
+
+ encoding_succeeded = gfx::JPEGCodec::Encode(
+ reinterpret_cast<unsigned char*>(bm.getAddr32(0, 0)),
+ gfx::JPEGCodec::FORMAT_SkBitmap,
+ bm.width(),
+ bm.height(),
+ bm.rowBytes(),
+ kJpegQuality,
+ &data);
+ } else {
+ encoding_succeeded = gfx::PNGCodec::EncodeBGRASkBitmap(bm, false, &data);
+ }
+
+ if (encoding_succeeded) {
+ *offset = 0;
+ return SkData::NewWithCopy(&data.front(), data.size());
+ }
+ return NULL;
+}
+
+bool DecodeBitmap(const void* buffer, size_t size, SkBitmap* bm) {
+ const unsigned char* data = static_cast<const unsigned char *>(buffer);
+
+ // Try PNG first.
+ if (gfx::PNGCodec::Decode(data, size, bm))
+ return true;
+
+ // Try JPEG.
+ scoped_ptr<SkBitmap> decoded_jpeg(gfx::JPEGCodec::Decode(data, size));
+ if (decoded_jpeg) {
+ *bm = *decoded_jpeg;
+ return true;
+ }
+ return false;
+}
+
+} // namespace
+
+scoped_refptr<Picture> Picture::Create(gfx::Rect layer_rect) {
+ return make_scoped_refptr(new Picture(layer_rect));
+}
+
+Picture::Picture(gfx::Rect layer_rect)
+ : layer_rect_(layer_rect) {
+ // Instead of recording a trace event for object creation here, we wait for
+ // the picture to be recorded in Picture::Record.
+}
+
+scoped_refptr<Picture> Picture::CreateFromValue(const base::Value* raw_value) {
+ const base::DictionaryValue* value = NULL;
+ if (!raw_value->GetAsDictionary(&value))
+ return NULL;
+
+ // Decode the picture from base64.
+ std::string encoded;
+ if (!value->GetString("skp64", &encoded))
+ return NULL;
+
+ std::string decoded;
+ base::Base64Decode(encoded, &decoded);
+ SkMemoryStream stream(decoded.data(), decoded.size());
+
+ const base::Value* layer_rect_value = NULL;
+ if (!value->Get("params.layer_rect", &layer_rect_value))
+ return NULL;
+
+ gfx::Rect layer_rect;
+ if (!MathUtil::FromValue(layer_rect_value, &layer_rect))
+ return NULL;
+
+ const base::Value* opaque_rect_value = NULL;
+ if (!value->Get("params.opaque_rect", &opaque_rect_value))
+ return NULL;
+
+ gfx::Rect opaque_rect;
+ if (!MathUtil::FromValue(opaque_rect_value, &opaque_rect))
+ return NULL;
+
+ // Read the picture. This creates an empty picture on failure.
+ SkPicture* skpicture = SkPicture::CreateFromStream(&stream, &DecodeBitmap);
+ if (skpicture == NULL)
+ return NULL;
+
+ return make_scoped_refptr(new Picture(skpicture, layer_rect, opaque_rect));
+}
+
+Picture::Picture(SkPicture* picture,
+ gfx::Rect layer_rect,
+ gfx::Rect opaque_rect) :
+ layer_rect_(layer_rect),
+ opaque_rect_(opaque_rect),
+ picture_(skia::AdoptRef(picture)) {
+}
+
+Picture::Picture(const skia::RefPtr<SkPicture>& picture,
+ gfx::Rect layer_rect,
+ gfx::Rect opaque_rect,
+ const PixelRefMap& pixel_refs) :
+ layer_rect_(layer_rect),
+ opaque_rect_(opaque_rect),
+ picture_(picture),
+ pixel_refs_(pixel_refs) {
+}
+
+Picture::~Picture() {
+ TRACE_EVENT_OBJECT_DELETED_WITH_ID(
+ TRACE_DISABLED_BY_DEFAULT("cc.debug"), "cc::Picture", this);
+}
+
+scoped_refptr<Picture> Picture::GetCloneForDrawingOnThread(
+ unsigned thread_index) const {
+ // SkPicture is not thread-safe to rasterize with, this returns a clone
+ // to rasterize with on a specific thread.
+ CHECK_GT(clones_.size(), thread_index);
+ return clones_[thread_index];
+}
+
+void Picture::CloneForDrawing(int num_threads) {
+ TRACE_EVENT1("cc", "Picture::CloneForDrawing", "num_threads", num_threads);
+
+ DCHECK(picture_);
+ scoped_ptr<SkPicture[]> clones(new SkPicture[num_threads]);
+ picture_->clone(&clones[0], num_threads);
+
+ clones_.clear();
+ for (int i = 0; i < num_threads; i++) {
+ scoped_refptr<Picture> clone = make_scoped_refptr(
+ new Picture(skia::AdoptRef(new SkPicture(clones[i])),
+ layer_rect_,
+ opaque_rect_,
+ pixel_refs_));
+ clones_.push_back(clone);
+
+ clone->EmitTraceSnapshot();
+ }
+}
+
+void Picture::Record(ContentLayerClient* painter,
+ const SkTileGridPicture::TileGridInfo& tile_grid_info,
+ RenderingStatsInstrumentation* stats_instrumentation) {
+ TRACE_EVENT1(benchmark_instrumentation::kCategory,
+ benchmark_instrumentation::kPictureRecord,
+ benchmark_instrumentation::kData, AsTraceableRecordData());
+
+ DCHECK(!tile_grid_info.fTileInterval.isEmpty());
+ picture_ = skia::AdoptRef(new SkTileGridPicture(
+ layer_rect_.width(), layer_rect_.height(), tile_grid_info));
+
+ SkCanvas* canvas = picture_->beginRecording(
+ layer_rect_.width(),
+ layer_rect_.height(),
+ SkPicture::kUsePathBoundsForClip_RecordingFlag |
+ SkPicture::kOptimizeForClippedPlayback_RecordingFlag);
+
+ canvas->save();
+ canvas->translate(SkFloatToScalar(-layer_rect_.x()),
+ SkFloatToScalar(-layer_rect_.y()));
+
+ SkRect layer_skrect = SkRect::MakeXYWH(layer_rect_.x(),
+ layer_rect_.y(),
+ layer_rect_.width(),
+ layer_rect_.height());
+ canvas->clipRect(layer_skrect);
+
+ gfx::RectF opaque_layer_rect;
+ base::TimeTicks start_time = stats_instrumentation->StartRecording();
+
+ painter->PaintContents(canvas, layer_rect_, &opaque_layer_rect);
+
+ base::TimeDelta duration = stats_instrumentation->EndRecording(start_time);
+ stats_instrumentation->AddRecord(duration,
+ layer_rect_.width() * layer_rect_.height());
+
+ canvas->restore();
+ picture_->endRecording();
+
+ opaque_rect_ = gfx::ToEnclosedRect(opaque_layer_rect);
+
+ EmitTraceSnapshot();
+}
+
+void Picture::GatherPixelRefs(
+ const SkTileGridPicture::TileGridInfo& tile_grid_info,
+ RenderingStatsInstrumentation* stats_instrumentation) {
+ TRACE_EVENT2("cc", "Picture::GatherPixelRefs",
+ "width", layer_rect_.width(),
+ "height", layer_rect_.height());
+
+ DCHECK(picture_);
+ cell_size_ = gfx::Size(
+ tile_grid_info.fTileInterval.width() +
+ 2 * tile_grid_info.fMargin.width(),
+ tile_grid_info.fTileInterval.height() +
+ 2 * tile_grid_info.fMargin.height());
+ DCHECK_GT(cell_size_.width(), 0);
+ DCHECK_GT(cell_size_.height(), 0);
+
+ int min_x = std::numeric_limits<int>::max();
+ int min_y = std::numeric_limits<int>::max();
+ int max_x = 0;
+ int max_y = 0;
+
+ base::TimeTicks start_time = stats_instrumentation->StartRecording();
+
+ skia::LazyPixelRefList pixel_refs;
+ skia::LazyPixelRefUtils::GatherPixelRefs(picture_.get(), &pixel_refs);
+ for (skia::LazyPixelRefList::const_iterator it = pixel_refs.begin();
+ it != pixel_refs.end();
+ ++it) {
+ gfx::Point min(
+ RoundDown(static_cast<int>(it->pixel_ref_rect.x()),
+ cell_size_.width()),
+ RoundDown(static_cast<int>(it->pixel_ref_rect.y()),
+ cell_size_.height()));
+ gfx::Point max(
+ RoundDown(static_cast<int>(std::ceil(it->pixel_ref_rect.right())),
+ cell_size_.width()),
+ RoundDown(static_cast<int>(std::ceil(it->pixel_ref_rect.bottom())),
+ cell_size_.height()));
+
+ for (int y = min.y(); y <= max.y(); y += cell_size_.height()) {
+ for (int x = min.x(); x <= max.x(); x += cell_size_.width()) {
+ PixelRefMapKey key(x, y);
+ pixel_refs_[key].push_back(it->lazy_pixel_ref);
+ }
+ }
+
+ min_x = std::min(min_x, min.x());
+ min_y = std::min(min_y, min.y());
+ max_x = std::max(max_x, max.x());
+ max_y = std::max(max_y, max.y());
+ }
+
+ base::TimeDelta duration = stats_instrumentation->EndRecording(start_time);
+ stats_instrumentation->AddImageGathering(duration);
+
+ min_pixel_cell_ = gfx::Point(min_x, min_y);
+ max_pixel_cell_ = gfx::Point(max_x, max_y);
+}
+
+void Picture::Raster(
+ SkCanvas* canvas,
+ SkDrawPictureCallback* callback,
+ gfx::Rect content_rect,
+ float contents_scale) {
+ TRACE_EVENT_BEGIN1(benchmark_instrumentation::kCategory,
+ benchmark_instrumentation::kPictureRaster,
+ "data",
+ AsTraceableRasterData(content_rect, contents_scale));
+
+ DCHECK(picture_);
+
+ canvas->save();
+ canvas->clipRect(gfx::RectToSkRect(content_rect));
+ canvas->scale(contents_scale, contents_scale);
+ canvas->translate(layer_rect_.x(), layer_rect_.y());
+ picture_->draw(canvas, callback);
+ SkIRect bounds;
+ canvas->getClipDeviceBounds(&bounds);
+ canvas->restore();
+ TRACE_EVENT_END1(benchmark_instrumentation::kCategory,
+ benchmark_instrumentation::kPictureRaster,
+ benchmark_instrumentation::kNumPixelsRasterized,
+ bounds.width() * bounds.height());
+}
+
+void Picture::Replay(SkCanvas* canvas) {
+ TRACE_EVENT_BEGIN0("cc", "Picture::Replay");
+ DCHECK(picture_);
+
+ picture_->draw(canvas);
+ SkIRect bounds;
+ canvas->getClipDeviceBounds(&bounds);
+ TRACE_EVENT_END1("cc", "Picture::Replay",
+ "num_pixels_replayed", bounds.width() * bounds.height());
+}
+
+scoped_ptr<base::Value> Picture::AsValue() const {
+ SkDynamicMemoryWStream stream;
+
+ // Serialize the picture.
+ picture_->serialize(&stream, &EncodeBitmap);
+
+ // Encode the picture as base64.
+ scoped_ptr<base::DictionaryValue> res(new base::DictionaryValue());
+ res->Set("params.layer_rect", MathUtil::AsValue(layer_rect_).release());
+ res->Set("params.opaque_rect", MathUtil::AsValue(opaque_rect_).release());
+
+ size_t serialized_size = stream.bytesWritten();
+ scoped_ptr<char[]> serialized_picture(new char[serialized_size]);
+ stream.copyTo(serialized_picture.get());
+ std::string b64_picture;
+ base::Base64Encode(std::string(serialized_picture.get(), serialized_size),
+ &b64_picture);
+ res->SetString("skp64", b64_picture);
+ return res.PassAs<base::Value>();
+}
+
+void Picture::EmitTraceSnapshot() {
+ TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
+ "cc::Picture", this, TracedPicture::AsTraceablePicture(this));
+}
+
+base::LazyInstance<Picture::PixelRefs>
+ Picture::PixelRefIterator::empty_pixel_refs_;
+
+Picture::PixelRefIterator::PixelRefIterator()
+ : picture_(NULL),
+ current_pixel_refs_(empty_pixel_refs_.Pointer()),
+ current_index_(0),
+ min_point_(-1, -1),
+ max_point_(-1, -1),
+ current_x_(0),
+ current_y_(0) {
+}
+
+Picture::PixelRefIterator::PixelRefIterator(
+ gfx::Rect query_rect,
+ const Picture* picture)
+ : picture_(picture),
+ current_pixel_refs_(empty_pixel_refs_.Pointer()),
+ current_index_(0) {
+ gfx::Rect layer_rect = picture->layer_rect_;
+ gfx::Size cell_size = picture->cell_size_;
+
+ // Early out if the query rect doesn't intersect this picture
+ if (!query_rect.Intersects(layer_rect)) {
+ min_point_ = gfx::Point(0, 0);
+ max_point_ = gfx::Point(0, 0);
+ current_x_ = 1;
+ current_y_ = 1;
+ return;
+ }
+
+ // First, subtract the layer origin as cells are stored in layer space.
+ query_rect.Offset(-layer_rect.OffsetFromOrigin());
+
+ // We have to find a cell_size aligned point that corresponds to
+ // query_rect. Point is a multiple of cell_size.
+ min_point_ = gfx::Point(
+ RoundDown(query_rect.x(), cell_size.width()),
+ RoundDown(query_rect.y(), cell_size.height()));
+ max_point_ = gfx::Point(
+ RoundDown(query_rect.right() - 1, cell_size.width()),
+ RoundDown(query_rect.bottom() - 1, cell_size.height()));
+
+ // Limit the points to known pixel ref boundaries.
+ min_point_ = gfx::Point(
+ std::max(min_point_.x(), picture->min_pixel_cell_.x()),
+ std::max(min_point_.y(), picture->min_pixel_cell_.y()));
+ max_point_ = gfx::Point(
+ std::min(max_point_.x(), picture->max_pixel_cell_.x()),
+ std::min(max_point_.y(), picture->max_pixel_cell_.y()));
+
+ // Make the current x be cell_size.width() less than min point, so that
+ // the first increment will point at min_point_.
+ current_x_ = min_point_.x() - cell_size.width();
+ current_y_ = min_point_.y();
+ if (current_y_ <= max_point_.y())
+ ++(*this);
+}
+
+Picture::PixelRefIterator::~PixelRefIterator() {
+}
+
+Picture::PixelRefIterator& Picture::PixelRefIterator::operator++() {
+ ++current_index_;
+ // If we're not at the end of the list, then we have the next item.
+ if (current_index_ < current_pixel_refs_->size())
+ return *this;
+
+ DCHECK(current_y_ <= max_point_.y());
+ while (true) {
+ gfx::Size cell_size = picture_->cell_size_;
+
+ // Advance the current grid cell.
+ current_x_ += cell_size.width();
+ if (current_x_ > max_point_.x()) {
+ current_y_ += cell_size.height();
+ current_x_ = min_point_.x();
+ if (current_y_ > max_point_.y()) {
+ current_pixel_refs_ = empty_pixel_refs_.Pointer();
+ current_index_ = 0;
+ break;
+ }
+ }
+
+ // If there are no pixel refs at this grid cell, keep incrementing.
+ PixelRefMapKey key(current_x_, current_y_);
+ PixelRefMap::const_iterator iter = picture_->pixel_refs_.find(key);
+ if (iter == picture_->pixel_refs_.end())
+ continue;
+
+ // We found a non-empty list: store it and get the first pixel ref.
+ current_pixel_refs_ = &iter->second;
+ current_index_ = 0;
+ break;
+ }
+ return *this;
+}
+
+scoped_ptr<base::debug::ConvertableToTraceFormat>
+ Picture::AsTraceableRasterData(gfx::Rect rect, float scale) const {
+ scoped_ptr<base::DictionaryValue> raster_data(new base::DictionaryValue());
+ raster_data->Set("picture_id", TracedValue::CreateIDRef(this).release());
+ raster_data->SetDouble("scale", scale);
+ raster_data->SetDouble("rect_x", rect.x());
+ raster_data->SetDouble("rect_y", rect.y());
+ raster_data->SetDouble("rect_width", rect.width());
+ raster_data->SetDouble("rect_height", rect.height());
+ return TracedValue::FromValue(raster_data.release());
+}
+
+scoped_ptr<base::debug::ConvertableToTraceFormat>
+ Picture::AsTraceableRecordData() const {
+ scoped_ptr<base::DictionaryValue> record_data(new base::DictionaryValue());
+ record_data->Set("picture_id", TracedValue::CreateIDRef(this).release());
+ record_data->SetInteger(benchmark_instrumentation::kWidth,
+ layer_rect_.width());
+ record_data->SetInteger(benchmark_instrumentation::kHeight,
+ layer_rect_.height());
+ return TracedValue::FromValue(record_data.release());
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/picture.h b/chromium/cc/resources/picture.h
new file mode 100644
index 00000000000..15a6c9d0ab4
--- /dev/null
+++ b/chromium/cc/resources/picture.h
@@ -0,0 +1,157 @@
+// 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.
+
+#ifndef CC_RESOURCES_PICTURE_H_
+#define CC_RESOURCES_PICTURE_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/debug/trace_event.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "skia/ext/lazy_pixel_ref.h"
+#include "skia/ext/refptr.h"
+#include "third_party/skia/include/core/SkPixelRef.h"
+#include "third_party/skia/include/core/SkTileGridPicture.h"
+#include "ui/gfx/rect.h"
+
+namespace base {
+class Value;
+}
+
+namespace skia {
+class AnalysisCanvas;
+}
+
+namespace cc {
+
+class ContentLayerClient;
+class RenderingStatsInstrumentation;
+
+class CC_EXPORT Picture
+ : public base::RefCountedThreadSafe<Picture> {
+ public:
+ typedef std::pair<int, int> PixelRefMapKey;
+ typedef std::vector<skia::LazyPixelRef*> PixelRefs;
+ typedef base::hash_map<PixelRefMapKey, PixelRefs> PixelRefMap;
+
+ static scoped_refptr<Picture> Create(gfx::Rect layer_rect);
+ static scoped_refptr<Picture> CreateFromValue(const base::Value* value);
+
+ gfx::Rect LayerRect() const { return layer_rect_; }
+ gfx::Rect OpaqueRect() const { return opaque_rect_; }
+
+ // Get thread-safe clone for rasterizing with on a specific thread.
+ scoped_refptr<Picture> GetCloneForDrawingOnThread(
+ unsigned thread_index) const;
+
+ // Make thread-safe clones for rasterizing with.
+ void CloneForDrawing(int num_threads);
+
+ // Record a paint operation. To be able to safely use this SkPicture for
+ // playback on a different thread this can only be called once.
+ void Record(ContentLayerClient* client,
+ const SkTileGridPicture::TileGridInfo& tile_grid_info,
+ RenderingStatsInstrumentation* stats_instrumentation);
+
+ // Gather pixel refs from recording.
+ void GatherPixelRefs(const SkTileGridPicture::TileGridInfo& tile_grid_info,
+ RenderingStatsInstrumentation* stats_instrumentation);
+
+ // Has Record() been called yet?
+ bool HasRecording() const { return picture_.get() != NULL; }
+
+ // Apply this contents scale and raster the content rect into the canvas.
+ void Raster(SkCanvas* canvas,
+ SkDrawPictureCallback* callback,
+ gfx::Rect content_rect,
+ float contents_scale);
+
+ // Draw the picture directly into the given canvas, without applying any
+ // clip/scale/layer transformations.
+ void Replay(SkCanvas* canvas);
+
+ scoped_ptr<base::Value> AsValue() const;
+
+ class CC_EXPORT PixelRefIterator {
+ public:
+ PixelRefIterator();
+ PixelRefIterator(gfx::Rect layer_rect, const Picture* picture);
+ ~PixelRefIterator();
+
+ skia::LazyPixelRef* operator->() const {
+ DCHECK_LT(current_index_, current_pixel_refs_->size());
+ return (*current_pixel_refs_)[current_index_];
+ }
+
+ skia::LazyPixelRef* operator*() const {
+ DCHECK_LT(current_index_, current_pixel_refs_->size());
+ return (*current_pixel_refs_)[current_index_];
+ }
+
+ PixelRefIterator& operator++();
+ operator bool() const {
+ return current_index_ < current_pixel_refs_->size();
+ }
+
+ private:
+ static base::LazyInstance<PixelRefs> empty_pixel_refs_;
+ const Picture* picture_;
+ const PixelRefs* current_pixel_refs_;
+ unsigned current_index_;
+
+ gfx::Point min_point_;
+ gfx::Point max_point_;
+ int current_x_;
+ int current_y_;
+ };
+
+ void EmitTraceSnapshot();
+
+ private:
+ explicit Picture(gfx::Rect layer_rect);
+ // This constructor assumes SkPicture is already ref'd and transfers
+ // ownership to this picture.
+ Picture(const skia::RefPtr<SkPicture>&,
+ gfx::Rect layer_rect,
+ gfx::Rect opaque_rect,
+ const PixelRefMap& pixel_refs);
+ // This constructor will call AdoptRef on the SkPicture.
+ Picture(SkPicture*,
+ gfx::Rect layer_rect,
+ gfx::Rect opaque_rect);
+ ~Picture();
+
+ gfx::Rect layer_rect_;
+ gfx::Rect opaque_rect_;
+ skia::RefPtr<SkPicture> picture_;
+
+ typedef std::vector<scoped_refptr<Picture> > PictureVector;
+ PictureVector clones_;
+
+ PixelRefMap pixel_refs_;
+ gfx::Point min_pixel_cell_;
+ gfx::Point max_pixel_cell_;
+ gfx::Size cell_size_;
+
+ scoped_ptr<base::debug::ConvertableToTraceFormat>
+ AsTraceableRasterData(gfx::Rect rect, float scale) const;
+ scoped_ptr<base::debug::ConvertableToTraceFormat>
+ AsTraceableRecordData() const;
+
+ friend class base::RefCountedThreadSafe<Picture>;
+ friend class PixelRefIterator;
+ DISALLOW_COPY_AND_ASSIGN(Picture);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_PICTURE_H_
diff --git a/chromium/cc/resources/picture_layer_tiling.cc b/chromium/cc/resources/picture_layer_tiling.cc
new file mode 100644
index 00000000000..12bfb18b441
--- /dev/null
+++ b/chromium/cc/resources/picture_layer_tiling.cc
@@ -0,0 +1,815 @@
+// 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 "cc/resources/picture_layer_tiling.h"
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+
+#include "base/debug/trace_event.h"
+#include "cc/base/math_util.h"
+#include "ui/gfx/point_conversions.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/safe_integer_conversions.h"
+#include "ui/gfx/size_conversions.h"
+
+namespace cc {
+
+scoped_ptr<PictureLayerTiling> PictureLayerTiling::Create(
+ float contents_scale,
+ gfx::Size layer_bounds,
+ PictureLayerTilingClient* client) {
+ return make_scoped_ptr(new PictureLayerTiling(contents_scale,
+ layer_bounds,
+ client));
+}
+
+PictureLayerTiling::PictureLayerTiling(float contents_scale,
+ gfx::Size layer_bounds,
+ PictureLayerTilingClient* client)
+ : contents_scale_(contents_scale),
+ layer_bounds_(layer_bounds),
+ resolution_(NON_IDEAL_RESOLUTION),
+ client_(client),
+ tiling_data_(gfx::Size(), gfx::Size(), true),
+ last_impl_frame_time_in_seconds_(0.0) {
+ gfx::Size content_bounds =
+ gfx::ToCeiledSize(gfx::ScaleSize(layer_bounds, contents_scale));
+ gfx::Size tile_size = client_->CalculateTileSize(content_bounds);
+
+ DCHECK(!gfx::ToFlooredSize(
+ gfx::ScaleSize(layer_bounds, contents_scale)).IsEmpty()) <<
+ "Tiling created with scale too small as contents become empty." <<
+ " Layer bounds: " << layer_bounds.ToString() <<
+ " Contents scale: " << contents_scale;
+
+ tiling_data_.SetTotalSize(content_bounds);
+ tiling_data_.SetMaxTextureSize(tile_size);
+}
+
+PictureLayerTiling::~PictureLayerTiling() {
+}
+
+void PictureLayerTiling::SetClient(PictureLayerTilingClient* client) {
+ client_ = client;
+}
+
+gfx::Rect PictureLayerTiling::ContentRect() const {
+ return gfx::Rect(tiling_data_.total_size());
+}
+
+gfx::SizeF PictureLayerTiling::ContentSizeF() const {
+ return gfx::ScaleSize(layer_bounds_, contents_scale_);
+}
+
+Tile* PictureLayerTiling::TileAt(int i, int j) const {
+ TileMap::const_iterator iter = tiles_.find(TileMapKey(i, j));
+ if (iter == tiles_.end())
+ return NULL;
+ return iter->second.get();
+}
+
+void PictureLayerTiling::CreateTile(int i,
+ int j,
+ const PictureLayerTiling* twin_tiling) {
+ TileMapKey key(i, j);
+ DCHECK(tiles_.find(key) == tiles_.end());
+
+ gfx::Rect paint_rect = tiling_data_.TileBoundsWithBorder(i, j);
+ gfx::Rect tile_rect = paint_rect;
+ tile_rect.set_size(tiling_data_.max_texture_size());
+
+ // Check our twin for a valid tile.
+ if (twin_tiling &&
+ tiling_data_.max_texture_size() ==
+ twin_tiling->tiling_data_.max_texture_size()) {
+ if (Tile* candidate_tile = twin_tiling->TileAt(i, j)) {
+ gfx::Rect rect =
+ gfx::ScaleToEnclosingRect(paint_rect, 1.0f / contents_scale_);
+ if (!client_->GetInvalidation()->Intersects(rect)) {
+ tiles_[key] = candidate_tile;
+ return;
+ }
+ }
+ }
+
+ // Create a new tile because our twin didn't have a valid one.
+ scoped_refptr<Tile> tile = client_->CreateTile(this, tile_rect);
+ if (tile.get())
+ tiles_[key] = tile;
+}
+
+Region PictureLayerTiling::OpaqueRegionInContentRect(
+ gfx::Rect content_rect) const {
+ Region opaque_region;
+ // TODO(enne): implement me
+ return opaque_region;
+}
+
+void PictureLayerTiling::SetCanUseLCDText(bool can_use_lcd_text) {
+ for (TileMap::iterator it = tiles_.begin(); it != tiles_.end(); ++it)
+ it->second->set_can_use_lcd_text(can_use_lcd_text);
+}
+
+void PictureLayerTiling::CreateMissingTilesInLiveTilesRect() {
+ const PictureLayerTiling* twin_tiling = client_->GetTwinTiling(this);
+ for (TilingData::Iterator iter(&tiling_data_, live_tiles_rect_); iter;
+ ++iter) {
+ TileMapKey key = iter.index();
+ TileMap::iterator find = tiles_.find(key);
+ if (find != tiles_.end())
+ continue;
+ CreateTile(key.first, key.second, twin_tiling);
+ }
+}
+
+void PictureLayerTiling::SetLayerBounds(gfx::Size layer_bounds) {
+ if (layer_bounds_ == layer_bounds)
+ return;
+
+ DCHECK(!layer_bounds.IsEmpty());
+
+ gfx::Size old_layer_bounds = layer_bounds_;
+ layer_bounds_ = layer_bounds;
+ gfx::Size old_content_bounds = tiling_data_.total_size();
+ gfx::Size content_bounds =
+ gfx::ToCeiledSize(gfx::ScaleSize(layer_bounds_, contents_scale_));
+
+ gfx::Size tile_size = client_->CalculateTileSize(content_bounds);
+ if (tile_size != tiling_data_.max_texture_size()) {
+ tiling_data_.SetTotalSize(content_bounds);
+ tiling_data_.SetMaxTextureSize(tile_size);
+ Reset();
+ return;
+ }
+
+ // Any tiles outside our new bounds are invalid and should be dropped.
+ gfx::Rect bounded_live_tiles_rect(live_tiles_rect_);
+ bounded_live_tiles_rect.Intersect(gfx::Rect(content_bounds));
+ SetLiveTilesRect(bounded_live_tiles_rect);
+ tiling_data_.SetTotalSize(content_bounds);
+
+ // Create tiles for newly exposed areas.
+ Region layer_region((gfx::Rect(layer_bounds_)));
+ layer_region.Subtract(gfx::Rect(old_layer_bounds));
+ Invalidate(layer_region);
+}
+
+void PictureLayerTiling::Invalidate(const Region& layer_region) {
+ std::vector<TileMapKey> new_tile_keys;
+ for (Region::Iterator iter(layer_region); iter.has_rect(); iter.next()) {
+ gfx::Rect layer_rect = iter.rect();
+ gfx::Rect content_rect =
+ gfx::ScaleToEnclosingRect(layer_rect, contents_scale_);
+ content_rect.Intersect(live_tiles_rect_);
+ if (content_rect.IsEmpty())
+ continue;
+ for (TilingData::Iterator iter(&tiling_data_, content_rect); iter; ++iter) {
+ TileMapKey key(iter.index());
+ TileMap::iterator find = tiles_.find(key);
+ if (find == tiles_.end())
+ continue;
+ tiles_.erase(find);
+ new_tile_keys.push_back(key);
+ }
+ }
+
+ const PictureLayerTiling* twin_tiling = client_->GetTwinTiling(this);
+ for (size_t i = 0; i < new_tile_keys.size(); ++i)
+ CreateTile(new_tile_keys[i].first, new_tile_keys[i].second, twin_tiling);
+}
+
+PictureLayerTiling::CoverageIterator::CoverageIterator()
+ : tiling_(NULL),
+ current_tile_(NULL),
+ tile_i_(0),
+ tile_j_(0),
+ left_(0),
+ top_(0),
+ right_(-1),
+ bottom_(-1) {
+}
+
+PictureLayerTiling::CoverageIterator::CoverageIterator(
+ const PictureLayerTiling* tiling,
+ float dest_scale,
+ gfx::Rect dest_rect)
+ : tiling_(tiling),
+ dest_rect_(dest_rect),
+ dest_to_content_scale_(0),
+ current_tile_(NULL),
+ tile_i_(0),
+ tile_j_(0),
+ left_(0),
+ top_(0),
+ right_(-1),
+ bottom_(-1) {
+ DCHECK(tiling_);
+ if (dest_rect_.IsEmpty())
+ return;
+
+ dest_to_content_scale_ = tiling_->contents_scale_ / dest_scale;
+ // This is the maximum size that the dest rect can be, given the content size.
+ gfx::Size dest_content_size = gfx::ToCeiledSize(gfx::ScaleSize(
+ tiling_->ContentRect().size(),
+ 1 / dest_to_content_scale_,
+ 1 / dest_to_content_scale_));
+
+ gfx::Rect content_rect =
+ gfx::ScaleToEnclosingRect(dest_rect_,
+ dest_to_content_scale_,
+ dest_to_content_scale_);
+ // IndexFromSrcCoord clamps to valid tile ranges, so it's necessary to
+ // check for non-intersection first.
+ content_rect.Intersect(gfx::Rect(tiling_->tiling_data_.total_size()));
+ if (content_rect.IsEmpty())
+ return;
+
+ left_ = tiling_->tiling_data_.TileXIndexFromSrcCoord(content_rect.x());
+ top_ = tiling_->tiling_data_.TileYIndexFromSrcCoord(content_rect.y());
+ right_ = tiling_->tiling_data_.TileXIndexFromSrcCoord(
+ content_rect.right() - 1);
+ bottom_ = tiling_->tiling_data_.TileYIndexFromSrcCoord(
+ content_rect.bottom() - 1);
+
+ tile_i_ = left_ - 1;
+ tile_j_ = top_;
+ ++(*this);
+}
+
+PictureLayerTiling::CoverageIterator::~CoverageIterator() {
+}
+
+PictureLayerTiling::CoverageIterator&
+PictureLayerTiling::CoverageIterator::operator++() {
+ if (tile_j_ > bottom_)
+ return *this;
+
+ bool first_time = tile_i_ < left_;
+ bool new_row = false;
+ tile_i_++;
+ if (tile_i_ > right_) {
+ tile_i_ = left_;
+ tile_j_++;
+ new_row = true;
+ if (tile_j_ > bottom_) {
+ current_tile_ = NULL;
+ return *this;
+ }
+ }
+
+ current_tile_ = tiling_->TileAt(tile_i_, tile_j_);
+
+ // Calculate the current geometry rect. Due to floating point rounding
+ // and ToEnclosingRect, tiles might overlap in destination space on the
+ // edges.
+ gfx::Rect last_geometry_rect = current_geometry_rect_;
+
+ gfx::Rect content_rect = tiling_->tiling_data_.TileBounds(tile_i_, tile_j_);
+
+ current_geometry_rect_ =
+ gfx::ScaleToEnclosingRect(content_rect,
+ 1 / dest_to_content_scale_,
+ 1 / dest_to_content_scale_);
+
+ current_geometry_rect_.Intersect(dest_rect_);
+
+ if (first_time)
+ return *this;
+
+ // Iteration happens left->right, top->bottom. Running off the bottom-right
+ // edge is handled by the intersection above with dest_rect_. Here we make
+ // sure that the new current geometry rect doesn't overlap with the last.
+ int min_left;
+ int min_top;
+ if (new_row) {
+ min_left = dest_rect_.x();
+ min_top = last_geometry_rect.bottom();
+ } else {
+ min_left = last_geometry_rect.right();
+ min_top = last_geometry_rect.y();
+ }
+
+ int inset_left = std::max(0, min_left - current_geometry_rect_.x());
+ int inset_top = std::max(0, min_top - current_geometry_rect_.y());
+ current_geometry_rect_.Inset(inset_left, inset_top, 0, 0);
+
+ if (!new_row) {
+ DCHECK_EQ(last_geometry_rect.right(), current_geometry_rect_.x());
+ DCHECK_EQ(last_geometry_rect.bottom(), current_geometry_rect_.bottom());
+ DCHECK_EQ(last_geometry_rect.y(), current_geometry_rect_.y());
+ }
+
+ return *this;
+}
+
+gfx::Rect PictureLayerTiling::CoverageIterator::geometry_rect() const {
+ return current_geometry_rect_;
+}
+
+gfx::Rect
+PictureLayerTiling::CoverageIterator::full_tile_geometry_rect() const {
+ gfx::Rect rect = tiling_->tiling_data_.TileBoundsWithBorder(tile_i_, tile_j_);
+ rect.set_size(tiling_->tiling_data_.max_texture_size());
+ return rect;
+}
+
+gfx::RectF PictureLayerTiling::CoverageIterator::texture_rect() const {
+ gfx::PointF tex_origin =
+ tiling_->tiling_data_.TileBoundsWithBorder(tile_i_, tile_j_).origin();
+
+ // Convert from dest space => content space => texture space.
+ gfx::RectF texture_rect(current_geometry_rect_);
+ texture_rect.Scale(dest_to_content_scale_,
+ dest_to_content_scale_);
+ texture_rect.Offset(-tex_origin.OffsetFromOrigin());
+ texture_rect.Intersect(tiling_->ContentRect());
+
+ return texture_rect;
+}
+
+gfx::Size PictureLayerTiling::CoverageIterator::texture_size() const {
+ return tiling_->tiling_data_.max_texture_size();
+}
+
+void PictureLayerTiling::Reset() {
+ live_tiles_rect_ = gfx::Rect();
+ tiles_.clear();
+}
+
+namespace {
+
+bool NearlyOne(SkMScalar lhs) {
+ return std::abs(lhs-1.0) < std::numeric_limits<float>::epsilon();
+}
+
+bool NearlyZero(SkMScalar lhs) {
+ return std::abs(lhs) < std::numeric_limits<float>::epsilon();
+}
+
+bool ApproximatelyTranslation(const SkMatrix44& matrix) {
+ return
+ NearlyOne(matrix.get(0, 0)) &&
+ NearlyZero(matrix.get(1, 0)) &&
+ NearlyZero(matrix.get(2, 0)) &&
+ matrix.get(3, 0) == 0 &&
+ NearlyZero(matrix.get(0, 1)) &&
+ NearlyOne(matrix.get(1, 1)) &&
+ NearlyZero(matrix.get(2, 1)) &&
+ matrix.get(3, 1) == 0 &&
+ NearlyZero(matrix.get(0, 2)) &&
+ NearlyZero(matrix.get(1, 2)) &&
+ NearlyOne(matrix.get(2, 2)) &&
+ matrix.get(3, 2) == 0 &&
+ matrix.get(3, 3) == 1;
+}
+
+} // namespace
+
+void PictureLayerTiling::UpdateTilePriorities(
+ WhichTree tree,
+ gfx::Size device_viewport,
+ gfx::Rect viewport_in_layer_space,
+ gfx::Rect visible_layer_rect,
+ gfx::Size last_layer_bounds,
+ gfx::Size current_layer_bounds,
+ float last_layer_contents_scale,
+ float current_layer_contents_scale,
+ const gfx::Transform& last_screen_transform,
+ const gfx::Transform& current_screen_transform,
+ double current_frame_time_in_seconds,
+ size_t max_tiles_for_interest_area) {
+ if (!NeedsUpdateForFrameAtTime(current_frame_time_in_seconds)) {
+ // This should never be zero for the purposes of has_ever_been_updated().
+ DCHECK_NE(current_frame_time_in_seconds, 0.0);
+ return;
+ }
+ if (ContentRect().IsEmpty()) {
+ last_impl_frame_time_in_seconds_ = current_frame_time_in_seconds;
+ return;
+ }
+
+ gfx::Rect viewport_in_content_space =
+ gfx::ScaleToEnclosingRect(viewport_in_layer_space, contents_scale_);
+ gfx::Rect visible_content_rect =
+ gfx::ScaleToEnclosingRect(visible_layer_rect, contents_scale_);
+
+ gfx::Size tile_size = tiling_data_.max_texture_size();
+ int64 interest_rect_area =
+ max_tiles_for_interest_area * tile_size.width() * tile_size.height();
+
+ gfx::Rect starting_rect = visible_content_rect.IsEmpty()
+ ? viewport_in_content_space
+ : visible_content_rect;
+ gfx::Rect interest_rect = ExpandRectEquallyToAreaBoundedBy(
+ starting_rect,
+ interest_rect_area,
+ ContentRect());
+ DCHECK(interest_rect.IsEmpty() ||
+ ContentRect().Contains(interest_rect));
+
+ SetLiveTilesRect(interest_rect);
+
+ double time_delta = 0;
+ if (last_impl_frame_time_in_seconds_ != 0.0 &&
+ last_layer_bounds == current_layer_bounds) {
+ time_delta =
+ current_frame_time_in_seconds - last_impl_frame_time_in_seconds_;
+ }
+
+ gfx::Rect view_rect(device_viewport);
+ float current_scale = current_layer_contents_scale / contents_scale_;
+ float last_scale = last_layer_contents_scale / contents_scale_;
+
+ bool store_screen_space_quads_on_tiles;
+ TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
+ &store_screen_space_quads_on_tiles);
+
+ // Fast path tile priority calculation when both transforms are translations.
+ if (ApproximatelyTranslation(last_screen_transform.matrix()) &&
+ ApproximatelyTranslation(current_screen_transform.matrix())) {
+ gfx::Vector2dF current_offset(
+ current_screen_transform.matrix().get(0, 3),
+ current_screen_transform.matrix().get(1, 3));
+ gfx::Vector2dF last_offset(
+ last_screen_transform.matrix().get(0, 3),
+ last_screen_transform.matrix().get(1, 3));
+
+ for (TilingData::Iterator iter(&tiling_data_, interest_rect);
+ iter; ++iter) {
+ TileMap::iterator find = tiles_.find(iter.index());
+ if (find == tiles_.end())
+ continue;
+ Tile* tile = find->second.get();
+
+ gfx::Rect tile_bounds =
+ tiling_data_.TileBounds(iter.index_x(), iter.index_y());
+ gfx::RectF current_screen_rect = gfx::ScaleRect(
+ tile_bounds,
+ current_scale,
+ current_scale) + current_offset;
+ gfx::RectF last_screen_rect = gfx::ScaleRect(
+ tile_bounds,
+ last_scale,
+ last_scale) + last_offset;
+
+ float distance_to_visible_in_pixels =
+ TilePriority::manhattanDistance(current_screen_rect, view_rect);
+
+ float time_to_visible_in_seconds =
+ TilePriority::TimeForBoundsToIntersect(
+ last_screen_rect, current_screen_rect, time_delta, view_rect);
+ TilePriority priority(
+ resolution_,
+ time_to_visible_in_seconds,
+ distance_to_visible_in_pixels);
+ if (store_screen_space_quads_on_tiles)
+ priority.set_current_screen_quad(gfx::QuadF(current_screen_rect));
+ tile->SetPriority(tree, priority);
+ }
+ } else if (!last_screen_transform.HasPerspective() &&
+ !current_screen_transform.HasPerspective()) {
+ // Secondary fast path that can be applied for any affine transforms.
+
+ // Initialize the necessary geometry in screen space, so that we can
+ // iterate over tiles in screen space without needing a costly transform
+ // mapping for each tile.
+
+ // Apply screen space transform to the local origin point (0, 0); only the
+ // translation component is needed and can be initialized directly.
+ gfx::Point current_screen_space_origin(
+ current_screen_transform.matrix().get(0, 3),
+ current_screen_transform.matrix().get(1, 3));
+
+ gfx::Point last_screen_space_origin(
+ last_screen_transform.matrix().get(0, 3),
+ last_screen_transform.matrix().get(1, 3));
+
+ float current_tile_width = tiling_data_.TileSizeX(0) * current_scale;
+ float last_tile_width = tiling_data_.TileSizeX(0) * last_scale;
+ float current_tile_height = tiling_data_.TileSizeY(0) * current_scale;
+ float last_tile_height = tiling_data_.TileSizeY(0) * last_scale;
+
+ // Apply screen space transform to local basis vectors (tile_width, 0) and
+ // (0, tile_height); the math simplifies and can be initialized directly.
+ gfx::Vector2dF current_horizontal(
+ current_screen_transform.matrix().get(0, 0) * current_tile_width,
+ current_screen_transform.matrix().get(1, 0) * current_tile_width);
+ gfx::Vector2dF current_vertical(
+ current_screen_transform.matrix().get(0, 1) * current_tile_height,
+ current_screen_transform.matrix().get(1, 1) * current_tile_height);
+
+ gfx::Vector2dF last_horizontal(
+ last_screen_transform.matrix().get(0, 0) * last_tile_width,
+ last_screen_transform.matrix().get(1, 0) * last_tile_width);
+ gfx::Vector2dF last_vertical(
+ last_screen_transform.matrix().get(0, 1) * last_tile_height,
+ last_screen_transform.matrix().get(1, 1) * last_tile_height);
+
+ for (TilingData::Iterator iter(&tiling_data_, interest_rect);
+ iter; ++iter) {
+ TileMap::iterator find = tiles_.find(iter.index());
+ if (find == tiles_.end())
+ continue;
+
+ Tile* tile = find->second.get();
+
+ int i = iter.index_x();
+ int j = iter.index_y();
+ gfx::PointF current_tile_origin = current_screen_space_origin +
+ ScaleVector2d(current_horizontal, i) +
+ ScaleVector2d(current_vertical, j);
+ gfx::PointF last_tile_origin = last_screen_space_origin +
+ ScaleVector2d(last_horizontal, i) +
+ ScaleVector2d(last_vertical, j);
+
+ gfx::RectF current_screen_rect = gfx::QuadF(
+ current_tile_origin,
+ current_tile_origin + current_horizontal,
+ current_tile_origin + current_horizontal + current_vertical,
+ current_tile_origin + current_vertical).BoundingBox();
+
+ gfx::RectF last_screen_rect = gfx::QuadF(
+ last_tile_origin,
+ last_tile_origin + last_horizontal,
+ last_tile_origin + last_horizontal + last_vertical,
+ last_tile_origin + last_vertical).BoundingBox();
+
+ float distance_to_visible_in_pixels =
+ TilePriority::manhattanDistance(current_screen_rect, view_rect);
+
+ float time_to_visible_in_seconds =
+ TilePriority::TimeForBoundsToIntersect(
+ last_screen_rect, current_screen_rect, time_delta, view_rect);
+ TilePriority priority(
+ resolution_,
+ time_to_visible_in_seconds,
+ distance_to_visible_in_pixels);
+
+ if (store_screen_space_quads_on_tiles) {
+ // This overhead is only triggered when logging event tracing data.
+ gfx::Rect tile_bounds =
+ tiling_data_.TileBounds(iter.index_x(), iter.index_y());
+ gfx::RectF current_layer_content_rect = gfx::ScaleRect(
+ tile_bounds,
+ current_scale,
+ current_scale);
+ bool clipped;
+ priority.set_current_screen_quad(
+ MathUtil::MapQuad(current_screen_transform,
+ gfx::QuadF(current_layer_content_rect),
+ &clipped));
+ }
+ tile->SetPriority(tree, priority);
+ }
+ } else {
+ for (TilingData::Iterator iter(&tiling_data_, interest_rect);
+ iter; ++iter) {
+ TileMap::iterator find = tiles_.find(iter.index());
+ if (find == tiles_.end())
+ continue;
+ Tile* tile = find->second.get();
+
+ gfx::Rect tile_bounds =
+ tiling_data_.TileBounds(iter.index_x(), iter.index_y());
+ gfx::RectF current_layer_content_rect = gfx::ScaleRect(
+ tile_bounds,
+ current_scale,
+ current_scale);
+ gfx::RectF current_screen_rect = MathUtil::MapClippedRect(
+ current_screen_transform, current_layer_content_rect);
+ gfx::RectF last_layer_content_rect = gfx::ScaleRect(
+ tile_bounds,
+ last_scale,
+ last_scale);
+ gfx::RectF last_screen_rect = MathUtil::MapClippedRect(
+ last_screen_transform, last_layer_content_rect);
+
+ float distance_to_visible_in_pixels =
+ TilePriority::manhattanDistance(current_screen_rect, view_rect);
+
+ float time_to_visible_in_seconds =
+ TilePriority::TimeForBoundsToIntersect(
+ last_screen_rect, current_screen_rect, time_delta, view_rect);
+
+ TilePriority priority(
+ resolution_,
+ time_to_visible_in_seconds,
+ distance_to_visible_in_pixels);
+ if (store_screen_space_quads_on_tiles) {
+ bool clipped;
+ priority.set_current_screen_quad(
+ MathUtil::MapQuad(current_screen_transform,
+ gfx::QuadF(current_layer_content_rect),
+ &clipped));
+ }
+ tile->SetPriority(tree, priority);
+ }
+ }
+
+ last_impl_frame_time_in_seconds_ = current_frame_time_in_seconds;
+}
+
+void PictureLayerTiling::SetLiveTilesRect(
+ gfx::Rect new_live_tiles_rect) {
+ DCHECK(new_live_tiles_rect.IsEmpty() ||
+ ContentRect().Contains(new_live_tiles_rect));
+ if (live_tiles_rect_ == new_live_tiles_rect)
+ return;
+
+ // Iterate to delete all tiles outside of our new live_tiles rect.
+ for (TilingData::DifferenceIterator iter(&tiling_data_,
+ live_tiles_rect_,
+ new_live_tiles_rect);
+ iter;
+ ++iter) {
+ TileMapKey key(iter.index());
+ TileMap::iterator found = tiles_.find(key);
+ // If the tile was outside of the recorded region, it won't exist even
+ // though it was in the live rect.
+ if (found != tiles_.end())
+ tiles_.erase(found);
+ }
+
+ const PictureLayerTiling* twin_tiling = client_->GetTwinTiling(this);
+
+ // Iterate to allocate new tiles for all regions with newly exposed area.
+ for (TilingData::DifferenceIterator iter(&tiling_data_,
+ new_live_tiles_rect,
+ live_tiles_rect_);
+ iter;
+ ++iter) {
+ TileMapKey key(iter.index());
+ CreateTile(key.first, key.second, twin_tiling);
+ }
+
+ live_tiles_rect_ = new_live_tiles_rect;
+}
+
+void PictureLayerTiling::DidBecomeActive() {
+ for (TileMap::const_iterator it = tiles_.begin(); it != tiles_.end(); ++it) {
+ it->second->SetPriority(ACTIVE_TREE, it->second->priority(PENDING_TREE));
+ it->second->SetPriority(PENDING_TREE, TilePriority());
+
+ // Tile holds a ref onto a picture pile. If the tile never gets invalidated
+ // and recreated, then that picture pile ref could exist indefinitely. To
+ // prevent this, ask the client to update the pile to its own ref. This
+ // will cause PicturePileImpls and their clones to get deleted once the
+ // corresponding PictureLayerImpl and any in flight raster jobs go out of
+ // scope.
+ client_->UpdatePile(it->second.get());
+ }
+}
+
+void PictureLayerTiling::UpdateTilesToCurrentPile() {
+ for (TileMap::const_iterator it = tiles_.begin(); it != tiles_.end(); ++it) {
+ client_->UpdatePile(it->second.get());
+ }
+}
+
+scoped_ptr<base::Value> PictureLayerTiling::AsValue() const {
+ scoped_ptr<base::DictionaryValue> state(new base::DictionaryValue());
+ state->SetInteger("num_tiles", tiles_.size());
+ state->SetDouble("content_scale", contents_scale_);
+ state->Set("content_bounds",
+ MathUtil::AsValue(ContentRect().size()).release());
+ return state.PassAs<base::Value>();
+}
+
+size_t PictureLayerTiling::GPUMemoryUsageInBytes() const {
+ size_t amount = 0;
+ for (TileMap::const_iterator it = tiles_.begin(); it != tiles_.end(); ++it) {
+ const Tile* tile = it->second.get();
+ amount += tile->GPUMemoryUsageInBytes();
+ }
+ return amount;
+}
+
+namespace {
+
+// This struct represents an event at which the expending rect intersects
+// one of its boundaries. 4 intersection events will occur during expansion.
+struct EdgeEvent {
+ enum { BOTTOM, TOP, LEFT, RIGHT } edge;
+ int* num_edges;
+ int distance;
+};
+
+// Compute the delta to expand from edges to cover target_area.
+int ComputeExpansionDelta(int num_x_edges, int num_y_edges,
+ int width, int height,
+ int64 target_area) {
+ // Compute coefficients for the quadratic equation:
+ // a*x^2 + b*x + c = 0
+ int a = num_y_edges * num_x_edges;
+ int b = num_y_edges * width + num_x_edges * height;
+ int64 c = static_cast<int64>(width) * height - target_area;
+
+ // Compute the delta for our edges using the quadratic equation.
+ return a == 0 ? -c / b :
+ (-b + static_cast<int>(
+ std::sqrt(static_cast<int64>(b) * b - 4.0 * a * c))) / (2 * a);
+}
+
+} // namespace
+
+gfx::Rect PictureLayerTiling::ExpandRectEquallyToAreaBoundedBy(
+ gfx::Rect starting_rect,
+ int64 target_area,
+ gfx::Rect bounding_rect) {
+ if (starting_rect.IsEmpty())
+ return starting_rect;
+
+ DCHECK(!bounding_rect.IsEmpty());
+ DCHECK_GT(target_area, 0);
+
+ // Expand the starting rect to cover target_area, if it is smaller than it.
+ int delta = ComputeExpansionDelta(
+ 2, 2, starting_rect.width(), starting_rect.height(), target_area);
+ gfx::Rect expanded_starting_rect = starting_rect;
+ if (delta > 0)
+ expanded_starting_rect.Inset(-delta, -delta);
+
+ gfx::Rect rect = IntersectRects(expanded_starting_rect, bounding_rect);
+ if (rect.IsEmpty()) {
+ // The starting_rect and bounding_rect are far away.
+ return rect;
+ }
+ if (delta >= 0 && rect == expanded_starting_rect) {
+ // The starting rect already covers the entire bounding_rect and isn't too
+ // large for the target_area.
+ return rect;
+ }
+
+ // Continue to expand/shrink rect to let it cover target_area.
+
+ // These values will be updated by the loop and uses as the output.
+ int origin_x = rect.x();
+ int origin_y = rect.y();
+ int width = rect.width();
+ int height = rect.height();
+
+ // In the beginning we will consider 2 edges in each dimension.
+ int num_y_edges = 2;
+ int num_x_edges = 2;
+
+ // Create an event list.
+ EdgeEvent events[] = {
+ { EdgeEvent::BOTTOM, &num_y_edges, rect.y() - bounding_rect.y() },
+ { EdgeEvent::TOP, &num_y_edges, bounding_rect.bottom() - rect.bottom() },
+ { EdgeEvent::LEFT, &num_x_edges, rect.x() - bounding_rect.x() },
+ { EdgeEvent::RIGHT, &num_x_edges, bounding_rect.right() - rect.right() }
+ };
+
+ // Sort the events by distance (closest first).
+ if (events[0].distance > events[1].distance) std::swap(events[0], events[1]);
+ if (events[2].distance > events[3].distance) std::swap(events[2], events[3]);
+ if (events[0].distance > events[2].distance) std::swap(events[0], events[2]);
+ if (events[1].distance > events[3].distance) std::swap(events[1], events[3]);
+ if (events[1].distance > events[2].distance) std::swap(events[1], events[2]);
+
+ for (int event_index = 0; event_index < 4; event_index++) {
+ const EdgeEvent& event = events[event_index];
+
+ int delta = ComputeExpansionDelta(
+ num_x_edges, num_y_edges, width, height, target_area);
+
+ // Clamp delta to our event distance.
+ if (delta > event.distance)
+ delta = event.distance;
+
+ // Adjust the edge count for this kind of edge.
+ --*event.num_edges;
+
+ // Apply the delta to the edges and edge events.
+ for (int i = event_index; i < 4; i++) {
+ switch (events[i].edge) {
+ case EdgeEvent::BOTTOM:
+ origin_y -= delta;
+ height += delta;
+ break;
+ case EdgeEvent::TOP:
+ height += delta;
+ break;
+ case EdgeEvent::LEFT:
+ origin_x -= delta;
+ width += delta;
+ break;
+ case EdgeEvent::RIGHT:
+ width += delta;
+ break;
+ }
+ events[i].distance -= delta;
+ }
+
+ // If our delta is less then our event distance, we're done.
+ if (delta < event.distance)
+ break;
+ }
+
+ return gfx::Rect(origin_x, origin_y, width, height);
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/picture_layer_tiling.h b/chromium/cc/resources/picture_layer_tiling.h
new file mode 100644
index 00000000000..973fa50b73f
--- /dev/null
+++ b/chromium/cc/resources/picture_layer_tiling.h
@@ -0,0 +1,201 @@
+// 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.
+
+#ifndef CC_RESOURCES_PICTURE_LAYER_TILING_H_
+#define CC_RESOURCES_PICTURE_LAYER_TILING_H_
+
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/base/region.h"
+#include "cc/base/tiling_data.h"
+#include "cc/resources/tile.h"
+#include "cc/resources/tile_priority.h"
+#include "ui/gfx/rect.h"
+
+namespace cc {
+
+class PictureLayerTiling;
+
+class CC_EXPORT PictureLayerTilingClient {
+ public:
+ // Create a tile at the given content_rect (in the contents scale of the
+ // tiling) This might return null if the client cannot create such a tile.
+ virtual scoped_refptr<Tile> CreateTile(
+ PictureLayerTiling* tiling,
+ gfx::Rect content_rect) = 0;
+ virtual void UpdatePile(Tile* tile) = 0;
+ virtual gfx::Size CalculateTileSize(
+ gfx::Size content_bounds) const = 0;
+ virtual const Region* GetInvalidation() = 0;
+ virtual const PictureLayerTiling* GetTwinTiling(
+ const PictureLayerTiling* tiling) = 0;
+
+ protected:
+ virtual ~PictureLayerTilingClient() {}
+};
+
+class CC_EXPORT PictureLayerTiling {
+ public:
+ ~PictureLayerTiling();
+
+ // Create a tiling with no tiles. CreateTiles must be called to add some.
+ static scoped_ptr<PictureLayerTiling> Create(
+ float contents_scale,
+ gfx::Size layer_bounds,
+ PictureLayerTilingClient* client);
+ gfx::Size layer_bounds() const { return layer_bounds_; }
+ void SetLayerBounds(gfx::Size layer_bounds);
+ void Invalidate(const Region& layer_region);
+ void CreateMissingTilesInLiveTilesRect();
+
+ void SetCanUseLCDText(bool can_use_lcd_text);
+
+ void SetClient(PictureLayerTilingClient* client);
+ void set_resolution(TileResolution resolution) { resolution_ = resolution; }
+ TileResolution resolution() const { return resolution_; }
+
+ gfx::Rect ContentRect() const;
+ gfx::SizeF ContentSizeF() const;
+ gfx::Rect live_tiles_rect() const { return live_tiles_rect_; }
+ gfx::Size tile_size() const { return tiling_data_.max_texture_size(); }
+ float contents_scale() const { return contents_scale_; }
+
+ void CreateAllTilesForTesting() {
+ SetLiveTilesRect(gfx::Rect(tiling_data_.total_size()));
+ }
+
+ std::vector<Tile*> AllTilesForTesting() const {
+ std::vector<Tile*> all_tiles;
+ for (TileMap::const_iterator it = tiles_.begin();
+ it != tiles_.end(); ++it)
+ all_tiles.push_back(it->second.get());
+ return all_tiles;
+ }
+
+ // Iterate over all tiles to fill content_rect. Even if tiles are invalid
+ // (i.e. no valid resource) this tiling should still iterate over them.
+ // The union of all geometry_rect calls for each element iterated over should
+ // exactly equal content_rect and no two geometry_rects should intersect.
+ class CC_EXPORT CoverageIterator {
+ public:
+ CoverageIterator();
+ CoverageIterator(const PictureLayerTiling* tiling,
+ float dest_scale,
+ gfx::Rect rect);
+ ~CoverageIterator();
+
+ // Visible rect (no borders), always in the space of content_rect,
+ // regardless of the contents scale of the tiling.
+ gfx::Rect geometry_rect() const;
+ // Texture rect (in texels) for geometry_rect
+ gfx::RectF texture_rect() const;
+ gfx::Size texture_size() const;
+
+ // Full rect (including borders) of the current tile, always in the space
+ // of content_rect, regardless of the contents scale of the tiling.
+ gfx::Rect full_tile_geometry_rect() const;
+
+ Tile* operator->() const { return current_tile_; }
+ Tile* operator*() const { return current_tile_; }
+
+ CoverageIterator& operator++();
+ operator bool() const { return tile_j_ <= bottom_; }
+
+ private:
+ const PictureLayerTiling* tiling_;
+ gfx::Rect dest_rect_;
+ float dest_to_content_scale_;
+
+ Tile* current_tile_;
+ gfx::Rect current_geometry_rect_;
+ int tile_i_;
+ int tile_j_;
+ int left_;
+ int top_;
+ int right_;
+ int bottom_;
+
+ friend class PictureLayerTiling;
+ };
+
+ Region OpaqueRegionInContentRect(gfx::Rect content_rect) const;
+
+ void Reset();
+
+ void UpdateTilePriorities(
+ WhichTree tree,
+ gfx::Size device_viewport,
+ gfx::Rect viewport_in_layer_space,
+ gfx::Rect visible_layer_rect,
+ gfx::Size last_layer_bounds,
+ gfx::Size current_layer_bounds,
+ float last_layer_contents_scale,
+ float current_layer_contents_scale,
+ const gfx::Transform& last_screen_transform,
+ const gfx::Transform& current_screen_transform,
+ double current_frame_time_in_seconds,
+ size_t max_tiles_for_interest_area);
+
+ // Copies the src_tree priority into the dst_tree priority for all tiles.
+ // The src_tree priority is reset to the lowest priority possible. This
+ // also updates the pile on each tile to be the current client's pile.
+ void DidBecomeActive();
+
+ void UpdateTilesToCurrentPile();
+
+ bool NeedsUpdateForFrameAtTime(double frame_time_in_seconds) {
+ return frame_time_in_seconds != last_impl_frame_time_in_seconds_;
+ }
+
+ scoped_ptr<base::Value> AsValue() const;
+ size_t GPUMemoryUsageInBytes() const;
+
+ static gfx::Rect ExpandRectEquallyToAreaBoundedBy(
+ gfx::Rect starting_rect,
+ int64 target_area,
+ gfx::Rect bounding_rect);
+
+ bool has_ever_been_updated() const {
+ return last_impl_frame_time_in_seconds_ != 0.0;
+ }
+
+ protected:
+ typedef std::pair<int, int> TileMapKey;
+ typedef base::hash_map<TileMapKey, scoped_refptr<Tile> > TileMap;
+
+ PictureLayerTiling(float contents_scale,
+ gfx::Size layer_bounds,
+ PictureLayerTilingClient* client);
+ void SetLiveTilesRect(gfx::Rect live_tiles_rect);
+ void CreateTile(int i, int j, const PictureLayerTiling* twin_tiling);
+ Tile* TileAt(int, int) const;
+
+ // Given properties.
+ float contents_scale_;
+ gfx::Size layer_bounds_;
+ TileResolution resolution_;
+ PictureLayerTilingClient* client_;
+
+ // Internal data.
+ TilingData tiling_data_;
+ TileMap tiles_; // It is not legal to have a NULL tile in the tiles_ map.
+ gfx::Rect live_tiles_rect_;
+
+ // State saved for computing velocities based upon finite differences.
+ double last_impl_frame_time_in_seconds_;
+
+ friend class CoverageIterator;
+
+ private:
+ DISALLOW_ASSIGN(PictureLayerTiling);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_PICTURE_LAYER_TILING_H_
diff --git a/chromium/cc/resources/picture_layer_tiling_set.cc b/chromium/cc/resources/picture_layer_tiling_set.cc
new file mode 100644
index 00000000000..1b0cb5f7335
--- /dev/null
+++ b/chromium/cc/resources/picture_layer_tiling_set.cc
@@ -0,0 +1,351 @@
+// 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 "cc/resources/picture_layer_tiling_set.h"
+
+#include <limits>
+
+namespace cc {
+
+namespace {
+
+class LargestToSmallestScaleFunctor {
+ public:
+ bool operator() (PictureLayerTiling* left, PictureLayerTiling* right) {
+ return left->contents_scale() > right->contents_scale();
+ }
+};
+
+} // namespace
+
+
+PictureLayerTilingSet::PictureLayerTilingSet(
+ PictureLayerTilingClient* client,
+ gfx::Size layer_bounds)
+ : client_(client),
+ layer_bounds_(layer_bounds) {
+}
+
+PictureLayerTilingSet::~PictureLayerTilingSet() {
+}
+
+void PictureLayerTilingSet::SetClient(PictureLayerTilingClient* client) {
+ client_ = client;
+ for (size_t i = 0; i < tilings_.size(); ++i)
+ tilings_[i]->SetClient(client_);
+}
+
+void PictureLayerTilingSet::SyncTilings(
+ const PictureLayerTilingSet& other,
+ gfx::Size new_layer_bounds,
+ const Region& layer_invalidation,
+ float minimum_contents_scale) {
+ if (new_layer_bounds.IsEmpty()) {
+ RemoveAllTilings();
+ layer_bounds_ = new_layer_bounds;
+ return;
+ }
+
+ tilings_.reserve(other.tilings_.size());
+
+ // Remove any tilings that aren't in |other| or don't meet the minimum.
+ for (size_t i = 0; i < tilings_.size(); ++i) {
+ float scale = tilings_[i]->contents_scale();
+ if (scale >= minimum_contents_scale && !!other.TilingAtScale(scale))
+ continue;
+ // Swap with the last element and remove it.
+ tilings_.swap(tilings_.begin() + i, tilings_.end() - 1);
+ tilings_.pop_back();
+ --i;
+ }
+
+ // Add any missing tilings from |other| that meet the minimum.
+ for (size_t i = 0; i < other.tilings_.size(); ++i) {
+ float contents_scale = other.tilings_[i]->contents_scale();
+ if (contents_scale < minimum_contents_scale)
+ continue;
+ if (PictureLayerTiling* this_tiling = TilingAtScale(contents_scale)) {
+ this_tiling->set_resolution(other.tilings_[i]->resolution());
+
+ // These two calls must come before updating the pile, because they may
+ // destroy tiles that the new pile cannot raster.
+ this_tiling->SetLayerBounds(new_layer_bounds);
+ this_tiling->Invalidate(layer_invalidation);
+
+ this_tiling->UpdateTilesToCurrentPile();
+ this_tiling->CreateMissingTilesInLiveTilesRect();
+
+ DCHECK(this_tiling->tile_size() ==
+ client_->CalculateTileSize(this_tiling->ContentRect().size()));
+ continue;
+ }
+ scoped_ptr<PictureLayerTiling> new_tiling = PictureLayerTiling::Create(
+ contents_scale,
+ new_layer_bounds,
+ client_);
+ new_tiling->set_resolution(other.tilings_[i]->resolution());
+ tilings_.push_back(new_tiling.Pass());
+ }
+ tilings_.sort(LargestToSmallestScaleFunctor());
+
+ layer_bounds_ = new_layer_bounds;
+}
+
+void PictureLayerTilingSet::SetCanUseLCDText(bool can_use_lcd_text) {
+ for (size_t i = 0; i < tilings_.size(); ++i)
+ tilings_[i]->SetCanUseLCDText(can_use_lcd_text);
+}
+
+PictureLayerTiling* PictureLayerTilingSet::AddTiling(float contents_scale) {
+ for (size_t i = 0; i < tilings_.size(); ++i)
+ DCHECK_NE(tilings_[i]->contents_scale(), contents_scale);
+
+ tilings_.push_back(PictureLayerTiling::Create(contents_scale,
+ layer_bounds_,
+ client_));
+ PictureLayerTiling* appended = tilings_.back();
+
+ tilings_.sort(LargestToSmallestScaleFunctor());
+ return appended;
+}
+
+PictureLayerTiling* PictureLayerTilingSet::TilingAtScale(float scale) const {
+ for (size_t i = 0; i < tilings_.size(); ++i) {
+ if (tilings_[i]->contents_scale() == scale)
+ return tilings_[i];
+ }
+ return NULL;
+}
+
+void PictureLayerTilingSet::RemoveAllTilings() {
+ tilings_.clear();
+}
+
+void PictureLayerTilingSet::Remove(PictureLayerTiling* tiling) {
+ ScopedPtrVector<PictureLayerTiling>::iterator iter =
+ std::find(tilings_.begin(), tilings_.end(), tiling);
+ if (iter == tilings_.end())
+ return;
+ tilings_.erase(iter);
+}
+
+void PictureLayerTilingSet::RemoveAllTiles() {
+ for (size_t i = 0; i < tilings_.size(); ++i)
+ tilings_[i]->Reset();
+}
+
+PictureLayerTilingSet::CoverageIterator::CoverageIterator(
+ const PictureLayerTilingSet* set,
+ float contents_scale,
+ gfx::Rect content_rect,
+ float ideal_contents_scale)
+ : set_(set),
+ contents_scale_(contents_scale),
+ ideal_contents_scale_(ideal_contents_scale),
+ current_tiling_(-1) {
+ missing_region_.Union(content_rect);
+
+ for (ideal_tiling_ = 0;
+ static_cast<size_t>(ideal_tiling_) < set_->tilings_.size();
+ ++ideal_tiling_) {
+ PictureLayerTiling* tiling = set_->tilings_[ideal_tiling_];
+ if (tiling->contents_scale() < ideal_contents_scale_) {
+ if (ideal_tiling_ > 0)
+ ideal_tiling_--;
+ break;
+ }
+ }
+
+ DCHECK_LE(set_->tilings_.size(),
+ static_cast<size_t>(std::numeric_limits<int>::max()));
+
+ int num_tilings = set_->tilings_.size();
+ if (ideal_tiling_ == num_tilings && ideal_tiling_ > 0)
+ ideal_tiling_--;
+
+ ++(*this);
+}
+
+PictureLayerTilingSet::CoverageIterator::~CoverageIterator() {
+}
+
+gfx::Rect PictureLayerTilingSet::CoverageIterator::geometry_rect() const {
+ if (!tiling_iter_) {
+ if (!region_iter_.has_rect())
+ return gfx::Rect();
+ return region_iter_.rect();
+ }
+ return tiling_iter_.geometry_rect();
+}
+
+gfx::RectF PictureLayerTilingSet::CoverageIterator::texture_rect() const {
+ if (!tiling_iter_)
+ return gfx::RectF();
+ return tiling_iter_.texture_rect();
+}
+
+gfx::Size PictureLayerTilingSet::CoverageIterator::texture_size() const {
+ if (!tiling_iter_)
+ return gfx::Size();
+ return tiling_iter_.texture_size();
+}
+
+Tile* PictureLayerTilingSet::CoverageIterator::operator->() const {
+ if (!tiling_iter_)
+ return NULL;
+ return *tiling_iter_;
+}
+
+Tile* PictureLayerTilingSet::CoverageIterator::operator*() const {
+ if (!tiling_iter_)
+ return NULL;
+ return *tiling_iter_;
+}
+
+PictureLayerTiling* PictureLayerTilingSet::CoverageIterator::CurrentTiling() {
+ if (current_tiling_ < 0)
+ return NULL;
+ if (static_cast<size_t>(current_tiling_) >= set_->tilings_.size())
+ return NULL;
+ return set_->tilings_[current_tiling_];
+}
+
+int PictureLayerTilingSet::CoverageIterator::NextTiling() const {
+ // Order returned by this method is:
+ // 1. Ideal tiling index
+ // 2. Tiling index < Ideal in decreasing order (higher res than ideal)
+ // 3. Tiling index > Ideal in increasing order (lower res than ideal)
+ // 4. Tiling index > tilings.size() (invalid index)
+ if (current_tiling_ < 0)
+ return ideal_tiling_;
+ else if (current_tiling_ > ideal_tiling_)
+ return current_tiling_ + 1;
+ else if (current_tiling_)
+ return current_tiling_ - 1;
+ else
+ return ideal_tiling_ + 1;
+}
+
+PictureLayerTilingSet::CoverageIterator&
+PictureLayerTilingSet::CoverageIterator::operator++() {
+ bool first_time = current_tiling_ < 0;
+
+ if (!*this && !first_time)
+ return *this;
+
+ if (tiling_iter_)
+ ++tiling_iter_;
+
+ // Loop until we find a valid place to stop.
+ while (true) {
+ while (tiling_iter_ &&
+ (!*tiling_iter_ || !tiling_iter_->IsReadyToDraw())) {
+ missing_region_.Union(tiling_iter_.geometry_rect());
+ ++tiling_iter_;
+ }
+ if (tiling_iter_)
+ return *this;
+
+ // If the set of current rects for this tiling is done, go to the next
+ // tiling and set up to iterate through all of the remaining holes.
+ // This will also happen the first time through the loop.
+ if (!region_iter_.has_rect()) {
+ current_tiling_ = NextTiling();
+ current_region_.Swap(&missing_region_);
+ missing_region_.Clear();
+ region_iter_ = Region::Iterator(current_region_);
+
+ // All done and all filled.
+ if (!region_iter_.has_rect()) {
+ current_tiling_ = set_->tilings_.size();
+ return *this;
+ }
+
+ // No more valid tiles, return this checkerboard rect.
+ if (current_tiling_ >= static_cast<int>(set_->tilings_.size()))
+ return *this;
+ }
+
+ // Pop a rect off. If there are no more tilings, then these will be
+ // treated as geometry with null tiles that the caller can checkerboard.
+ gfx::Rect last_rect = region_iter_.rect();
+ region_iter_.next();
+
+ // Done, found next checkerboard rect to return.
+ if (current_tiling_ >= static_cast<int>(set_->tilings_.size()))
+ return *this;
+
+ // Construct a new iterator for the next tiling, but we need to loop
+ // again until we get to a valid one.
+ tiling_iter_ = PictureLayerTiling::CoverageIterator(
+ set_->tilings_[current_tiling_],
+ contents_scale_,
+ last_rect);
+ }
+
+ return *this;
+}
+
+PictureLayerTilingSet::CoverageIterator::operator bool() const {
+ return current_tiling_ < static_cast<int>(set_->tilings_.size()) ||
+ region_iter_.has_rect();
+}
+
+void PictureLayerTilingSet::UpdateTilePriorities(
+ WhichTree tree,
+ gfx::Size device_viewport,
+ gfx::Rect viewport_in_content_space,
+ gfx::Rect visible_content_rect,
+ gfx::Size last_layer_bounds,
+ gfx::Size current_layer_bounds,
+ float last_layer_contents_scale,
+ float current_layer_contents_scale,
+ const gfx::Transform& last_screen_transform,
+ const gfx::Transform& current_screen_transform,
+ double current_frame_time_in_seconds,
+ size_t max_tiles_for_interest_area) {
+ gfx::Rect viewport_in_layer_space = gfx::ScaleToEnclosingRect(
+ viewport_in_content_space,
+ 1.f / current_layer_contents_scale);
+ gfx::Rect visible_layer_rect = gfx::ScaleToEnclosingRect(
+ visible_content_rect,
+ 1.f / current_layer_contents_scale);
+
+ for (size_t i = 0; i < tilings_.size(); ++i) {
+ tilings_[i]->UpdateTilePriorities(
+ tree,
+ device_viewport,
+ viewport_in_layer_space,
+ visible_layer_rect,
+ last_layer_bounds,
+ current_layer_bounds,
+ last_layer_contents_scale,
+ current_layer_contents_scale,
+ last_screen_transform,
+ current_screen_transform,
+ current_frame_time_in_seconds,
+ max_tiles_for_interest_area);
+ }
+}
+
+void PictureLayerTilingSet::DidBecomeActive() {
+ for (size_t i = 0; i < tilings_.size(); ++i)
+ tilings_[i]->DidBecomeActive();
+}
+
+scoped_ptr<base::Value> PictureLayerTilingSet::AsValue() const {
+ scoped_ptr<base::ListValue> state(new base::ListValue());
+ for (size_t i = 0; i < tilings_.size(); ++i)
+ state->Append(tilings_[i]->AsValue().release());
+ return state.PassAs<base::Value>();
+}
+
+size_t PictureLayerTilingSet::GPUMemoryUsageInBytes() const {
+ size_t amount = 0;
+ for (size_t i = 0; i < tilings_.size(); ++i)
+ amount += tilings_[i]->GPUMemoryUsageInBytes();
+ return amount;
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/picture_layer_tiling_set.h b/chromium/cc/resources/picture_layer_tiling_set.h
new file mode 100644
index 00000000000..e498cb964b6
--- /dev/null
+++ b/chromium/cc/resources/picture_layer_tiling_set.h
@@ -0,0 +1,130 @@
+// 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.
+
+#ifndef CC_RESOURCES_PICTURE_LAYER_TILING_SET_H_
+#define CC_RESOURCES_PICTURE_LAYER_TILING_SET_H_
+
+#include "cc/base/region.h"
+#include "cc/base/scoped_ptr_vector.h"
+#include "cc/resources/picture_layer_tiling.h"
+#include "ui/gfx/size.h"
+
+namespace cc {
+
+class CC_EXPORT PictureLayerTilingSet {
+ public:
+ PictureLayerTilingSet(PictureLayerTilingClient* client,
+ gfx::Size layer_bounds);
+ ~PictureLayerTilingSet();
+
+ void SetClient(PictureLayerTilingClient* client);
+ const PictureLayerTilingClient* client() const { return client_; }
+
+ // Make this set of tilings match the same set of content scales from |other|.
+ // Delete any tilings that don't meet |minimum_contents_scale|. Recreate
+ // any tiles that intersect |layer_invalidation|. Update the size of all
+ // tilings to |new_layer_bounds|.
+ void SyncTilings(
+ const PictureLayerTilingSet& other,
+ gfx::Size new_layer_bounds,
+ const Region& layer_invalidation,
+ float minimum_contents_scale);
+
+ gfx::Size layer_bounds() const { return layer_bounds_; }
+
+ void SetCanUseLCDText(bool can_use_lcd_text);
+
+ PictureLayerTiling* AddTiling(float contents_scale);
+ size_t num_tilings() const { return tilings_.size(); }
+ PictureLayerTiling* tiling_at(size_t idx) { return tilings_[idx]; }
+ const PictureLayerTiling* tiling_at(size_t idx) const {
+ return tilings_[idx];
+ }
+
+ PictureLayerTiling* TilingAtScale(float scale) const;
+
+ // Remove all tilings.
+ void RemoveAllTilings();
+
+ // Remove one tiling.
+ void Remove(PictureLayerTiling* tiling);
+
+ // Remove all tiles; keep all tilings.
+ void RemoveAllTiles();
+
+ void UpdateTilePriorities(
+ WhichTree tree,
+ gfx::Size device_viewport,
+ gfx::Rect viewport_in_content_space,
+ gfx::Rect visible_content_rect,
+ gfx::Size last_layer_bounds,
+ gfx::Size current_layer_bounds,
+ float last_layer_contents_scale,
+ float current_layer_contents_scale,
+ const gfx::Transform& last_screen_transform,
+ const gfx::Transform& current_screen_transform,
+ double current_frame_time_in_seconds,
+ size_t max_tiles_for_interest_area);
+
+ void DidBecomeActive();
+
+ // For a given rect, iterates through tiles that can fill it. If no
+ // set of tiles with resources can fill the rect, then it will iterate
+ // through null tiles with valid geometry_rect() until the rect is full.
+ // If all tiles have resources, the union of all geometry_rects will
+ // exactly fill rect with no overlap.
+ class CC_EXPORT CoverageIterator {
+ public:
+ CoverageIterator(const PictureLayerTilingSet* set,
+ float contents_scale,
+ gfx::Rect content_rect,
+ float ideal_contents_scale);
+ ~CoverageIterator();
+
+ // Visible rect (no borders), always in the space of rect,
+ // regardless of the relative contents scale of the tiling.
+ gfx::Rect geometry_rect() const;
+ // Texture rect (in texels) for geometry_rect
+ gfx::RectF texture_rect() const;
+ // Texture size in texels
+ gfx::Size texture_size() const;
+
+ Tile* operator->() const;
+ Tile* operator*() const;
+
+ CoverageIterator& operator++();
+ operator bool() const;
+
+ PictureLayerTiling* CurrentTiling();
+
+ private:
+ int NextTiling() const;
+
+ const PictureLayerTilingSet* set_;
+ float contents_scale_;
+ float ideal_contents_scale_;
+ PictureLayerTiling::CoverageIterator tiling_iter_;
+ int current_tiling_;
+ int ideal_tiling_;
+
+ Region current_region_;
+ Region missing_region_;
+ Region::Iterator region_iter_;
+ };
+
+ scoped_ptr<base::Value> AsValue() const;
+ size_t GPUMemoryUsageInBytes() const;
+
+ private:
+ PictureLayerTilingClient* client_;
+ gfx::Size layer_bounds_;
+ ScopedPtrVector<PictureLayerTiling> tilings_;
+
+ friend class Iterator;
+ DISALLOW_COPY_AND_ASSIGN(PictureLayerTilingSet);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_PICTURE_LAYER_TILING_SET_H_
diff --git a/chromium/cc/resources/picture_layer_tiling_set_unittest.cc b/chromium/cc/resources/picture_layer_tiling_set_unittest.cc
new file mode 100644
index 00000000000..c47cf4b5a90
--- /dev/null
+++ b/chromium/cc/resources/picture_layer_tiling_set_unittest.cc
@@ -0,0 +1,421 @@
+// 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 "cc/resources/picture_layer_tiling_set.h"
+
+#include <map>
+#include <vector>
+
+#include "cc/resources/resource_pool.h"
+#include "cc/resources/resource_provider.h"
+#include "cc/test/fake_output_surface.h"
+#include "cc/test/fake_picture_layer_tiling_client.h"
+#include "cc/test/fake_tile_manager_client.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/size_conversions.h"
+
+namespace cc {
+namespace {
+
+TEST(PictureLayerTilingSetTest, NoResources) {
+ FakePictureLayerTilingClient client;
+ gfx::Size layer_bounds(1000, 800);
+ PictureLayerTilingSet set(&client, layer_bounds);
+ client.SetTileSize(gfx::Size(256, 256));
+
+ set.AddTiling(1.0);
+ set.AddTiling(1.5);
+ set.AddTiling(2.0);
+
+ float contents_scale = 2.0;
+ gfx::Size content_bounds(
+ gfx::ToCeiledSize(gfx::ScaleSize(layer_bounds, contents_scale)));
+ gfx::Rect content_rect(content_bounds);
+
+ Region remaining(content_rect);
+ PictureLayerTilingSet::CoverageIterator iter(
+ &set,
+ contents_scale,
+ content_rect,
+ contents_scale);
+ for (; iter; ++iter) {
+ gfx::Rect geometry_rect = iter.geometry_rect();
+ EXPECT_TRUE(content_rect.Contains(geometry_rect));
+ ASSERT_TRUE(remaining.Contains(geometry_rect));
+ remaining.Subtract(geometry_rect);
+
+ // No tiles have resources, so no iter represents a real tile.
+ EXPECT_FALSE(*iter);
+ }
+ EXPECT_TRUE(remaining.IsEmpty());
+}
+
+class PictureLayerTilingSetTestWithResources : public testing::Test {
+ public:
+ void runTest(
+ int num_tilings,
+ float min_scale,
+ float scale_increment,
+ float ideal_contents_scale,
+ float expected_scale) {
+ scoped_ptr<FakeOutputSurface> output_surface =
+ FakeOutputSurface::Create3d();
+ scoped_ptr<ResourceProvider> resource_provider =
+ ResourceProvider::Create(output_surface.get(), 0);
+
+ FakePictureLayerTilingClient client;
+ client.SetTileSize(gfx::Size(256, 256));
+ gfx::Size layer_bounds(1000, 800);
+ PictureLayerTilingSet set(&client, layer_bounds);
+
+ float scale = min_scale;
+ for (int i = 0; i < num_tilings; ++i, scale += scale_increment) {
+ PictureLayerTiling* tiling = set.AddTiling(scale);
+ tiling->CreateAllTilesForTesting();
+ std::vector<Tile*> tiles = tiling->AllTilesForTesting();
+ for (size_t i = 0; i < tiles.size(); ++i) {
+ ManagedTileState::TileVersion& tile_version =
+ tiles[i]->GetTileVersionForTesting(HIGH_QUALITY_NO_LCD_RASTER_MODE);
+ EXPECT_FALSE(tile_version.GetResourceForTesting());
+
+ tile_version.SetResourceForTesting(
+ make_scoped_ptr(new ResourcePool::Resource(
+ resource_provider.get(),
+ gfx::Size(1, 1),
+ resource_provider->best_texture_format())));
+ }
+ }
+
+ float max_contents_scale = scale;
+ gfx::Size content_bounds(
+ gfx::ToCeiledSize(gfx::ScaleSize(layer_bounds, max_contents_scale)));
+ gfx::Rect content_rect(content_bounds);
+
+ Region remaining(content_rect);
+ PictureLayerTilingSet::CoverageIterator iter(
+ &set,
+ max_contents_scale,
+ content_rect,
+ ideal_contents_scale);
+ for (; iter; ++iter) {
+ gfx::Rect geometry_rect = iter.geometry_rect();
+ EXPECT_TRUE(content_rect.Contains(geometry_rect));
+ ASSERT_TRUE(remaining.Contains(geometry_rect));
+ remaining.Subtract(geometry_rect);
+
+ float scale = iter.CurrentTiling()->contents_scale();
+ EXPECT_EQ(expected_scale, scale);
+
+ if (num_tilings)
+ EXPECT_TRUE(*iter);
+ else
+ EXPECT_FALSE(*iter);
+ }
+ EXPECT_TRUE(remaining.IsEmpty());
+ }
+};
+
+TEST_F(PictureLayerTilingSetTestWithResources, NoTilings) {
+ runTest(0, 0.f, 0.f, 2.f, 0.f);
+}
+TEST_F(PictureLayerTilingSetTestWithResources, OneTiling_Smaller) {
+ runTest(1, 1.f, 0.f, 2.f, 1.f);
+}
+TEST_F(PictureLayerTilingSetTestWithResources, OneTiling_Larger) {
+ runTest(1, 3.f, 0.f, 2.f, 3.f);
+}
+TEST_F(PictureLayerTilingSetTestWithResources, TwoTilings_Smaller) {
+ runTest(2, 1.f, 1.f, 3.f, 2.f);
+}
+
+TEST_F(PictureLayerTilingSetTestWithResources, TwoTilings_SmallerEqual) {
+ runTest(2, 1.f, 1.f, 2.f, 2.f);
+}
+
+TEST_F(PictureLayerTilingSetTestWithResources, TwoTilings_LargerEqual) {
+ runTest(2, 1.f, 1.f, 1.f, 1.f);
+}
+
+TEST_F(PictureLayerTilingSetTestWithResources, TwoTilings_Larger) {
+ runTest(2, 2.f, 8.f, 1.f, 2.f);
+}
+
+TEST_F(PictureLayerTilingSetTestWithResources, ManyTilings_Equal) {
+ runTest(10, 1.f, 1.f, 5.f, 5.f);
+}
+
+TEST_F(PictureLayerTilingSetTestWithResources, ManyTilings_NotEqual) {
+ runTest(10, 1.f, 1.f, 4.5f, 5.f);
+}
+
+class PictureLayerTilingSetSyncTest : public testing::Test {
+ public:
+ PictureLayerTilingSetSyncTest()
+ : tile_size_(gfx::Size(10, 10)),
+ source_bounds_(gfx::Size(30, 20)),
+ target_bounds_(gfx::Size(30, 30)) {
+ source_client_.SetTileSize(tile_size_);
+ target_client_.SetTileSize(tile_size_);
+ source_.reset(new PictureLayerTilingSet(&source_client_, source_bounds_));
+ target_.reset(new PictureLayerTilingSet(&target_client_, target_bounds_));
+ }
+
+ // Sync from source to target.
+ void SyncTilings(gfx::Size new_bounds,
+ const Region& invalidation,
+ float minimum_scale) {
+ for (size_t i = 0; i < source_->num_tilings(); ++i)
+ source_->tiling_at(i)->CreateAllTilesForTesting();
+ for (size_t i = 0; i < target_->num_tilings(); ++i)
+ target_->tiling_at(i)->CreateAllTilesForTesting();
+
+ target_->SyncTilings(
+ *source_.get(), new_bounds, invalidation, minimum_scale);
+ }
+ void SyncTilings(gfx::Size new_bounds) {
+ Region invalidation;
+ SyncTilings(new_bounds, invalidation, 0.f);
+ }
+ void SyncTilings(gfx::Size new_bounds, const Region& invalidation) {
+ SyncTilings(new_bounds, invalidation, 0.f);
+ }
+ void SyncTilings(gfx::Size new_bounds, float minimum_scale) {
+ Region invalidation;
+ SyncTilings(new_bounds, invalidation, minimum_scale);
+ }
+
+ void VerifyTargetEqualsSource(gfx::Size new_bounds) const {
+ ASSERT_FALSE(new_bounds.IsEmpty());
+ EXPECT_EQ(target_->num_tilings(), source_->num_tilings());
+ EXPECT_EQ(target_->layer_bounds().ToString(), new_bounds.ToString());
+
+ for (size_t i = 0; i < target_->num_tilings(); ++i) {
+ ASSERT_GT(source_->num_tilings(), i);
+ const PictureLayerTiling* source_tiling = source_->tiling_at(i);
+ const PictureLayerTiling* target_tiling = target_->tiling_at(i);
+ EXPECT_EQ(target_tiling->layer_bounds().ToString(),
+ new_bounds.ToString());
+ EXPECT_EQ(source_tiling->contents_scale(),
+ target_tiling->contents_scale());
+ }
+
+ EXPECT_EQ(source_->client(), &source_client_);
+ EXPECT_EQ(target_->client(), &target_client_);
+ ValidateTargetTilingSet();
+ }
+
+ void ValidateTargetTilingSet() const {
+ // Tilings should be sorted largest to smallest.
+ if (target_->num_tilings() > 0) {
+ float last_scale = target_->tiling_at(0)->contents_scale();
+ for (size_t i = 1; i < target_->num_tilings(); ++i) {
+ const PictureLayerTiling* target_tiling = target_->tiling_at(i);
+ EXPECT_LT(target_tiling->contents_scale(), last_scale);
+ last_scale = target_tiling->contents_scale();
+ }
+ }
+
+ for (size_t i = 0; i < target_->num_tilings(); ++i)
+ ValidateTiling(target_->tiling_at(i), target_client_.pile());
+ }
+
+ void ValidateTiling(const PictureLayerTiling* tiling,
+ const PicturePileImpl* pile) const {
+ if (tiling->ContentRect().IsEmpty())
+ EXPECT_TRUE(tiling->live_tiles_rect().IsEmpty());
+ else if (!tiling->live_tiles_rect().IsEmpty())
+ EXPECT_TRUE(tiling->ContentRect().Contains(tiling->live_tiles_rect()));
+
+ std::vector<Tile*> tiles = tiling->AllTilesForTesting();
+ for (size_t i = 0; i < tiles.size(); ++i) {
+ const Tile* tile = tiles[i];
+ ASSERT_TRUE(!!tile);
+ EXPECT_EQ(tile->picture_pile(), pile);
+ EXPECT_TRUE(tile->content_rect().Intersects(tiling->live_tiles_rect()))
+ << "All tiles must be inside the live tiles rect.";
+ }
+
+ for (PictureLayerTiling::CoverageIterator iter(
+ tiling, tiling->contents_scale(), tiling->live_tiles_rect());
+ iter;
+ ++iter) {
+ EXPECT_TRUE(*iter) << "The live tiles rect must be full.";
+ }
+ }
+
+ gfx::Size tile_size_;
+
+ FakePictureLayerTilingClient source_client_;
+ gfx::Size source_bounds_;
+ scoped_ptr<PictureLayerTilingSet> source_;
+
+ FakePictureLayerTilingClient target_client_;
+ gfx::Size target_bounds_;
+ scoped_ptr<PictureLayerTilingSet> target_;
+};
+
+TEST_F(PictureLayerTilingSetSyncTest, EmptyBounds) {
+ float source_scales[] = {1.f, 1.2f};
+ for (size_t i = 0; i < arraysize(source_scales); ++i)
+ source_->AddTiling(source_scales[i]);
+
+ gfx::Size new_bounds;
+ SyncTilings(new_bounds);
+ EXPECT_EQ(target_->num_tilings(), 0u);
+ EXPECT_EQ(target_->layer_bounds().ToString(), new_bounds.ToString());
+}
+
+TEST_F(PictureLayerTilingSetSyncTest, AllNew) {
+ float source_scales[] = {0.5f, 1.f, 1.2f};
+ for (size_t i = 0; i < arraysize(source_scales); ++i)
+ source_->AddTiling(source_scales[i]);
+ float target_scales[] = {0.75f, 1.4f, 3.f};
+ for (size_t i = 0; i < arraysize(target_scales); ++i)
+ target_->AddTiling(target_scales[i]);
+
+ gfx::Size new_bounds(15, 40);
+ SyncTilings(new_bounds);
+ VerifyTargetEqualsSource(new_bounds);
+}
+
+Tile* FindTileAtOrigin(PictureLayerTiling* tiling) {
+ std::vector<Tile*> tiles = tiling->AllTilesForTesting();
+ for (size_t i = 0; i < tiles.size(); ++i) {
+ if (tiles[i]->content_rect().origin() == gfx::Point())
+ return tiles[i];
+ }
+ return NULL;
+}
+
+TEST_F(PictureLayerTilingSetSyncTest, KeepExisting) {
+ float source_scales[] = {0.7f, 1.f, 1.1f, 2.f};
+ for (size_t i = 0; i < arraysize(source_scales); ++i)
+ source_->AddTiling(source_scales[i]);
+ float target_scales[] = {0.5f, 1.f, 2.f};
+ for (size_t i = 0; i < arraysize(target_scales); ++i)
+ target_->AddTiling(target_scales[i]);
+
+ PictureLayerTiling* tiling1 = source_->TilingAtScale(1.f);
+ ASSERT_TRUE(tiling1);
+ tiling1->CreateAllTilesForTesting();
+ EXPECT_EQ(tiling1->contents_scale(), 1.f);
+ Tile* tile1 = FindTileAtOrigin(tiling1);
+ ASSERT_TRUE(tile1);
+
+ PictureLayerTiling* tiling2 = source_->TilingAtScale(2.f);
+ tiling2->CreateAllTilesForTesting();
+ ASSERT_TRUE(tiling2);
+ EXPECT_EQ(tiling2->contents_scale(), 2.f);
+ Tile* tile2 = FindTileAtOrigin(tiling2);
+ ASSERT_TRUE(tile2);
+
+ gfx::Size new_bounds(15, 40);
+ SyncTilings(new_bounds);
+ VerifyTargetEqualsSource(new_bounds);
+
+ EXPECT_EQ(tiling1, source_->TilingAtScale(1.f));
+ EXPECT_EQ(tile1, FindTileAtOrigin(tiling1));
+ EXPECT_FALSE(tiling1->live_tiles_rect().IsEmpty());
+
+ EXPECT_EQ(tiling2, source_->TilingAtScale(2.f));
+ EXPECT_EQ(tile2, FindTileAtOrigin(tiling2));
+ EXPECT_FALSE(tiling2->live_tiles_rect().IsEmpty());
+}
+
+TEST_F(PictureLayerTilingSetSyncTest, EmptySet) {
+ float target_scales[] = {0.2f, 1.f};
+ for (size_t i = 0; i < arraysize(target_scales); ++i)
+ target_->AddTiling(target_scales[i]);
+
+ gfx::Size new_bounds(15, 40);
+ SyncTilings(new_bounds);
+ VerifyTargetEqualsSource(new_bounds);
+}
+
+TEST_F(PictureLayerTilingSetSyncTest, MinimumScale) {
+ float source_scales[] = {0.7f, 1.f, 1.1f, 2.f};
+ for (size_t i = 0; i < arraysize(source_scales); ++i)
+ source_->AddTiling(source_scales[i]);
+ float target_scales[] = {0.5f, 0.7f, 1.f, 1.1f, 2.f};
+ for (size_t i = 0; i < arraysize(target_scales); ++i)
+ target_->AddTiling(target_scales[i]);
+
+ gfx::Size new_bounds(15, 40);
+ float minimum_scale = 1.5f;
+ SyncTilings(new_bounds, minimum_scale);
+
+ EXPECT_EQ(target_->num_tilings(), 1u);
+ EXPECT_EQ(target_->tiling_at(0)->contents_scale(), 2.f);
+ ValidateTargetTilingSet();
+}
+
+TEST_F(PictureLayerTilingSetSyncTest, Invalidation) {
+ source_->AddTiling(2.f);
+ target_->AddTiling(2.f);
+ target_->tiling_at(0)->CreateAllTilesForTesting();
+
+ Region layer_invalidation;
+ layer_invalidation.Union(gfx::Rect(0, 0, 1, 1));
+ layer_invalidation.Union(gfx::Rect(0, 15, 1, 1));
+ // Out of bounds layer_invalidation.
+ layer_invalidation.Union(gfx::Rect(100, 100, 1, 1));
+
+ Region content_invalidation;
+ for (Region::Iterator iter(layer_invalidation); iter.has_rect();
+ iter.next()) {
+ gfx::Rect content_rect = gfx::ScaleToEnclosingRect(iter.rect(), 2.f);
+ content_invalidation.Union(content_rect);
+ }
+
+ std::vector<Tile*> old_tiles = target_->tiling_at(0)->AllTilesForTesting();
+ std::map<gfx::Point, scoped_refptr<Tile> > old_tile_map;
+ for (size_t i = 0; i < old_tiles.size(); ++i)
+ old_tile_map[old_tiles[i]->content_rect().origin()] = old_tiles[i];
+
+ SyncTilings(target_bounds_, layer_invalidation);
+ VerifyTargetEqualsSource(target_bounds_);
+
+ std::vector<Tile*> new_tiles = target_->tiling_at(0)->AllTilesForTesting();
+ for (size_t i = 0; i < new_tiles.size(); ++i) {
+ const Tile* tile = new_tiles[i];
+ std::map<gfx::Point, scoped_refptr<Tile> >::iterator find =
+ old_tile_map.find(tile->content_rect().origin());
+ if (content_invalidation.Intersects(tile->content_rect()))
+ EXPECT_NE(tile, find->second.get());
+ else
+ EXPECT_EQ(tile, find->second.get());
+ }
+}
+
+TEST_F(PictureLayerTilingSetSyncTest, TileSizeChange) {
+ source_->AddTiling(1.f);
+ target_->AddTiling(1.f);
+
+ target_->tiling_at(0)->CreateAllTilesForTesting();
+ std::vector<Tile*> original_tiles =
+ target_->tiling_at(0)->AllTilesForTesting();
+ EXPECT_GT(original_tiles.size(), 0u);
+ gfx::Size new_tile_size(100, 100);
+ target_client_.SetTileSize(new_tile_size);
+ EXPECT_NE(target_->tiling_at(0)->tile_size().ToString(),
+ new_tile_size.ToString());
+
+ gfx::Size new_bounds(15, 40);
+ SyncTilings(new_bounds);
+ VerifyTargetEqualsSource(new_bounds);
+
+ EXPECT_EQ(target_->tiling_at(0)->tile_size().ToString(),
+ new_tile_size.ToString());
+
+ // All old tiles should not be present in new tiles.
+ std::vector<Tile*> new_tiles = target_->tiling_at(0)->AllTilesForTesting();
+ for (size_t i = 0; i < original_tiles.size(); ++i) {
+ std::vector<Tile*>::iterator find =
+ std::find(new_tiles.begin(), new_tiles.end(), original_tiles[i]);
+ EXPECT_TRUE(find == new_tiles.end());
+ }
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/resources/picture_layer_tiling_unittest.cc b/chromium/cc/resources/picture_layer_tiling_unittest.cc
new file mode 100644
index 00000000000..af70f297ed2
--- /dev/null
+++ b/chromium/cc/resources/picture_layer_tiling_unittest.cc
@@ -0,0 +1,1413 @@
+// 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 "cc/resources/picture_layer_tiling.h"
+
+#include <limits>
+
+#include "cc/base/math_util.h"
+#include "cc/resources/picture_layer_tiling_set.h"
+#include "cc/test/fake_picture_layer_tiling_client.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/size_conversions.h"
+
+namespace cc {
+namespace {
+
+static gfx::Rect ViewportInLayerSpace(
+ const gfx::Transform& transform,
+ gfx::Size device_viewport) {
+
+ gfx::Transform inverse;
+ if (!transform.GetInverse(&inverse))
+ return gfx::Rect();
+
+ gfx::RectF viewport_in_layer_space = MathUtil::ProjectClippedRect(
+ inverse, gfx::RectF(gfx::Point(0, 0), device_viewport));
+
+ return ToEnclosingRect(viewport_in_layer_space);
+}
+
+class TestablePictureLayerTiling : public PictureLayerTiling {
+ public:
+ using PictureLayerTiling::SetLiveTilesRect;
+ using PictureLayerTiling::TileAt;
+
+ static scoped_ptr<TestablePictureLayerTiling> Create(
+ float contents_scale,
+ gfx::Size layer_bounds,
+ PictureLayerTilingClient* client) {
+ return make_scoped_ptr(new TestablePictureLayerTiling(
+ contents_scale,
+ layer_bounds,
+ client));
+ }
+
+ protected:
+ TestablePictureLayerTiling(float contents_scale,
+ gfx::Size layer_bounds,
+ PictureLayerTilingClient* client)
+ : PictureLayerTiling(contents_scale, layer_bounds, client) { }
+};
+
+class PictureLayerTilingIteratorTest : public testing::Test {
+ public:
+ PictureLayerTilingIteratorTest() {}
+ virtual ~PictureLayerTilingIteratorTest() {}
+
+ void Initialize(gfx::Size tile_size,
+ float contents_scale,
+ gfx::Size layer_bounds) {
+ client_.SetTileSize(tile_size);
+ tiling_ = TestablePictureLayerTiling::Create(contents_scale,
+ layer_bounds,
+ &client_);
+ }
+
+ void SetLiveRectAndVerifyTiles(gfx::Rect live_tiles_rect) {
+ tiling_->SetLiveTilesRect(live_tiles_rect);
+
+ std::vector<Tile*> tiles = tiling_->AllTilesForTesting();
+ for (std::vector<Tile*>::iterator iter = tiles.begin();
+ iter != tiles.end();
+ ++iter) {
+ EXPECT_TRUE(live_tiles_rect.Intersects((*iter)->content_rect()));
+ }
+ }
+
+ void VerifyTilesExactlyCoverRect(
+ float rect_scale,
+ gfx::Rect request_rect,
+ gfx::Rect expect_rect) {
+ EXPECT_TRUE(request_rect.Contains(expect_rect));
+
+ // Iterators are not valid if this ratio is too large (i.e. the
+ // tiling is too high-res for a low-res destination rect.) This is an
+ // artifact of snapping geometry to integer coordinates and then mapping
+ // back to floating point texture coordinates.
+ float dest_to_contents_scale = tiling_->contents_scale() / rect_scale;
+ ASSERT_LE(dest_to_contents_scale, 2.0);
+
+ Region remaining = expect_rect;
+ for (PictureLayerTiling::CoverageIterator
+ iter(tiling_.get(), rect_scale, request_rect);
+ iter;
+ ++iter) {
+ // Geometry cannot overlap previous geometry at all
+ gfx::Rect geometry = iter.geometry_rect();
+ EXPECT_TRUE(expect_rect.Contains(geometry));
+ EXPECT_TRUE(remaining.Contains(geometry));
+ remaining.Subtract(geometry);
+
+ // Sanity check that texture coords are within the texture rect.
+ gfx::RectF texture_rect = iter.texture_rect();
+ EXPECT_GE(texture_rect.x(), 0);
+ EXPECT_GE(texture_rect.y(), 0);
+ EXPECT_LE(texture_rect.right(), client_.TileSize().width());
+ EXPECT_LE(texture_rect.bottom(), client_.TileSize().height());
+
+ EXPECT_EQ(iter.texture_size(), client_.TileSize());
+ }
+
+ // The entire rect must be filled by geometry from the tiling.
+ EXPECT_TRUE(remaining.IsEmpty());
+ }
+
+ void VerifyTilesExactlyCoverRect(float rect_scale, gfx::Rect rect) {
+ VerifyTilesExactlyCoverRect(rect_scale, rect, rect);
+ }
+
+ void VerifyTiles(
+ float rect_scale,
+ gfx::Rect rect,
+ base::Callback<void(Tile* tile, gfx::Rect geometry_rect)> callback) {
+ VerifyTiles(tiling_.get(),
+ rect_scale,
+ rect,
+ callback);
+ }
+
+ void VerifyTiles(
+ PictureLayerTiling* tiling,
+ float rect_scale,
+ gfx::Rect rect,
+ base::Callback<void(Tile* tile, gfx::Rect geometry_rect)> callback) {
+ Region remaining = rect;
+ for (PictureLayerTiling::CoverageIterator iter(tiling, rect_scale, rect);
+ iter;
+ ++iter) {
+ remaining.Subtract(iter.geometry_rect());
+ callback.Run(*iter, iter.geometry_rect());
+ }
+ EXPECT_TRUE(remaining.IsEmpty());
+ }
+
+ void VerifyTilesCoverNonContainedRect(float rect_scale, gfx::Rect dest_rect) {
+ float dest_to_contents_scale = tiling_->contents_scale() / rect_scale;
+ gfx::Rect clamped_rect = gfx::ScaleToEnclosingRect(
+ tiling_->ContentRect(), 1.f / dest_to_contents_scale);
+ clamped_rect.Intersect(dest_rect);
+ VerifyTilesExactlyCoverRect(rect_scale, dest_rect, clamped_rect);
+ }
+
+ protected:
+ FakePictureLayerTilingClient client_;
+ scoped_ptr<TestablePictureLayerTiling> tiling_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PictureLayerTilingIteratorTest);
+};
+
+TEST_F(PictureLayerTilingIteratorTest, LiveTilesExactlyCoverLiveTileRect) {
+ Initialize(gfx::Size(100, 100), 1, gfx::Size(1099, 801));
+ SetLiveRectAndVerifyTiles(gfx::Rect(100, 100));
+ SetLiveRectAndVerifyTiles(gfx::Rect(101, 99));
+ SetLiveRectAndVerifyTiles(gfx::Rect(1099, 1));
+ SetLiveRectAndVerifyTiles(gfx::Rect(1, 801));
+ SetLiveRectAndVerifyTiles(gfx::Rect(1099, 1));
+ SetLiveRectAndVerifyTiles(gfx::Rect(201, 800));
+}
+
+TEST_F(PictureLayerTilingIteratorTest, IteratorCoversLayerBoundsNoScale) {
+ Initialize(gfx::Size(100, 100), 1, gfx::Size(1099, 801));
+ VerifyTilesExactlyCoverRect(1, gfx::Rect());
+ VerifyTilesExactlyCoverRect(1, gfx::Rect(0, 0, 1099, 801));
+ VerifyTilesExactlyCoverRect(1, gfx::Rect(52, 83, 789, 412));
+
+ // With borders, a size of 3x3 = 1 pixel of content.
+ Initialize(gfx::Size(3, 3), 1, gfx::Size(10, 10));
+ VerifyTilesExactlyCoverRect(1, gfx::Rect(0, 0, 1, 1));
+ VerifyTilesExactlyCoverRect(1, gfx::Rect(0, 0, 2, 2));
+ VerifyTilesExactlyCoverRect(1, gfx::Rect(1, 1, 2, 2));
+ VerifyTilesExactlyCoverRect(1, gfx::Rect(3, 2, 5, 2));
+}
+
+TEST_F(PictureLayerTilingIteratorTest, IteratorCoversLayerBoundsTilingScale) {
+ Initialize(gfx::Size(200, 100), 2.0f, gfx::Size(1005, 2010));
+ VerifyTilesExactlyCoverRect(1, gfx::Rect());
+ VerifyTilesExactlyCoverRect(1, gfx::Rect(0, 0, 1005, 2010));
+ VerifyTilesExactlyCoverRect(1, gfx::Rect(50, 112, 512, 381));
+
+ Initialize(gfx::Size(3, 3), 2.0f, gfx::Size(10, 10));
+ VerifyTilesExactlyCoverRect(1, gfx::Rect());
+ VerifyTilesExactlyCoverRect(1, gfx::Rect(0, 0, 1, 1));
+ VerifyTilesExactlyCoverRect(1, gfx::Rect(0, 0, 2, 2));
+ VerifyTilesExactlyCoverRect(1, gfx::Rect(1, 1, 2, 2));
+ VerifyTilesExactlyCoverRect(1, gfx::Rect(3, 2, 5, 2));
+
+ Initialize(gfx::Size(100, 200), 0.5f, gfx::Size(1005, 2010));
+ VerifyTilesExactlyCoverRect(1, gfx::Rect(0, 0, 1005, 2010));
+ VerifyTilesExactlyCoverRect(1, gfx::Rect(50, 112, 512, 381));
+
+ Initialize(gfx::Size(150, 250), 0.37f, gfx::Size(1005, 2010));
+ VerifyTilesExactlyCoverRect(1, gfx::Rect(0, 0, 1005, 2010));
+ VerifyTilesExactlyCoverRect(1, gfx::Rect(50, 112, 512, 381));
+
+ Initialize(gfx::Size(312, 123), 0.01f, gfx::Size(1005, 2010));
+ VerifyTilesExactlyCoverRect(1, gfx::Rect(0, 0, 1005, 2010));
+ VerifyTilesExactlyCoverRect(1, gfx::Rect(50, 112, 512, 381));
+}
+
+TEST_F(PictureLayerTilingIteratorTest, IteratorCoversLayerBoundsBothScale) {
+ Initialize(gfx::Size(50, 50), 4.0f, gfx::Size(800, 600));
+ VerifyTilesExactlyCoverRect(2.0f, gfx::Rect());
+ VerifyTilesExactlyCoverRect(2.0f, gfx::Rect(0, 0, 1600, 1200));
+ VerifyTilesExactlyCoverRect(2.0f, gfx::Rect(512, 365, 253, 182));
+
+ float scale = 6.7f;
+ gfx::Size bounds(800, 600);
+ gfx::Rect full_rect(gfx::ToCeiledSize(gfx::ScaleSize(bounds, scale)));
+ Initialize(gfx::Size(256, 512), 5.2f, bounds);
+ VerifyTilesExactlyCoverRect(scale, full_rect);
+ VerifyTilesExactlyCoverRect(scale, gfx::Rect(2014, 1579, 867, 1033));
+}
+
+TEST_F(PictureLayerTilingIteratorTest, IteratorEmptyRect) {
+ Initialize(gfx::Size(100, 100), 1.0f, gfx::Size(800, 600));
+
+ gfx::Rect empty;
+ PictureLayerTiling::CoverageIterator iter(tiling_.get(), 1.0f, empty);
+ EXPECT_FALSE(iter);
+}
+
+TEST_F(PictureLayerTilingIteratorTest, NonIntersectingRect) {
+ Initialize(gfx::Size(100, 100), 1.0f, gfx::Size(800, 600));
+ gfx::Rect non_intersecting(1000, 1000, 50, 50);
+ PictureLayerTiling::CoverageIterator iter(tiling_.get(), 1, non_intersecting);
+ EXPECT_FALSE(iter);
+}
+
+TEST_F(PictureLayerTilingIteratorTest, LayerEdgeTextureCoordinates) {
+ Initialize(gfx::Size(300, 300), 1.0f, gfx::Size(256, 256));
+ // All of these sizes are 256x256, scaled and ceiled.
+ VerifyTilesExactlyCoverRect(1.0f, gfx::Rect(0, 0, 256, 256));
+ VerifyTilesExactlyCoverRect(0.8f, gfx::Rect(0, 0, 205, 205));
+ VerifyTilesExactlyCoverRect(1.2f, gfx::Rect(0, 0, 308, 308));
+}
+
+TEST_F(PictureLayerTilingIteratorTest, NonContainedDestRect) {
+ Initialize(gfx::Size(100, 100), 1.0f, gfx::Size(400, 400));
+
+ // Too large in all dimensions
+ VerifyTilesCoverNonContainedRect(1.0f, gfx::Rect(-1000, -1000, 2000, 2000));
+ VerifyTilesCoverNonContainedRect(1.5f, gfx::Rect(-1000, -1000, 2000, 2000));
+ VerifyTilesCoverNonContainedRect(0.5f, gfx::Rect(-1000, -1000, 2000, 2000));
+
+ // Partially covering content, but too large
+ VerifyTilesCoverNonContainedRect(1.0f, gfx::Rect(-1000, 100, 2000, 100));
+ VerifyTilesCoverNonContainedRect(1.5f, gfx::Rect(-1000, 100, 2000, 100));
+ VerifyTilesCoverNonContainedRect(0.5f, gfx::Rect(-1000, 100, 2000, 100));
+}
+
+TEST(PictureLayerTilingTest, ExpandRectEqual) {
+ gfx::Rect in(40, 50, 100, 200);
+ gfx::Rect bounds(-1000, -1000, 10000, 10000);
+ int64 target_area = 100 * 200;
+ gfx::Rect out = PictureLayerTiling::ExpandRectEquallyToAreaBoundedBy(
+ in, target_area, bounds);
+ EXPECT_EQ(in.ToString(), out.ToString());
+}
+
+TEST(PictureLayerTilingTest, ExpandRectSmaller) {
+ gfx::Rect in(40, 50, 100, 200);
+ gfx::Rect bounds(-1000, -1000, 10000, 10000);
+ int64 target_area = 100 * 100;
+ gfx::Rect out = PictureLayerTiling::ExpandRectEquallyToAreaBoundedBy(
+ in, target_area, bounds);
+ EXPECT_EQ(out.bottom() - in.bottom(), in.y() - out.y());
+ EXPECT_EQ(out.right() - in.right(), in.x() - out.x());
+ EXPECT_EQ(out.width() - in.width(), out.height() - in.height());
+ EXPECT_NEAR(100 * 100, out.width() * out.height(), 50);
+ EXPECT_TRUE(bounds.Contains(out));
+}
+
+TEST(PictureLayerTilingTest, ExpandRectUnbounded) {
+ gfx::Rect in(40, 50, 100, 200);
+ gfx::Rect bounds(-1000, -1000, 10000, 10000);
+ int64 target_area = 200 * 200;
+ gfx::Rect out = PictureLayerTiling::ExpandRectEquallyToAreaBoundedBy(
+ in, target_area, bounds);
+ EXPECT_EQ(out.bottom() - in.bottom(), in.y() - out.y());
+ EXPECT_EQ(out.right() - in.right(), in.x() - out.x());
+ EXPECT_EQ(out.width() - in.width(), out.height() - in.height());
+ EXPECT_NEAR(200 * 200, out.width() * out.height(), 100);
+ EXPECT_TRUE(bounds.Contains(out));
+}
+
+TEST(PictureLayerTilingTest, ExpandRectBoundedSmaller) {
+ gfx::Rect in(40, 50, 100, 200);
+ gfx::Rect bounds(50, 60, 40, 30);
+ int64 target_area = 200 * 200;
+ gfx::Rect out = PictureLayerTiling::ExpandRectEquallyToAreaBoundedBy(
+ in, target_area, bounds);
+ EXPECT_EQ(bounds.ToString(), out.ToString());
+}
+
+TEST(PictureLayerTilingTest, ExpandRectBoundedEqual) {
+ gfx::Rect in(40, 50, 100, 200);
+ gfx::Rect bounds = in;
+ int64 target_area = 200 * 200;
+ gfx::Rect out = PictureLayerTiling::ExpandRectEquallyToAreaBoundedBy(
+ in, target_area, bounds);
+ EXPECT_EQ(bounds.ToString(), out.ToString());
+}
+
+TEST(PictureLayerTilingTest, ExpandRectBoundedSmallerStretchVertical) {
+ gfx::Rect in(40, 50, 100, 200);
+ gfx::Rect bounds(45, 0, 90, 300);
+ int64 target_area = 200 * 200;
+ gfx::Rect out = PictureLayerTiling::ExpandRectEquallyToAreaBoundedBy(
+ in, target_area, bounds);
+ EXPECT_EQ(bounds.ToString(), out.ToString());
+}
+
+TEST(PictureLayerTilingTest, ExpandRectBoundedEqualStretchVertical) {
+ gfx::Rect in(40, 50, 100, 200);
+ gfx::Rect bounds(40, 0, 100, 300);
+ int64 target_area = 200 * 200;
+ gfx::Rect out = PictureLayerTiling::ExpandRectEquallyToAreaBoundedBy(
+ in, target_area, bounds);
+ EXPECT_EQ(bounds.ToString(), out.ToString());
+}
+
+TEST(PictureLayerTilingTest, ExpandRectBoundedSmallerStretchHorizontal) {
+ gfx::Rect in(40, 50, 100, 200);
+ gfx::Rect bounds(0, 55, 180, 190);
+ int64 target_area = 200 * 200;
+ gfx::Rect out = PictureLayerTiling::ExpandRectEquallyToAreaBoundedBy(
+ in, target_area, bounds);
+ EXPECT_EQ(bounds.ToString(), out.ToString());
+}
+
+TEST(PictureLayerTilingTest, ExpandRectBoundedEqualStretchHorizontal) {
+ gfx::Rect in(40, 50, 100, 200);
+ gfx::Rect bounds(0, 50, 180, 200);
+ int64 target_area = 200 * 200;
+ gfx::Rect out = PictureLayerTiling::ExpandRectEquallyToAreaBoundedBy(
+ in, target_area, bounds);
+ EXPECT_EQ(bounds.ToString(), out.ToString());
+}
+
+TEST(PictureLayerTilingTest, ExpandRectBoundedLeft) {
+ gfx::Rect in(40, 50, 100, 200);
+ gfx::Rect bounds(20, -1000, 10000, 10000);
+ int64 target_area = 200 * 200;
+ gfx::Rect out = PictureLayerTiling::ExpandRectEquallyToAreaBoundedBy(
+ in, target_area, bounds);
+ EXPECT_EQ(out.bottom() - in.bottom(), in.y() - out.y());
+ EXPECT_EQ(out.bottom() - in.bottom(), out.right() - in.right());
+ EXPECT_LE(out.width() * out.height(), target_area);
+ EXPECT_GT(out.width() * out.height(),
+ target_area - out.width() - out.height() * 2);
+ EXPECT_TRUE(bounds.Contains(out));
+}
+
+TEST(PictureLayerTilingTest, ExpandRectBoundedRight) {
+ gfx::Rect in(40, 50, 100, 200);
+ gfx::Rect bounds(-1000, -1000, 1000+120, 10000);
+ int64 target_area = 200 * 200;
+ gfx::Rect out = PictureLayerTiling::ExpandRectEquallyToAreaBoundedBy(
+ in, target_area, bounds);
+ EXPECT_EQ(out.bottom() - in.bottom(), in.y() - out.y());
+ EXPECT_EQ(out.bottom() - in.bottom(), in.x() - out.x());
+ EXPECT_LE(out.width() * out.height(), target_area);
+ EXPECT_GT(out.width() * out.height(),
+ target_area - out.width() - out.height() * 2);
+ EXPECT_TRUE(bounds.Contains(out));
+}
+
+TEST(PictureLayerTilingTest, ExpandRectBoundedTop) {
+ gfx::Rect in(40, 50, 100, 200);
+ gfx::Rect bounds(-1000, 30, 10000, 10000);
+ int64 target_area = 200 * 200;
+ gfx::Rect out = PictureLayerTiling::ExpandRectEquallyToAreaBoundedBy(
+ in, target_area, bounds);
+ EXPECT_EQ(out.right() - in.right(), in.x() - out.x());
+ EXPECT_EQ(out.right() - in.right(), out.bottom() - in.bottom());
+ EXPECT_LE(out.width() * out.height(), target_area);
+ EXPECT_GT(out.width() * out.height(),
+ target_area - out.width() * 2 - out.height());
+ EXPECT_TRUE(bounds.Contains(out));
+}
+
+TEST(PictureLayerTilingTest, ExpandRectBoundedBottom) {
+ gfx::Rect in(40, 50, 100, 200);
+ gfx::Rect bounds(-1000, -1000, 10000, 1000 + 220);
+ int64 target_area = 200 * 200;
+ gfx::Rect out = PictureLayerTiling::ExpandRectEquallyToAreaBoundedBy(
+ in, target_area, bounds);
+ EXPECT_EQ(out.right() - in.right(), in.x() - out.x());
+ EXPECT_EQ(out.right() - in.right(), in.y() - out.y());
+ EXPECT_LE(out.width() * out.height(), target_area);
+ EXPECT_GT(out.width() * out.height(),
+ target_area - out.width() * 2 - out.height());
+ EXPECT_TRUE(bounds.Contains(out));
+}
+
+TEST(PictureLayerTilingTest, ExpandRectSquishedHorizontally) {
+ gfx::Rect in(40, 50, 100, 200);
+ gfx::Rect bounds(0, -4000, 100+40+20, 100000);
+ int64 target_area = 400 * 400;
+ gfx::Rect out = PictureLayerTiling::ExpandRectEquallyToAreaBoundedBy(
+ in, target_area, bounds);
+ EXPECT_EQ(20, out.right() - in.right());
+ EXPECT_EQ(40, in.x() - out.x());
+ EXPECT_EQ(out.bottom() - in.bottom(), in.y() - out.y());
+ EXPECT_LE(out.width() * out.height(), target_area);
+ EXPECT_GT(out.width() * out.height(),
+ target_area - out.width() * 2);
+ EXPECT_TRUE(bounds.Contains(out));
+}
+
+TEST(PictureLayerTilingTest, ExpandRectSquishedVertically) {
+ gfx::Rect in(40, 50, 100, 200);
+ gfx::Rect bounds(-4000, 0, 100000, 200+50+30);
+ int64 target_area = 400 * 400;
+ gfx::Rect out = PictureLayerTiling::ExpandRectEquallyToAreaBoundedBy(
+ in, target_area, bounds);
+ EXPECT_EQ(30, out.bottom() - in.bottom());
+ EXPECT_EQ(50, in.y() - out.y());
+ EXPECT_EQ(out.right() - in.right(), in.x() - out.x());
+ EXPECT_LE(out.width() * out.height(), target_area);
+ EXPECT_GT(out.width() * out.height(),
+ target_area - out.height() * 2);
+ EXPECT_TRUE(bounds.Contains(out));
+}
+
+TEST(PictureLayerTilingTest, ExpandRectOutOfBoundsFarAway) {
+ gfx::Rect in(400, 500, 100, 200);
+ gfx::Rect bounds(0, 0, 10, 10);
+ int64 target_area = 400 * 400;
+ gfx::Rect out = PictureLayerTiling::ExpandRectEquallyToAreaBoundedBy(
+ in, target_area, bounds);
+ EXPECT_TRUE(out.IsEmpty());
+}
+
+TEST(PictureLayerTilingTest, ExpandRectOutOfBoundsExpandedFullyCover) {
+ gfx::Rect in(40, 50, 100, 100);
+ gfx::Rect bounds(0, 0, 10, 10);
+ int64 target_area = 400 * 400;
+ gfx::Rect out = PictureLayerTiling::ExpandRectEquallyToAreaBoundedBy(
+ in, target_area, bounds);
+ EXPECT_EQ(bounds.ToString(), out.ToString());
+}
+
+TEST(PictureLayerTilingTest, ExpandRectOutOfBoundsExpandedPartlyCover) {
+ gfx::Rect in(600, 600, 100, 100);
+ gfx::Rect bounds(0, 0, 500, 500);
+ int64 target_area = 400 * 400;
+ gfx::Rect out = PictureLayerTiling::ExpandRectEquallyToAreaBoundedBy(
+ in, target_area, bounds);
+ EXPECT_EQ(bounds.right(), out.right());
+ EXPECT_EQ(bounds.bottom(), out.bottom());
+ EXPECT_LE(out.width() * out.height(), target_area);
+ EXPECT_GT(out.width() * out.height(),
+ target_area - out.width() - out.height());
+ EXPECT_TRUE(bounds.Contains(out));
+}
+
+TEST(PictureLayerTilingTest, EmptyStartingRect) {
+ // If a layer has a non-invertible transform, then the starting rect
+ // for the layer would be empty.
+ gfx::Rect in(40, 40, 0, 0);
+ gfx::Rect bounds(0, 0, 10, 10);
+ int64 target_area = 400 * 400;
+ gfx::Rect out = PictureLayerTiling::ExpandRectEquallyToAreaBoundedBy(
+ in, target_area, bounds);
+ EXPECT_TRUE(out.IsEmpty());
+}
+
+static void TileExists(bool exists, Tile* tile, gfx::Rect geometry_rect) {
+ EXPECT_EQ(exists, tile != NULL) << geometry_rect.ToString();
+}
+
+TEST_F(PictureLayerTilingIteratorTest, TilesExist) {
+ gfx::Size layer_bounds(1099, 801);
+ Initialize(gfx::Size(100, 100), 1.f, layer_bounds);
+ VerifyTilesExactlyCoverRect(1.f, gfx::Rect(layer_bounds));
+ VerifyTiles(1.f, gfx::Rect(layer_bounds), base::Bind(&TileExists, false));
+
+ tiling_->UpdateTilePriorities(
+ ACTIVE_TREE,
+ layer_bounds, // device viewport
+ gfx::Rect(layer_bounds), // viewport in layer space
+ gfx::Rect(layer_bounds), // visible content rect
+ layer_bounds, // last layer bounds
+ layer_bounds, // current layer bounds
+ 1.f, // last contents scale
+ 1.f, // current contents scale
+ gfx::Transform(), // last screen transform
+ gfx::Transform(), // current screen transform
+ 1.0, // current frame time
+ 10000); // max tiles in tile manager
+ VerifyTiles(1.f, gfx::Rect(layer_bounds), base::Bind(&TileExists, true));
+
+ // Make the viewport rect empty. All tiles are killed and become zombies.
+ tiling_->UpdateTilePriorities(
+ ACTIVE_TREE,
+ layer_bounds, // device viewport
+ gfx::Rect(), // viewport in layer space
+ gfx::Rect(), // visible content rect
+ layer_bounds, // last layer bounds
+ layer_bounds, // current layer bounds
+ 1.f, // last contents scale
+ 1.f, // current contents scale
+ gfx::Transform(), // last screen transform
+ gfx::Transform(), // current screen transform
+ 2.0, // current frame time
+ 10000); // max tiles in tile manager
+ VerifyTiles(1.f, gfx::Rect(layer_bounds), base::Bind(&TileExists, false));
+}
+
+TEST_F(PictureLayerTilingIteratorTest, TilesExistGiantViewport) {
+ gfx::Size layer_bounds(1099, 801);
+ Initialize(gfx::Size(100, 100), 1.f, layer_bounds);
+ VerifyTilesExactlyCoverRect(1.f, gfx::Rect(layer_bounds));
+ VerifyTiles(1.f, gfx::Rect(layer_bounds), base::Bind(&TileExists, false));
+
+ gfx::Rect giant_rect(-10000000, -10000000, 1000000000, 1000000000);
+
+ tiling_->UpdateTilePriorities(
+ ACTIVE_TREE,
+ layer_bounds, // device viewport
+ giant_rect, // viewport in layer space
+ gfx::Rect(layer_bounds), // visible content rect
+ layer_bounds, // last layer bounds
+ layer_bounds, // current layer bounds
+ 1.f, // last contents scale
+ 1.f, // current contents scale
+ gfx::Transform(), // last screen transform
+ gfx::Transform(), // current screen transform
+ 1.0, // current frame time
+ 10000); // max tiles in tile manager
+ VerifyTiles(1.f, gfx::Rect(layer_bounds), base::Bind(&TileExists, true));
+
+ // If the visible content rect is empty, it should still have live tiles.
+ tiling_->UpdateTilePriorities(
+ ACTIVE_TREE,
+ layer_bounds, // device viewport
+ giant_rect, // viewport in layer space
+ gfx::Rect(), // visible content rect
+ layer_bounds, // last layer bounds
+ layer_bounds, // current layer bounds
+ 1.f, // last contents scale
+ 1.f, // current contents scale
+ gfx::Transform(), // last screen transform
+ gfx::Transform(), // current screen transform
+ 2.0, // current frame time
+ 10000); // max tiles in tile manager
+ VerifyTiles(1.f, gfx::Rect(layer_bounds), base::Bind(&TileExists, true));
+}
+
+TEST_F(PictureLayerTilingIteratorTest, TilesExistOutsideViewport) {
+ gfx::Size layer_bounds(1099, 801);
+ Initialize(gfx::Size(100, 100), 1.f, layer_bounds);
+ VerifyTilesExactlyCoverRect(1.f, gfx::Rect(layer_bounds));
+ VerifyTiles(1.f, gfx::Rect(layer_bounds), base::Bind(&TileExists, false));
+
+ // This rect does not intersect with the layer, as the layer is outside the
+ // viewport.
+ gfx::Rect viewport_rect(1100, 0, 1000, 1000);
+ EXPECT_FALSE(viewport_rect.Intersects(gfx::Rect(layer_bounds)));
+
+ tiling_->UpdateTilePriorities(
+ ACTIVE_TREE,
+ layer_bounds, // device viewport
+ viewport_rect, // viewport in layer space
+ gfx::Rect(), // visible content rect
+ layer_bounds, // last layer bounds
+ layer_bounds, // current layer bounds
+ 1.f, // last contents scale
+ 1.f, // current contents scale
+ gfx::Transform(), // last screen transform
+ gfx::Transform(), // current screen transform
+ 1.0, // current frame time
+ 10000); // max tiles in tile manager
+ VerifyTiles(1.f, gfx::Rect(layer_bounds), base::Bind(&TileExists, true));
+}
+
+static void TilesIntersectingRectExist(gfx::Rect rect,
+ bool intersect_exists,
+ Tile* tile,
+ gfx::Rect geometry_rect) {
+ bool intersects = rect.Intersects(geometry_rect);
+ bool expected_exists = intersect_exists ? intersects : !intersects;
+ EXPECT_EQ(expected_exists, tile != NULL)
+ << "Rects intersecting " << rect.ToString() << " should exist. "
+ << "Current tile rect is " << geometry_rect.ToString();
+}
+
+TEST_F(PictureLayerTilingIteratorTest,
+ TilesExistLargeViewportAndLayerWithSmallVisibleArea) {
+ gfx::Size layer_bounds(10000, 10000);
+ Initialize(gfx::Size(100, 100), 1.f, layer_bounds);
+ VerifyTilesExactlyCoverRect(1.f, gfx::Rect(layer_bounds));
+ VerifyTiles(1.f, gfx::Rect(layer_bounds), base::Bind(&TileExists, false));
+
+ gfx::Rect visible_rect(8000, 8000, 50, 50);
+
+ tiling_->UpdateTilePriorities(
+ ACTIVE_TREE,
+ layer_bounds, // device viewport
+ gfx::Rect(layer_bounds), // viewport in layer space
+ visible_rect, // visible content rect
+ layer_bounds, // last layer bounds
+ layer_bounds, // current layer bounds
+ 1.f, // last contents scale
+ 1.f, // current contents scale
+ gfx::Transform(), // last screen transform
+ gfx::Transform(), // current screen transform
+ 1.0, // current frame time
+ 1); // max tiles in tile manager
+ VerifyTiles(1.f,
+ gfx::Rect(layer_bounds),
+ base::Bind(&TilesIntersectingRectExist, visible_rect, true));
+}
+
+static void CountExistingTiles(int *count,
+ Tile* tile,
+ gfx::Rect geometry_rect) {
+ if (tile != NULL)
+ ++(*count);
+}
+
+TEST_F(PictureLayerTilingIteratorTest,
+ TilesExistLargeViewportAndLayerWithLargeVisibleArea) {
+ gfx::Size layer_bounds(10000, 10000);
+ Initialize(gfx::Size(100, 100), 1.f, layer_bounds);
+ VerifyTilesExactlyCoverRect(1.f, gfx::Rect(layer_bounds));
+ VerifyTiles(1.f, gfx::Rect(layer_bounds), base::Bind(&TileExists, false));
+
+ tiling_->UpdateTilePriorities(
+ ACTIVE_TREE,
+ layer_bounds, // device viewport
+ gfx::Rect(layer_bounds), // viewport in layer space
+ gfx::Rect(layer_bounds), // visible content rect
+ layer_bounds, // last layer bounds
+ layer_bounds, // current layer bounds
+ 1.f, // last contents scale
+ 1.f, // current contents scale
+ gfx::Transform(), // last screen transform
+ gfx::Transform(), // current screen transform
+ 1.0, // current frame time
+ 1); // max tiles in tile manager
+
+ int num_tiles = 0;
+ VerifyTiles(1.f,
+ gfx::Rect(layer_bounds),
+ base::Bind(&CountExistingTiles, &num_tiles));
+ // If we're making a rect the size of one tile, it can only overlap up to 4
+ // tiles depending on its position.
+ EXPECT_LE(num_tiles, 4);
+ VerifyTiles(1.f, gfx::Rect(), base::Bind(&TileExists, false));
+}
+
+TEST_F(PictureLayerTilingIteratorTest, AddTilingsToMatchScale) {
+ gfx::Size layer_bounds(1099, 801);
+ gfx::Size tile_size(100, 100);
+
+ client_.SetTileSize(tile_size);
+
+ PictureLayerTilingSet active_set(&client_, layer_bounds);
+
+ active_set.AddTiling(1.f);
+
+ VerifyTiles(active_set.tiling_at(0),
+ 1.f,
+ gfx::Rect(layer_bounds),
+ base::Bind(&TileExists, false));
+
+ active_set.UpdateTilePriorities(
+ PENDING_TREE,
+ layer_bounds, // device viewport
+ gfx::Rect(layer_bounds), // viewport in layer space
+ gfx::Rect(layer_bounds), // visible content rect
+ layer_bounds, // last layer bounds
+ layer_bounds, // current layer bounds
+ 1.f, // last contents scale
+ 1.f, // current contents scale
+ gfx::Transform(), // last screen transform
+ gfx::Transform(), // current screen transform
+ 1.0, // current frame time
+ 10000); // max tiles in tile manager
+
+ // The active tiling has tiles now.
+ VerifyTiles(active_set.tiling_at(0),
+ 1.f,
+ gfx::Rect(layer_bounds),
+ base::Bind(&TileExists, true));
+
+ // Add the same tilings to the pending set.
+ PictureLayerTilingSet pending_set(&client_, layer_bounds);
+ Region invalidation;
+ pending_set.SyncTilings(active_set, layer_bounds, invalidation, 0.f);
+
+ // The pending tiling starts with no tiles.
+ VerifyTiles(pending_set.tiling_at(0),
+ 1.f,
+ gfx::Rect(layer_bounds),
+ base::Bind(&TileExists, false));
+
+ // UpdateTilePriorities on the pending tiling at the same frame time. The
+ // pending tiling should get tiles.
+ pending_set.UpdateTilePriorities(
+ PENDING_TREE,
+ layer_bounds, // device viewport
+ gfx::Rect(layer_bounds), // viewport in layer space
+ gfx::Rect(layer_bounds), // visible content rect
+ layer_bounds, // last layer bounds
+ layer_bounds, // current layer bounds
+ 1.f, // last contents scale
+ 1.f, // current contents scale
+ gfx::Transform(), // last screen transform
+ gfx::Transform(), // current screen transform
+ 1.0, // current frame time
+ 10000); // max tiles in tile manager
+
+ VerifyTiles(pending_set.tiling_at(0),
+ 1.f,
+ gfx::Rect(layer_bounds),
+ base::Bind(&TileExists, true));
+}
+
+TEST(UpdateTilePrioritiesTest, VisibleTiles) {
+ // The TilePriority of visible tiles should have zero distance_to_visible
+ // and time_to_visible.
+
+ FakePictureLayerTilingClient client;
+ scoped_ptr<TestablePictureLayerTiling> tiling;
+
+ gfx::Size device_viewport(800, 600);
+ gfx::Rect visible_layer_rect(0, 0, 200, 200);
+ gfx::Size last_layer_bounds(200, 200);
+ gfx::Size current_layer_bounds(200, 200);
+ float last_layer_contents_scale = 1.f;
+ float current_layer_contents_scale = 1.f;
+ gfx::Transform last_screen_transform;
+ gfx::Transform current_screen_transform;
+ double current_frame_time_in_seconds = 1.0;
+ size_t max_tiles_for_interest_area = 10000;
+
+ gfx::Rect viewport_in_layer_space = ViewportInLayerSpace(
+ current_screen_transform, device_viewport);
+
+ client.SetTileSize(gfx::Size(100, 100));
+ tiling = TestablePictureLayerTiling::Create(1.0f, // contents_scale
+ current_layer_bounds,
+ &client);
+
+ tiling->UpdateTilePriorities(
+ ACTIVE_TREE,
+ device_viewport,
+ viewport_in_layer_space,
+ visible_layer_rect,
+ last_layer_bounds,
+ current_layer_bounds,
+ last_layer_contents_scale,
+ current_layer_contents_scale,
+ last_screen_transform,
+ current_screen_transform,
+ current_frame_time_in_seconds,
+ max_tiles_for_interest_area);
+
+ ASSERT_TRUE(tiling->TileAt(0, 0));
+ ASSERT_TRUE(tiling->TileAt(0, 1));
+ ASSERT_TRUE(tiling->TileAt(1, 0));
+ ASSERT_TRUE(tiling->TileAt(1, 1));
+
+ TilePriority priority = tiling->TileAt(0, 0)->priority(ACTIVE_TREE);
+ EXPECT_FLOAT_EQ(0.f, priority.distance_to_visible_in_pixels);
+ EXPECT_FLOAT_EQ(0.f, priority.time_to_visible_in_seconds);
+
+ priority = tiling->TileAt(0, 1)->priority(ACTIVE_TREE);
+ EXPECT_FLOAT_EQ(0.f, priority.distance_to_visible_in_pixels);
+ EXPECT_FLOAT_EQ(0.f, priority.time_to_visible_in_seconds);
+
+ priority = tiling->TileAt(1, 0)->priority(ACTIVE_TREE);
+ EXPECT_FLOAT_EQ(0.f, priority.distance_to_visible_in_pixels);
+ EXPECT_FLOAT_EQ(0.f, priority.time_to_visible_in_seconds);
+
+ priority = tiling->TileAt(1, 1)->priority(ACTIVE_TREE);
+ EXPECT_FLOAT_EQ(0.f, priority.distance_to_visible_in_pixels);
+ EXPECT_FLOAT_EQ(0.f, priority.time_to_visible_in_seconds);
+}
+
+TEST(UpdateTilePrioritiesTest, OffscreenTiles) {
+ // The TilePriority of offscreen tiles (without movement) should have nonzero
+ // distance_to_visible and infinite time_to_visible.
+
+ FakePictureLayerTilingClient client;
+ scoped_ptr<TestablePictureLayerTiling> tiling;
+
+ gfx::Size device_viewport(800, 600);
+ gfx::Rect visible_layer_rect(0, 0, 0, 0); // offscreen; nothing is visible.
+ gfx::Size last_layer_bounds(200, 200);
+ gfx::Size current_layer_bounds(200, 200);
+ float last_layer_contents_scale = 1.f;
+ float current_layer_contents_scale = 1.f;
+ gfx::Transform last_screen_transform;
+ gfx::Transform current_screen_transform;
+ double current_frame_time_in_seconds = 1.0;
+ size_t max_tiles_for_interest_area = 10000;
+
+ current_screen_transform.Translate(850, 0);
+ last_screen_transform = current_screen_transform;
+
+ gfx::Rect viewport_in_layer_space = ViewportInLayerSpace(
+ current_screen_transform, device_viewport);
+
+ client.SetTileSize(gfx::Size(100, 100));
+ tiling = TestablePictureLayerTiling::Create(1.0f, // contents_scale
+ current_layer_bounds,
+ &client);
+
+ tiling->UpdateTilePriorities(
+ ACTIVE_TREE,
+ device_viewport,
+ viewport_in_layer_space,
+ visible_layer_rect,
+ last_layer_bounds,
+ current_layer_bounds,
+ last_layer_contents_scale,
+ current_layer_contents_scale,
+ last_screen_transform,
+ current_screen_transform,
+ current_frame_time_in_seconds,
+ max_tiles_for_interest_area);
+
+ ASSERT_TRUE(tiling->TileAt(0, 0));
+ ASSERT_TRUE(tiling->TileAt(0, 1));
+ ASSERT_TRUE(tiling->TileAt(1, 0));
+ ASSERT_TRUE(tiling->TileAt(1, 1));
+
+ TilePriority priority = tiling->TileAt(0, 0)->priority(ACTIVE_TREE);
+ EXPECT_GT(priority.distance_to_visible_in_pixels, 0.f);
+ EXPECT_FLOAT_EQ(std::numeric_limits<float>::infinity(),
+ priority.time_to_visible_in_seconds);
+
+ priority = tiling->TileAt(0, 1)->priority(ACTIVE_TREE);
+ EXPECT_GT(priority.distance_to_visible_in_pixels, 0.f);
+ EXPECT_FLOAT_EQ(std::numeric_limits<float>::infinity(),
+ priority.time_to_visible_in_seconds);
+
+ priority = tiling->TileAt(1, 0)->priority(ACTIVE_TREE);
+ EXPECT_GT(priority.distance_to_visible_in_pixels, 0.f);
+ EXPECT_FLOAT_EQ(std::numeric_limits<float>::infinity(),
+ priority.time_to_visible_in_seconds);
+
+ priority = tiling->TileAt(1, 1)->priority(ACTIVE_TREE);
+ EXPECT_GT(priority.distance_to_visible_in_pixels, 0.f);
+ EXPECT_FLOAT_EQ(std::numeric_limits<float>::infinity(),
+ priority.time_to_visible_in_seconds);
+
+ // Furthermore, in this scenario tiles on the right hand side should have a
+ // larger distance to visible.
+ TilePriority left = tiling->TileAt(0, 0)->priority(ACTIVE_TREE);
+ TilePriority right = tiling->TileAt(1, 0)->priority(ACTIVE_TREE);
+ EXPECT_GT(right.distance_to_visible_in_pixels,
+ left.distance_to_visible_in_pixels);
+
+ left = tiling->TileAt(0, 1)->priority(ACTIVE_TREE);
+ right = tiling->TileAt(1, 1)->priority(ACTIVE_TREE);
+ EXPECT_GT(right.distance_to_visible_in_pixels,
+ left.distance_to_visible_in_pixels);
+}
+
+TEST(UpdateTilePrioritiesTest, PartiallyOffscreenLayer) {
+ // Sanity check that a layer with some tiles visible and others offscreen has
+ // correct TilePriorities for each tile.
+
+ FakePictureLayerTilingClient client;
+ scoped_ptr<TestablePictureLayerTiling> tiling;
+
+ gfx::Size device_viewport(800, 600);
+ gfx::Rect visible_layer_rect(0, 0, 100, 100); // only top quarter.
+ gfx::Size last_layer_bounds(200, 200);
+ gfx::Size current_layer_bounds(200, 200);
+ float last_layer_contents_scale = 1.f;
+ float current_layer_contents_scale = 1.f;
+ gfx::Transform last_screen_transform;
+ gfx::Transform current_screen_transform;
+ double current_frame_time_in_seconds = 1.0;
+ size_t max_tiles_for_interest_area = 10000;
+
+ current_screen_transform.Translate(705, 505);
+ last_screen_transform = current_screen_transform;
+
+ gfx::Rect viewport_in_layer_space = ViewportInLayerSpace(
+ current_screen_transform, device_viewport);
+
+ client.SetTileSize(gfx::Size(100, 100));
+ tiling = TestablePictureLayerTiling::Create(1.0f, // contents_scale
+ current_layer_bounds,
+ &client);
+
+ tiling->UpdateTilePriorities(
+ ACTIVE_TREE,
+ device_viewport,
+ viewport_in_layer_space,
+ visible_layer_rect,
+ last_layer_bounds,
+ current_layer_bounds,
+ last_layer_contents_scale,
+ current_layer_contents_scale,
+ last_screen_transform,
+ current_screen_transform,
+ current_frame_time_in_seconds,
+ max_tiles_for_interest_area);
+
+ ASSERT_TRUE(tiling->TileAt(0, 0));
+ ASSERT_TRUE(tiling->TileAt(0, 1));
+ ASSERT_TRUE(tiling->TileAt(1, 0));
+ ASSERT_TRUE(tiling->TileAt(1, 1));
+
+ TilePriority priority = tiling->TileAt(0, 0)->priority(ACTIVE_TREE);
+ EXPECT_FLOAT_EQ(0.f, priority.distance_to_visible_in_pixels);
+ EXPECT_FLOAT_EQ(0.f, priority.time_to_visible_in_seconds);
+
+ priority = tiling->TileAt(0, 1)->priority(ACTIVE_TREE);
+ EXPECT_GT(priority.distance_to_visible_in_pixels, 0.f);
+ EXPECT_FLOAT_EQ(std::numeric_limits<float>::infinity(),
+ priority.time_to_visible_in_seconds);
+
+ priority = tiling->TileAt(1, 0)->priority(ACTIVE_TREE);
+ EXPECT_GT(priority.distance_to_visible_in_pixels, 0.f);
+ EXPECT_FLOAT_EQ(std::numeric_limits<float>::infinity(),
+ priority.time_to_visible_in_seconds);
+
+ priority = tiling->TileAt(1, 1)->priority(ACTIVE_TREE);
+ EXPECT_GT(priority.distance_to_visible_in_pixels, 0.f);
+ EXPECT_FLOAT_EQ(std::numeric_limits<float>::infinity(),
+ priority.time_to_visible_in_seconds);
+}
+
+TEST(UpdateTilePrioritiesTest, PartiallyOffscreenRotatedLayer) {
+ // Each tile of a layer may be affected differently by a transform; Check
+ // that UpdateTilePriorities correctly accounts for the transform between
+ // layer space and screen space.
+
+ FakePictureLayerTilingClient client;
+ scoped_ptr<TestablePictureLayerTiling> tiling;
+
+ gfx::Size device_viewport(800, 600);
+ gfx::Rect visible_layer_rect(0, 0, 100, 100); // only top-left quarter.
+ gfx::Size last_layer_bounds(200, 200);
+ gfx::Size current_layer_bounds(200, 200);
+ float last_layer_contents_scale = 1.f;
+ float current_layer_contents_scale = 1.f;
+ gfx::Transform last_screen_transform;
+ gfx::Transform current_screen_transform;
+ double current_frame_time_in_seconds = 1.0;
+ size_t max_tiles_for_interest_area = 10000;
+
+ // A diagonally rotated layer that is partially off the bottom of the screen.
+ // In this configuration, only the top-left tile would be visible.
+ current_screen_transform.Translate(400, 550);
+ current_screen_transform.RotateAboutZAxis(45);
+ last_screen_transform = current_screen_transform;
+
+ gfx::Rect viewport_in_layer_space = ViewportInLayerSpace(
+ current_screen_transform, device_viewport);
+
+ client.SetTileSize(gfx::Size(100, 100));
+ tiling = TestablePictureLayerTiling::Create(1.0f, // contents_scale
+ current_layer_bounds,
+ &client);
+
+ tiling->UpdateTilePriorities(
+ ACTIVE_TREE,
+ device_viewport,
+ viewport_in_layer_space,
+ visible_layer_rect,
+ last_layer_bounds,
+ current_layer_bounds,
+ last_layer_contents_scale,
+ current_layer_contents_scale,
+ last_screen_transform,
+ current_screen_transform,
+ current_frame_time_in_seconds,
+ max_tiles_for_interest_area);
+
+ ASSERT_TRUE(tiling->TileAt(0, 0));
+ ASSERT_TRUE(tiling->TileAt(0, 1));
+ ASSERT_TRUE(tiling->TileAt(1, 0));
+ ASSERT_TRUE(tiling->TileAt(1, 1));
+
+ TilePriority priority = tiling->TileAt(0, 0)->priority(ACTIVE_TREE);
+ EXPECT_FLOAT_EQ(0.f, priority.distance_to_visible_in_pixels);
+ EXPECT_FLOAT_EQ(0.f, priority.time_to_visible_in_seconds);
+
+ priority = tiling->TileAt(0, 1)->priority(ACTIVE_TREE);
+ EXPECT_GT(priority.distance_to_visible_in_pixels, 0.f);
+ EXPECT_FLOAT_EQ(std::numeric_limits<float>::infinity(),
+ priority.time_to_visible_in_seconds);
+
+ priority = tiling->TileAt(1, 0)->priority(ACTIVE_TREE);
+ EXPECT_GT(priority.distance_to_visible_in_pixels, 0.f);
+ EXPECT_FLOAT_EQ(std::numeric_limits<float>::infinity(),
+ priority.time_to_visible_in_seconds);
+
+ priority = tiling->TileAt(1, 1)->priority(ACTIVE_TREE);
+ EXPECT_GT(priority.distance_to_visible_in_pixels, 0.f);
+ EXPECT_FLOAT_EQ(std::numeric_limits<float>::infinity(),
+ priority.time_to_visible_in_seconds);
+
+ // Furthermore, in this scenario the bottom-right tile should have the larger
+ // distance to visible.
+ TilePriority top_left = tiling->TileAt(0, 0)->priority(ACTIVE_TREE);
+ TilePriority top_right = tiling->TileAt(1, 0)->priority(ACTIVE_TREE);
+ TilePriority bottom_left = tiling->TileAt(0, 1)->priority(ACTIVE_TREE);
+ TilePriority bottom_right = tiling->TileAt(1, 1)->priority(ACTIVE_TREE);
+ EXPECT_GT(top_right.distance_to_visible_in_pixels,
+ top_left.distance_to_visible_in_pixels);
+ EXPECT_GT(bottom_left.distance_to_visible_in_pixels,
+ top_left.distance_to_visible_in_pixels);
+
+ EXPECT_GT(bottom_right.distance_to_visible_in_pixels,
+ bottom_left.distance_to_visible_in_pixels);
+ EXPECT_GT(bottom_right.distance_to_visible_in_pixels,
+ top_right.distance_to_visible_in_pixels);
+}
+
+TEST(UpdateTilePrioritiesTest, PerspectiveLayer) {
+ // Perspective transforms need to take a different code path.
+ // This test checks tile priorities of a perspective layer.
+
+ FakePictureLayerTilingClient client;
+ scoped_ptr<TestablePictureLayerTiling> tiling;
+
+ gfx::Size device_viewport(800, 600);
+ gfx::Rect visible_layer_rect(0, 0, 0, 0); // offscreen.
+ gfx::Size last_layer_bounds(200, 200);
+ gfx::Size current_layer_bounds(200, 200);
+ float last_layer_contents_scale = 1.f;
+ float current_layer_contents_scale = 1.f;
+ gfx::Transform last_screen_transform;
+ gfx::Transform current_screen_transform;
+ double current_frame_time_in_seconds = 1.0;
+ size_t max_tiles_for_interest_area = 10000;
+
+ // A 3d perspective layer rotated about its Y axis, translated to almost
+ // fully offscreen. The left side will appear closer (i.e. larger in 2d) than
+ // the right side, so the top-left tile will technically be closer than the
+ // top-right.
+
+ // Translate layer to offscreen
+ current_screen_transform.Translate(400.0, 630.0);
+ // Apply perspective about the center of the layer
+ current_screen_transform.Translate(100.0, 100.0);
+ current_screen_transform.ApplyPerspectiveDepth(100.0);
+ current_screen_transform.RotateAboutYAxis(10.0);
+ current_screen_transform.Translate(-100.0, -100.0);
+ last_screen_transform = current_screen_transform;
+
+ // Sanity check that this transform wouldn't cause w<0 clipping.
+ bool clipped;
+ MathUtil::MapQuad(current_screen_transform,
+ gfx::QuadF(gfx::RectF(0, 0, 200, 200)),
+ &clipped);
+ ASSERT_FALSE(clipped);
+
+ gfx::Rect viewport_in_layer_space = ViewportInLayerSpace(
+ current_screen_transform, device_viewport);
+
+ client.SetTileSize(gfx::Size(100, 100));
+ tiling = TestablePictureLayerTiling::Create(1.0f, // contents_scale
+ current_layer_bounds,
+ &client);
+
+ tiling->UpdateTilePriorities(
+ ACTIVE_TREE,
+ device_viewport,
+ viewport_in_layer_space,
+ visible_layer_rect,
+ last_layer_bounds,
+ current_layer_bounds,
+ last_layer_contents_scale,
+ current_layer_contents_scale,
+ last_screen_transform,
+ current_screen_transform,
+ current_frame_time_in_seconds,
+ max_tiles_for_interest_area);
+
+ ASSERT_TRUE(tiling->TileAt(0, 0));
+ ASSERT_TRUE(tiling->TileAt(0, 1));
+ ASSERT_TRUE(tiling->TileAt(1, 0));
+ ASSERT_TRUE(tiling->TileAt(1, 1));
+
+ // All tiles will have a positive distance_to_visible
+ // and an infinite time_to_visible.
+ TilePriority priority = tiling->TileAt(0, 0)->priority(ACTIVE_TREE);
+ EXPECT_GT(priority.distance_to_visible_in_pixels, 0.f);
+ EXPECT_FLOAT_EQ(std::numeric_limits<float>::infinity(),
+ priority.time_to_visible_in_seconds);
+
+ priority = tiling->TileAt(0, 1)->priority(ACTIVE_TREE);
+ EXPECT_GT(priority.distance_to_visible_in_pixels, 0.f);
+ EXPECT_FLOAT_EQ(std::numeric_limits<float>::infinity(),
+ priority.time_to_visible_in_seconds);
+
+ priority = tiling->TileAt(1, 0)->priority(ACTIVE_TREE);
+ EXPECT_GT(priority.distance_to_visible_in_pixels, 0.f);
+ EXPECT_FLOAT_EQ(std::numeric_limits<float>::infinity(),
+ priority.time_to_visible_in_seconds);
+
+ priority = tiling->TileAt(1, 1)->priority(ACTIVE_TREE);
+ EXPECT_GT(priority.distance_to_visible_in_pixels, 0.f);
+ EXPECT_FLOAT_EQ(std::numeric_limits<float>::infinity(),
+ priority.time_to_visible_in_seconds);
+
+ // Furthermore, in this scenario the top-left distance_to_visible
+ // will be smallest, followed by top-right. The bottom layers
+ // will of course be further than the top layers.
+ TilePriority top_left = tiling->TileAt(0, 0)->priority(ACTIVE_TREE);
+ TilePriority top_right = tiling->TileAt(1, 0)->priority(ACTIVE_TREE);
+ TilePriority bottom_left = tiling->TileAt(0, 1)->priority(ACTIVE_TREE);
+ TilePriority bottom_right = tiling->TileAt(1, 1)->priority(ACTIVE_TREE);
+ EXPECT_GT(top_right.distance_to_visible_in_pixels,
+ top_left.distance_to_visible_in_pixels);
+
+ EXPECT_GT(bottom_right.distance_to_visible_in_pixels,
+ top_right.distance_to_visible_in_pixels);
+
+ EXPECT_GT(bottom_left.distance_to_visible_in_pixels,
+ top_left.distance_to_visible_in_pixels);
+}
+
+TEST(UpdateTilePrioritiesTest, PerspectiveLayerClippedByW) {
+ // Perspective transforms need to take a different code path.
+ // This test checks tile priorities of a perspective layer.
+
+ FakePictureLayerTilingClient client;
+ scoped_ptr<TestablePictureLayerTiling> tiling;
+
+ gfx::Size device_viewport(800, 600);
+ gfx::Rect visible_layer_rect(0, 0, 0, 0); // offscreen.
+ gfx::Size last_layer_bounds(200, 200);
+ gfx::Size current_layer_bounds(200, 200);
+ float last_layer_contents_scale = 1.f;
+ float current_layer_contents_scale = 1.f;
+ gfx::Transform last_screen_transform;
+ gfx::Transform current_screen_transform;
+ double current_frame_time_in_seconds = 1.0;
+ size_t max_tiles_for_interest_area = 10000;
+
+ // A 3d perspective layer rotated about its Y axis, translated to almost
+ // fully offscreen. The left side will appear closer (i.e. larger in 2d) than
+ // the right side, so the top-left tile will technically be closer than the
+ // top-right.
+
+ // Translate layer to offscreen
+ current_screen_transform.Translate(400.0, 970.0);
+ // Apply perspective and rotation about the center of the layer
+ current_screen_transform.Translate(100.0, 100.0);
+ current_screen_transform.ApplyPerspectiveDepth(10.0);
+ current_screen_transform.RotateAboutYAxis(10.0);
+ current_screen_transform.Translate(-100.0, -100.0);
+ last_screen_transform = current_screen_transform;
+
+ // Sanity check that this transform does cause w<0 clipping for the left side
+ // of the layer, but not the right side.
+ bool clipped;
+ MathUtil::MapQuad(current_screen_transform,
+ gfx::QuadF(gfx::RectF(0, 0, 100, 200)),
+ &clipped);
+ ASSERT_TRUE(clipped);
+
+ MathUtil::MapQuad(current_screen_transform,
+ gfx::QuadF(gfx::RectF(100, 0, 100, 200)),
+ &clipped);
+ ASSERT_FALSE(clipped);
+
+ gfx::Rect viewport_in_layer_space = ViewportInLayerSpace(
+ current_screen_transform, device_viewport);
+
+ client.SetTileSize(gfx::Size(100, 100));
+ tiling = TestablePictureLayerTiling::Create(1.0f, // contents_scale
+ current_layer_bounds,
+ &client);
+
+ tiling->UpdateTilePriorities(
+ ACTIVE_TREE,
+ device_viewport,
+ viewport_in_layer_space,
+ visible_layer_rect,
+ last_layer_bounds,
+ current_layer_bounds,
+ last_layer_contents_scale,
+ current_layer_contents_scale,
+ last_screen_transform,
+ current_screen_transform,
+ current_frame_time_in_seconds,
+ max_tiles_for_interest_area);
+
+ ASSERT_TRUE(tiling->TileAt(0, 0));
+ ASSERT_TRUE(tiling->TileAt(0, 1));
+ ASSERT_TRUE(tiling->TileAt(1, 0));
+ ASSERT_TRUE(tiling->TileAt(1, 1));
+
+ // Left-side tiles will be clipped by the transform, so we have to assume
+ // they are visible just in case.
+ TilePriority priority = tiling->TileAt(0, 0)->priority(ACTIVE_TREE);
+ EXPECT_FLOAT_EQ(0.f, priority.distance_to_visible_in_pixels);
+ EXPECT_FLOAT_EQ(0.f, priority.time_to_visible_in_seconds);
+
+ priority = tiling->TileAt(0, 1)->priority(ACTIVE_TREE);
+ EXPECT_FLOAT_EQ(0.f, priority.distance_to_visible_in_pixels);
+ EXPECT_FLOAT_EQ(0.f, priority.time_to_visible_in_seconds);
+
+ // Right-side tiles will have a positive distance_to_visible
+ // and an infinite time_to_visible.
+ priority = tiling->TileAt(1, 0)->priority(ACTIVE_TREE);
+ EXPECT_GT(priority.distance_to_visible_in_pixels, 0.f);
+ EXPECT_FLOAT_EQ(std::numeric_limits<float>::infinity(),
+ priority.time_to_visible_in_seconds);
+
+ priority = tiling->TileAt(1, 1)->priority(ACTIVE_TREE);
+ EXPECT_GT(priority.distance_to_visible_in_pixels, 0.f);
+ EXPECT_FLOAT_EQ(std::numeric_limits<float>::infinity(),
+ priority.time_to_visible_in_seconds);
+}
+
+TEST(UpdateTilePrioritiesTest, BasicMotion) {
+ // Test that time_to_visible is computed correctly when
+ // there is some motion.
+
+ FakePictureLayerTilingClient client;
+ scoped_ptr<TestablePictureLayerTiling> tiling;
+
+ gfx::Size device_viewport(800, 600);
+ gfx::Rect visible_layer_rect(0, 0, 0, 0);
+ gfx::Size last_layer_bounds(200, 200);
+ gfx::Size current_layer_bounds(200, 200);
+ float last_layer_contents_scale = 1.f;
+ float current_layer_contents_scale = 1.f;
+ gfx::Transform last_screen_transform;
+ gfx::Transform current_screen_transform;
+ double last_frame_time_in_seconds = 1.0;
+ double current_frame_time_in_seconds = 2.0;
+ size_t max_tiles_for_interest_area = 10000;
+
+ // Offscreen layer is coming closer to viewport at 1000 pixels per second.
+ current_screen_transform.Translate(1800, 0);
+ last_screen_transform.Translate(2800, 0);
+
+ gfx::Rect viewport_in_layer_space = ViewportInLayerSpace(
+ current_screen_transform, device_viewport);
+
+ client.SetTileSize(gfx::Size(100, 100));
+ tiling = TestablePictureLayerTiling::Create(1.0f, // contents_scale
+ current_layer_bounds,
+ &client);
+
+ // previous ("last") frame
+ tiling->UpdateTilePriorities(
+ ACTIVE_TREE,
+ device_viewport,
+ viewport_in_layer_space,
+ visible_layer_rect,
+ last_layer_bounds,
+ last_layer_bounds,
+ last_layer_contents_scale,
+ last_layer_contents_scale,
+ last_screen_transform,
+ last_screen_transform,
+ last_frame_time_in_seconds,
+ max_tiles_for_interest_area);
+
+ // current frame
+ tiling->UpdateTilePriorities(
+ ACTIVE_TREE,
+ device_viewport,
+ viewport_in_layer_space,
+ visible_layer_rect,
+ last_layer_bounds,
+ current_layer_bounds,
+ last_layer_contents_scale,
+ current_layer_contents_scale,
+ last_screen_transform,
+ current_screen_transform,
+ current_frame_time_in_seconds,
+ max_tiles_for_interest_area);
+
+ ASSERT_TRUE(tiling->TileAt(0, 0));
+ ASSERT_TRUE(tiling->TileAt(0, 1));
+ ASSERT_TRUE(tiling->TileAt(1, 0));
+ ASSERT_TRUE(tiling->TileAt(1, 1));
+
+ TilePriority priority = tiling->TileAt(0, 0)->priority(ACTIVE_TREE);
+ EXPECT_GT(priority.distance_to_visible_in_pixels, 0.f);
+ EXPECT_FLOAT_EQ(1.f,
+ priority.time_to_visible_in_seconds);
+
+ priority = tiling->TileAt(0, 1)->priority(ACTIVE_TREE);
+ EXPECT_GT(priority.distance_to_visible_in_pixels, 0.f);
+ EXPECT_FLOAT_EQ(1.f,
+ priority.time_to_visible_in_seconds);
+
+ // time_to_visible for the right hand side layers needs an extra 0.099
+ // seconds because this tile is 99 pixels further away.
+ priority = tiling->TileAt(1, 0)->priority(ACTIVE_TREE);
+ EXPECT_GT(priority.distance_to_visible_in_pixels, 0.f);
+ EXPECT_FLOAT_EQ(1.099f,
+ priority.time_to_visible_in_seconds);
+
+ priority = tiling->TileAt(1, 1)->priority(ACTIVE_TREE);
+ EXPECT_GT(priority.distance_to_visible_in_pixels, 0.f);
+ EXPECT_FLOAT_EQ(1.099f,
+ priority.time_to_visible_in_seconds);
+}
+
+TEST(UpdateTilePrioritiesTest, RotationMotion) {
+ // Each tile of a layer may be affected differently by a transform; Check
+ // that UpdateTilePriorities correctly accounts for the transform between
+ // layer space and screen space.
+
+ FakePictureLayerTilingClient client;
+ scoped_ptr<TestablePictureLayerTiling> tiling;
+
+ gfx::Size device_viewport(800, 600);
+ gfx::Rect visible_layer_rect(0, 0, 0, 0); // offscren.
+ gfx::Size last_layer_bounds(200, 200);
+ gfx::Size current_layer_bounds(200, 200);
+ float last_layer_contents_scale = 1.f;
+ float current_layer_contents_scale = 1.f;
+ gfx::Transform last_screen_transform;
+ gfx::Transform current_screen_transform;
+ double last_frame_time_in_seconds = 1.0;
+ double current_frame_time_in_seconds = 2.0;
+ size_t max_tiles_for_interest_area = 10000;
+
+ // Rotation motion is set up specifically so that:
+ // - rotation occurs about the center of the layer
+ // - the top-left tile becomes visible on rotation
+ // - the top-right tile will have an infinite time_to_visible
+ // because it is rotating away from viewport.
+ // - bottom-left layer will have a positive non-zero time_to_visible
+ // because it is rotating toward the viewport.
+ current_screen_transform.Translate(400, 550);
+ current_screen_transform.RotateAboutZAxis(45);
+
+ last_screen_transform.Translate(400, 550);
+
+ gfx::Rect viewport_in_layer_space = ViewportInLayerSpace(
+ current_screen_transform, device_viewport);
+
+ client.SetTileSize(gfx::Size(100, 100));
+ tiling = TestablePictureLayerTiling::Create(1.0f, // contents_scale
+ current_layer_bounds,
+ &client);
+
+ // previous ("last") frame
+ tiling->UpdateTilePriorities(
+ ACTIVE_TREE,
+ device_viewport,
+ viewport_in_layer_space,
+ visible_layer_rect,
+ last_layer_bounds,
+ last_layer_bounds,
+ last_layer_contents_scale,
+ last_layer_contents_scale,
+ last_screen_transform,
+ last_screen_transform,
+ last_frame_time_in_seconds,
+ max_tiles_for_interest_area);
+
+ // current frame
+ tiling->UpdateTilePriorities(
+ ACTIVE_TREE,
+ device_viewport,
+ viewport_in_layer_space,
+ visible_layer_rect,
+ last_layer_bounds,
+ current_layer_bounds,
+ last_layer_contents_scale,
+ current_layer_contents_scale,
+ last_screen_transform,
+ current_screen_transform,
+ current_frame_time_in_seconds,
+ max_tiles_for_interest_area);
+
+ ASSERT_TRUE(tiling->TileAt(0, 0));
+ ASSERT_TRUE(tiling->TileAt(0, 1));
+ ASSERT_TRUE(tiling->TileAt(1, 0));
+ ASSERT_TRUE(tiling->TileAt(1, 1));
+
+ TilePriority priority = tiling->TileAt(0, 0)->priority(ACTIVE_TREE);
+ EXPECT_FLOAT_EQ(0.f, priority.distance_to_visible_in_pixels);
+ EXPECT_FLOAT_EQ(0.f, priority.time_to_visible_in_seconds);
+
+ priority = tiling->TileAt(0, 1)->priority(ACTIVE_TREE);
+ EXPECT_GT(priority.distance_to_visible_in_pixels, 0.f);
+ EXPECT_GT(priority.time_to_visible_in_seconds, 0.f);
+
+ priority = tiling->TileAt(1, 0)->priority(ACTIVE_TREE);
+ EXPECT_GT(priority.distance_to_visible_in_pixels, 0.f);
+ EXPECT_FLOAT_EQ(std::numeric_limits<float>::infinity(),
+ priority.time_to_visible_in_seconds);
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/resources/picture_pile.cc b/chromium/cc/resources/picture_pile.cc
new file mode 100644
index 00000000000..17283ddd263
--- /dev/null
+++ b/chromium/cc/resources/picture_pile.cc
@@ -0,0 +1,177 @@
+// 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 "cc/resources/picture_pile.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "cc/base/region.h"
+#include "cc/debug/benchmark_instrumentation.h"
+#include "cc/resources/picture_pile_impl.h"
+
+namespace {
+// Maximum number of pictures that can overlap before we collapse them into
+// a larger one.
+const size_t kMaxOverlapping = 2;
+// Maximum percentage area of the base picture another picture in the picture
+// list can be. If higher, we destroy the list and recreate from scratch.
+const float kResetThreshold = 0.7f;
+// Layout pixel buffer around the visible layer rect to record. Any base
+// picture that intersects the visible layer rect expanded by this distance
+// will be recorded.
+const int kPixelDistanceToRecord = 8000;
+} // namespace
+
+namespace cc {
+
+PicturePile::PicturePile() {
+}
+
+PicturePile::~PicturePile() {
+}
+
+bool PicturePile::Update(
+ ContentLayerClient* painter,
+ SkColor background_color,
+ bool contents_opaque,
+ const Region& invalidation,
+ gfx::Rect visible_layer_rect,
+ RenderingStatsInstrumentation* stats_instrumentation) {
+ background_color_ = background_color;
+ contents_opaque_ = contents_opaque;
+
+ gfx::Rect interest_rect = visible_layer_rect;
+ interest_rect.Inset(
+ -kPixelDistanceToRecord,
+ -kPixelDistanceToRecord,
+ -kPixelDistanceToRecord,
+ -kPixelDistanceToRecord);
+ bool modified_pile = false;
+ for (Region::Iterator i(invalidation); i.has_rect(); i.next()) {
+ gfx::Rect invalidation = i.rect();
+ // Split this inflated invalidation across tile boundaries and apply it
+ // to all tiles that it touches.
+ for (TilingData::Iterator iter(&tiling_, invalidation);
+ iter; ++iter) {
+ gfx::Rect tile =
+ tiling_.TileBoundsWithBorder(iter.index_x(), iter.index_y());
+ if (!tile.Intersects(interest_rect)) {
+ // This invalidation touches a tile outside the interest rect, so
+ // just remove the entire picture list.
+ picture_list_map_.erase(iter.index());
+ modified_pile = true;
+ continue;
+ }
+
+ gfx::Rect tile_invalidation = gfx::IntersectRects(invalidation, tile);
+ if (tile_invalidation.IsEmpty())
+ continue;
+ PictureListMap::iterator find = picture_list_map_.find(iter.index());
+ if (find == picture_list_map_.end())
+ continue;
+ PictureList& pic_list = find->second;
+ // Leave empty pic_lists empty in case there are multiple invalidations.
+ if (!pic_list.empty()) {
+ // Inflate all recordings from invalidations with a margin so that when
+ // scaled down to at least min_contents_scale, any final pixel touched
+ // by an invalidation can be fully rasterized by this picture.
+ tile_invalidation.Inset(-buffer_pixels(), -buffer_pixels());
+
+ DCHECK_GE(tile_invalidation.width(), buffer_pixels() * 2 + 1);
+ DCHECK_GE(tile_invalidation.height(), buffer_pixels() * 2 + 1);
+
+ InvalidateRect(pic_list, tile_invalidation);
+ modified_pile = true;
+ }
+ }
+ }
+
+ int repeat_count = std::max(1, slow_down_raster_scale_factor_for_debug_);
+
+ // Walk through all pictures in the rect of interest and record.
+ for (TilingData::Iterator iter(&tiling_, interest_rect); iter; ++iter) {
+ // Create a picture in this list if it doesn't exist.
+ PictureList& pic_list = picture_list_map_[iter.index()];
+ if (pic_list.empty()) {
+ // Inflate the base picture with a margin, similar to invalidations, so
+ // that when scaled down to at least min_contents_scale, the enclosed
+ // rect still includes content all the way to the edge of the layer.
+ gfx::Rect tile = tiling_.TileBounds(iter.index_x(), iter.index_y());
+ tile.Inset(
+ -buffer_pixels(),
+ -buffer_pixels(),
+ -buffer_pixels(),
+ -buffer_pixels());
+ scoped_refptr<Picture> base_picture = Picture::Create(tile);
+ pic_list.push_back(base_picture);
+ }
+
+ for (PictureList::iterator pic = pic_list.begin();
+ pic != pic_list.end(); ++pic) {
+ if (!(*pic)->HasRecording()) {
+ modified_pile = true;
+ TRACE_EVENT0(benchmark_instrumentation::kCategory,
+ benchmark_instrumentation::kRecordLoop);
+ for (int i = 0; i < repeat_count; i++)
+ (*pic)->Record(painter, tile_grid_info_, stats_instrumentation);
+ (*pic)->GatherPixelRefs(tile_grid_info_, stats_instrumentation);
+ (*pic)->CloneForDrawing(num_raster_threads_);
+ }
+ }
+ }
+
+ UpdateRecordedRegion();
+
+ return modified_pile;
+}
+
+class FullyContainedPredicate {
+ public:
+ explicit FullyContainedPredicate(gfx::Rect rect) : layer_rect_(rect) {}
+ bool operator()(const scoped_refptr<Picture>& picture) {
+ return picture->LayerRect().IsEmpty() ||
+ layer_rect_.Contains(picture->LayerRect());
+ }
+ gfx::Rect layer_rect_;
+};
+
+void PicturePile::InvalidateRect(
+ PictureList& picture_list,
+ gfx::Rect invalidation) {
+ DCHECK(!picture_list.empty());
+ DCHECK(!invalidation.IsEmpty());
+
+ std::vector<PictureList::iterator> overlaps;
+ for (PictureList::iterator i = picture_list.begin();
+ i != picture_list.end(); ++i) {
+ if ((*i)->LayerRect().Contains(invalidation) && !(*i)->HasRecording())
+ return;
+ if ((*i)->LayerRect().Intersects(invalidation) && i != picture_list.begin())
+ overlaps.push_back(i);
+ }
+
+ gfx::Rect picture_rect = invalidation;
+ if (overlaps.size() >= kMaxOverlapping) {
+ for (size_t j = 0; j < overlaps.size(); j++)
+ picture_rect.Union((*overlaps[j])->LayerRect());
+ }
+
+ Picture* base_picture = picture_list.front().get();
+ int max_pixels = kResetThreshold * base_picture->LayerRect().size().GetArea();
+ if (picture_rect.size().GetArea() > max_pixels) {
+ // This picture list will be entirely recreated, so clear it.
+ picture_list.clear();
+ return;
+ }
+
+ FullyContainedPredicate pred(picture_rect);
+ picture_list.erase(std::remove_if(picture_list.begin(),
+ picture_list.end(),
+ pred),
+ picture_list.end());
+ picture_list.push_back(Picture::Create(picture_rect));
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/picture_pile.h b/chromium/cc/resources/picture_pile.h
new file mode 100644
index 00000000000..7830a9e62c4
--- /dev/null
+++ b/chromium/cc/resources/picture_pile.h
@@ -0,0 +1,60 @@
+// 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.
+
+#ifndef CC_RESOURCES_PICTURE_PILE_H_
+#define CC_RESOURCES_PICTURE_PILE_H_
+
+#include "cc/resources/picture_pile_base.h"
+#include "ui/gfx/rect.h"
+
+namespace cc {
+class PicturePileImpl;
+class Region;
+class RenderingStatsInstrumentation;
+
+class CC_EXPORT PicturePile : public PicturePileBase {
+ public:
+ PicturePile();
+
+ // Re-record parts of the picture that are invalid.
+ // Invalidations are in layer space.
+ // Return true iff the pile was modified.
+ bool Update(
+ ContentLayerClient* painter,
+ SkColor background_color,
+ bool contents_opaque,
+ const Region& invalidation,
+ gfx::Rect visible_layer_rect,
+ RenderingStatsInstrumentation* stats_instrumentation);
+
+ void set_num_raster_threads(int num_raster_threads) {
+ num_raster_threads_ = num_raster_threads;
+ }
+
+ void set_slow_down_raster_scale_factor(int factor) {
+ slow_down_raster_scale_factor_for_debug_ = factor;
+ }
+
+ void set_show_debug_picture_borders(bool show) {
+ show_debug_picture_borders_ = show;
+ }
+
+ protected:
+ virtual ~PicturePile();
+
+ private:
+ friend class PicturePileImpl;
+
+ // Add an invalidation to this picture list. If the list needs to be
+ // entirely recreated, leave it empty. Do not call this on an empty list.
+ void InvalidateRect(
+ PictureList& picture_list,
+ gfx::Rect invalidation);
+
+ DISALLOW_COPY_AND_ASSIGN(PicturePile);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_PICTURE_PILE_H_
diff --git a/chromium/cc/resources/picture_pile_base.cc b/chromium/cc/resources/picture_pile_base.cc
new file mode 100644
index 00000000000..d982f9fc9b8
--- /dev/null
+++ b/chromium/cc/resources/picture_pile_base.cc
@@ -0,0 +1,203 @@
+// Copyright 2013 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 "cc/resources/picture_pile_base.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/values.h"
+#include "cc/base/math_util.h"
+#include "cc/debug/traced_value.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/gfx/rect_conversions.h"
+
+namespace {
+// Dimensions of the tiles in this picture pile as well as the dimensions of
+// the base picture in each tile.
+const int kBasePictureSize = 3000;
+const int kTileGridBorderPixels = 1;
+}
+
+namespace cc {
+
+PicturePileBase::PicturePileBase()
+ : min_contents_scale_(0),
+ background_color_(SkColorSetARGBInline(0, 0, 0, 0)),
+ contents_opaque_(false),
+ slow_down_raster_scale_factor_for_debug_(0),
+ show_debug_picture_borders_(false),
+ num_raster_threads_(0) {
+ tiling_.SetMaxTextureSize(gfx::Size(kBasePictureSize, kBasePictureSize));
+ tile_grid_info_.fTileInterval.setEmpty();
+ tile_grid_info_.fMargin.setEmpty();
+ tile_grid_info_.fOffset.setZero();
+}
+
+PicturePileBase::PicturePileBase(const PicturePileBase* other)
+ : picture_list_map_(other->picture_list_map_),
+ tiling_(other->tiling_),
+ recorded_region_(other->recorded_region_),
+ min_contents_scale_(other->min_contents_scale_),
+ tile_grid_info_(other->tile_grid_info_),
+ background_color_(other->background_color_),
+ contents_opaque_(other->contents_opaque_),
+ slow_down_raster_scale_factor_for_debug_(
+ other->slow_down_raster_scale_factor_for_debug_),
+ show_debug_picture_borders_(other->show_debug_picture_borders_),
+ num_raster_threads_(other->num_raster_threads_) {
+}
+
+PicturePileBase::PicturePileBase(
+ const PicturePileBase* other, unsigned thread_index)
+ : tiling_(other->tiling_),
+ recorded_region_(other->recorded_region_),
+ min_contents_scale_(other->min_contents_scale_),
+ tile_grid_info_(other->tile_grid_info_),
+ background_color_(other->background_color_),
+ contents_opaque_(other->contents_opaque_),
+ slow_down_raster_scale_factor_for_debug_(
+ other->slow_down_raster_scale_factor_for_debug_),
+ show_debug_picture_borders_(other->show_debug_picture_borders_),
+ num_raster_threads_(other->num_raster_threads_) {
+ const PictureListMap& other_pic_list_map = other->picture_list_map_;
+ for (PictureListMap::const_iterator map_iter = other_pic_list_map.begin();
+ map_iter != other_pic_list_map.end(); ++map_iter) {
+ PictureList& pic_list = picture_list_map_[map_iter->first];
+ const PictureList& other_pic_list = map_iter->second;
+ for (PictureList::const_iterator pic_iter = other_pic_list.begin();
+ pic_iter != other_pic_list.end(); ++pic_iter) {
+ pic_list.push_back(
+ (*pic_iter)->GetCloneForDrawingOnThread(thread_index));
+ }
+ }
+}
+
+PicturePileBase::~PicturePileBase() {
+}
+
+void PicturePileBase::Resize(gfx::Size new_size) {
+ if (size() == new_size)
+ return;
+
+ gfx::Size old_size = size();
+ tiling_.SetTotalSize(new_size);
+
+ // Find all tiles that contain any pixels outside the new size.
+ std::vector<PictureListMapKey> to_erase;
+ int min_toss_x = tiling_.FirstBorderTileXIndexFromSrcCoord(
+ std::min(old_size.width(), new_size.width()));
+ int min_toss_y = tiling_.FirstBorderTileYIndexFromSrcCoord(
+ std::min(old_size.height(), new_size.height()));
+ for (PictureListMap::iterator iter = picture_list_map_.begin();
+ iter != picture_list_map_.end(); ++iter) {
+ if (iter->first.first < min_toss_x && iter->first.second < min_toss_y)
+ continue;
+ to_erase.push_back(iter->first);
+ }
+
+ for (size_t i = 0; i < to_erase.size(); ++i)
+ picture_list_map_.erase(to_erase[i]);
+}
+
+void PicturePileBase::SetMinContentsScale(float min_contents_scale) {
+ DCHECK(min_contents_scale);
+ if (min_contents_scale_ == min_contents_scale)
+ return;
+
+ // Picture contents are played back scaled. When the final contents scale is
+ // less than 1 (i.e. low res), then multiple recorded pixels will be used
+ // to raster one final pixel. To avoid splitting a final pixel across
+ // pictures (which would result in incorrect rasterization due to blending), a
+ // buffer margin is added so that any picture can be snapped to integral
+ // final pixels.
+ //
+ // For example, if a 1/4 contents scale is used, then that would be 3 buffer
+ // pixels, since that's the minimum number of pixels to add so that resulting
+ // content can be snapped to a four pixel aligned grid.
+ int buffer_pixels = static_cast<int>(ceil(1 / min_contents_scale) - 1);
+ buffer_pixels = std::max(0, buffer_pixels);
+ SetBufferPixels(buffer_pixels);
+ min_contents_scale_ = min_contents_scale;
+}
+
+void PicturePileBase::SetTileGridSize(gfx::Size tile_grid_size) {
+ tile_grid_info_.fTileInterval.set(
+ tile_grid_size.width() - 2 * kTileGridBorderPixels,
+ tile_grid_size.height() - 2 * kTileGridBorderPixels);
+ DCHECK_GT(tile_grid_info_.fTileInterval.width(), 0);
+ DCHECK_GT(tile_grid_info_.fTileInterval.height(), 0);
+ tile_grid_info_.fMargin.set(kTileGridBorderPixels,
+ kTileGridBorderPixels);
+ // Offset the tile grid coordinate space to take into account the fact
+ // that the top-most and left-most tiles do not have top and left borders
+ // respectively.
+ tile_grid_info_.fOffset.set(-kTileGridBorderPixels,
+ -kTileGridBorderPixels);
+}
+
+void PicturePileBase::SetBufferPixels(int new_buffer_pixels) {
+ if (new_buffer_pixels == buffer_pixels())
+ return;
+
+ Clear();
+ tiling_.SetBorderTexels(new_buffer_pixels);
+}
+
+void PicturePileBase::Clear() {
+ picture_list_map_.clear();
+}
+
+void PicturePileBase::UpdateRecordedRegion() {
+ recorded_region_.Clear();
+ for (int x = 0; x < num_tiles_x(); ++x) {
+ for (int y = 0; y < num_tiles_y(); ++y) {
+ if (!HasRecordingAt(x, y))
+ continue;
+ recorded_region_.Union(tile_bounds(x, y));
+ }
+ }
+}
+
+bool PicturePileBase::HasRecordingAt(int x, int y) {
+ PictureListMap::iterator found =
+ picture_list_map_.find(PictureListMapKey(x, y));
+ if (found == picture_list_map_.end())
+ return false;
+ DCHECK(!found->second.empty());
+ return true;
+}
+
+bool PicturePileBase::CanRaster(float contents_scale, gfx::Rect content_rect) {
+ if (tiling_.total_size().IsEmpty())
+ return false;
+ gfx::Rect layer_rect = gfx::ScaleToEnclosingRect(
+ content_rect, 1.f / contents_scale);
+ layer_rect.Intersect(gfx::Rect(tiling_.total_size()));
+ return recorded_region_.Contains(layer_rect);
+}
+
+scoped_ptr<base::Value> PicturePileBase::AsValue() const {
+ scoped_ptr<base::ListValue> pictures(new base::ListValue());
+ gfx::Rect layer_rect(tiling_.total_size());
+ for (TilingData::Iterator tile_iter(&tiling_, layer_rect);
+ tile_iter; ++tile_iter) {
+ PictureListMap::const_iterator map_iter =
+ picture_list_map_.find(tile_iter.index());
+ if (map_iter == picture_list_map_.end())
+ continue;
+ const PictureList& pic_list= map_iter->second;
+ if (pic_list.empty())
+ continue;
+ for (PictureList::const_reverse_iterator i = pic_list.rbegin();
+ i != pic_list.rend(); ++i) {
+ Picture* picture = (*i).get();
+ pictures->Append(TracedValue::CreateIDRef(picture).release());
+ }
+ }
+ return pictures.PassAs<base::Value>();
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/picture_pile_base.h b/chromium/cc/resources/picture_pile_base.h
new file mode 100644
index 00000000000..fdb593fa81a
--- /dev/null
+++ b/chromium/cc/resources/picture_pile_base.h
@@ -0,0 +1,82 @@
+// Copyright 2013 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.
+
+#ifndef CC_RESOURCES_PICTURE_PILE_BASE_H_
+#define CC_RESOURCES_PICTURE_PILE_BASE_H_
+
+#include <list>
+#include <utility>
+
+#include "base/containers/hash_tables.h"
+#include "base/memory/ref_counted.h"
+#include "cc/base/cc_export.h"
+#include "cc/base/region.h"
+#include "cc/base/tiling_data.h"
+#include "cc/resources/picture.h"
+#include "ui/gfx/size.h"
+
+namespace base {
+class Value;
+}
+
+namespace cc {
+
+class CC_EXPORT PicturePileBase : public base::RefCounted<PicturePileBase> {
+ public:
+ PicturePileBase();
+ explicit PicturePileBase(const PicturePileBase* other);
+ PicturePileBase(const PicturePileBase* other, unsigned thread_index);
+
+ void Resize(gfx::Size size);
+ gfx::Size size() const { return tiling_.total_size(); }
+ void SetMinContentsScale(float min_contents_scale);
+
+ void UpdateRecordedRegion();
+ const Region& recorded_region() const { return recorded_region_; }
+
+ int num_tiles_x() const { return tiling_.num_tiles_x(); }
+ int num_tiles_y() const { return tiling_.num_tiles_y(); }
+ gfx::Rect tile_bounds(int x, int y) const { return tiling_.TileBounds(x, y); }
+ bool HasRecordingAt(int x, int y);
+ bool CanRaster(float contents_scale, gfx::Rect content_rect);
+
+ void SetTileGridSize(gfx::Size tile_grid_size);
+ TilingData& tiling() { return tiling_; }
+
+ scoped_ptr<base::Value> AsValue() const;
+
+ protected:
+ virtual ~PicturePileBase();
+
+ int num_raster_threads() { return num_raster_threads_; }
+ int buffer_pixels() const { return tiling_.border_texels(); }
+ void Clear();
+
+ typedef std::pair<int, int> PictureListMapKey;
+ typedef std::list<scoped_refptr<Picture> > PictureList;
+ typedef base::hash_map<PictureListMapKey, PictureList> PictureListMap;
+
+ // A picture pile is a tiled set of picture lists. The picture list map
+ // is a map of tile indices to picture lists.
+ PictureListMap picture_list_map_;
+ TilingData tiling_;
+ Region recorded_region_;
+ float min_contents_scale_;
+ SkTileGridPicture::TileGridInfo tile_grid_info_;
+ SkColor background_color_;
+ bool contents_opaque_;
+ int slow_down_raster_scale_factor_for_debug_;
+ bool show_debug_picture_borders_;
+ int num_raster_threads_;
+
+ private:
+ void SetBufferPixels(int buffer_pixels);
+
+ friend class base::RefCounted<PicturePileBase>;
+ DISALLOW_COPY_AND_ASSIGN(PicturePileBase);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_PICTURE_PILE_BASE_H_
diff --git a/chromium/cc/resources/picture_pile_impl.cc b/chromium/cc/resources/picture_pile_impl.cc
new file mode 100644
index 00000000000..42f3dabd4cf
--- /dev/null
+++ b/chromium/cc/resources/picture_pile_impl.cc
@@ -0,0 +1,389 @@
+// 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 <algorithm>
+#include <limits>
+
+#include "base/debug/trace_event.h"
+#include "cc/base/region.h"
+#include "cc/debug/benchmark_instrumentation.h"
+#include "cc/debug/debug_colors.h"
+#include "cc/resources/picture_pile_impl.h"
+#include "skia/ext/analysis_canvas.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkSize.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/size_conversions.h"
+#include "ui/gfx/skia_util.h"
+
+namespace cc {
+
+PicturePileImpl::ClonesForDrawing::ClonesForDrawing(
+ const PicturePileImpl* pile, int num_threads) {
+ for (int i = 0; i < num_threads; i++) {
+ scoped_refptr<PicturePileImpl> clone =
+ PicturePileImpl::CreateCloneForDrawing(pile, i);
+ clones_.push_back(clone);
+ }
+}
+
+PicturePileImpl::ClonesForDrawing::~ClonesForDrawing() {
+}
+
+scoped_refptr<PicturePileImpl> PicturePileImpl::Create() {
+ return make_scoped_refptr(new PicturePileImpl);
+}
+
+scoped_refptr<PicturePileImpl> PicturePileImpl::CreateFromOther(
+ const PicturePileBase* other) {
+ return make_scoped_refptr(new PicturePileImpl(other));
+}
+
+scoped_refptr<PicturePileImpl> PicturePileImpl::CreateCloneForDrawing(
+ const PicturePileImpl* other, unsigned thread_index) {
+ return make_scoped_refptr(new PicturePileImpl(other, thread_index));
+}
+
+PicturePileImpl::PicturePileImpl()
+ : clones_for_drawing_(ClonesForDrawing(this, 0)) {
+}
+
+PicturePileImpl::PicturePileImpl(const PicturePileBase* other)
+ : PicturePileBase(other),
+ clones_for_drawing_(ClonesForDrawing(this, num_raster_threads())) {
+}
+
+PicturePileImpl::PicturePileImpl(
+ const PicturePileImpl* other, unsigned thread_index)
+ : PicturePileBase(other, thread_index),
+ clones_for_drawing_(ClonesForDrawing(this, 0)) {
+}
+
+PicturePileImpl::~PicturePileImpl() {
+}
+
+PicturePileImpl* PicturePileImpl::GetCloneForDrawingOnThread(
+ unsigned thread_index) const {
+ CHECK_GT(clones_for_drawing_.clones_.size(), thread_index);
+ return clones_for_drawing_.clones_[thread_index].get();
+}
+
+void PicturePileImpl::RasterDirect(
+ SkCanvas* canvas,
+ gfx::Rect canvas_rect,
+ float contents_scale,
+ RasterStats* raster_stats) {
+ RasterCommon(canvas, NULL, canvas_rect, contents_scale, raster_stats);
+}
+
+void PicturePileImpl::RasterForAnalysis(
+ skia::AnalysisCanvas* canvas,
+ gfx::Rect canvas_rect,
+ float contents_scale) {
+ RasterCommon(canvas, canvas, canvas_rect, contents_scale, NULL);
+}
+
+void PicturePileImpl::RasterToBitmap(
+ SkCanvas* canvas,
+ gfx::Rect canvas_rect,
+ float contents_scale,
+ RasterStats* raster_stats) {
+#ifndef NDEBUG
+ // Any non-painted areas will be left in this color.
+ canvas->clear(DebugColors::NonPaintedFillColor());
+#endif // NDEBUG
+
+ // If this picture has opaque contents, it is guaranteeing that it will
+ // draw an opaque rect the size of the layer. If it is not, then we must
+ // clear this canvas ourselves.
+ if (!contents_opaque_) {
+ // Clearing is about ~4x faster than drawing a rect even if the content
+ // isn't covering a majority of the canvas.
+ canvas->clear(SK_ColorTRANSPARENT);
+ } else {
+ // Even if it is opaque, on any rasterizations that touch the edge of the
+ // layer, we also need to raster the background color underneath the last
+ // texel (since the recording won't cover it) and outside the last texel
+ // (due to linear filtering when using this texture).
+ gfx::SizeF total_content_size = gfx::ScaleSize(tiling_.total_size(),
+ contents_scale);
+ gfx::Rect content_rect(gfx::ToCeiledSize(total_content_size));
+ gfx::Rect deflated_content_rect = content_rect;
+ content_rect.Intersect(canvas_rect);
+
+ // The final texel of content may only be partially covered by a
+ // rasterization; this rect represents the content rect that is fully
+ // covered by content.
+ deflated_content_rect.Inset(0, 0, 1, 1);
+ deflated_content_rect.Intersect(canvas_rect);
+ if (!deflated_content_rect.Contains(canvas_rect)) {
+ // Drawing at most 2 x 2 x (canvas width + canvas height) texels is 2-3X
+ // faster than clearing, so special case this.
+ canvas->save();
+ gfx::Rect inflated_content_rect = content_rect;
+ inflated_content_rect.Inset(0, 0, -1, -1);
+ canvas->clipRect(gfx::RectToSkRect(inflated_content_rect),
+ SkRegion::kReplace_Op);
+ canvas->clipRect(gfx::RectToSkRect(deflated_content_rect),
+ SkRegion::kDifference_Op);
+ canvas->drawColor(background_color_, SkXfermode::kSrc_Mode);
+ canvas->restore();
+ }
+ }
+
+ RasterCommon(canvas, NULL, canvas_rect, contents_scale, raster_stats);
+}
+
+void PicturePileImpl::RasterCommon(
+ SkCanvas* canvas,
+ SkDrawPictureCallback* callback,
+ gfx::Rect canvas_rect,
+ float contents_scale,
+ RasterStats* raster_stats) {
+ DCHECK(contents_scale >= min_contents_scale_);
+
+ canvas->translate(-canvas_rect.x(), -canvas_rect.y());
+
+ gfx::SizeF total_content_size = gfx::ScaleSize(tiling_.total_size(),
+ contents_scale);
+ gfx::Rect total_content_rect(gfx::ToCeiledSize(total_content_size));
+ gfx::Rect content_rect = total_content_rect;
+ content_rect.Intersect(canvas_rect);
+
+ // Rasterize the collection of relevant picture piles.
+ gfx::Rect layer_rect = gfx::ScaleToEnclosingRect(
+ content_rect, 1.f / contents_scale);
+
+ canvas->clipRect(gfx::RectToSkRect(content_rect),
+ SkRegion::kIntersect_Op);
+ Region unclipped(content_rect);
+
+ if (raster_stats) {
+ raster_stats->total_pixels_rasterized = 0;
+ raster_stats->total_rasterize_time = base::TimeDelta::FromSeconds(0);
+ raster_stats->best_rasterize_time = base::TimeDelta::FromSeconds(0);
+ }
+
+ for (TilingData::Iterator tile_iter(&tiling_, layer_rect);
+ tile_iter; ++tile_iter) {
+ PictureListMap::iterator map_iter =
+ picture_list_map_.find(tile_iter.index());
+ if (map_iter == picture_list_map_.end())
+ continue;
+ PictureList& pic_list= map_iter->second;
+ if (pic_list.empty())
+ continue;
+
+ // Raster through the picture list top down, using clips to make sure that
+ // pictures on top are not overdrawn by pictures on the bottom.
+ for (PictureList::reverse_iterator i = pic_list.rbegin();
+ i != pic_list.rend(); ++i) {
+ // This is intentionally *enclosed* rect, so that the clip is aligned on
+ // integral post-scale content pixels and does not extend past the edges
+ // of the picture's layer rect. The min_contents_scale enforces that
+ // enough buffer pixels have been added such that the enclosed rect
+ // encompasses all invalidated pixels at any larger scale level.
+ gfx::Rect content_clip = gfx::ScaleToEnclosedRect(
+ (*i)->LayerRect(), contents_scale);
+ DCHECK(!content_clip.IsEmpty()) <<
+ "Layer rect: " << (*i)->LayerRect().ToString() <<
+ "Contents scale: " << contents_scale;
+ if (!unclipped.Intersects(content_clip))
+ continue;
+
+ base::TimeDelta total_duration =
+ base::TimeDelta::FromInternalValue(0);
+ base::TimeDelta best_duration =
+ base::TimeDelta::FromInternalValue(std::numeric_limits<int64>::max());
+ int repeat_count = std::max(1, slow_down_raster_scale_factor_for_debug_);
+
+ TRACE_EVENT0(benchmark_instrumentation::kCategory,
+ benchmark_instrumentation::kRasterLoop);
+ for (int j = 0; j < repeat_count; ++j) {
+ base::TimeTicks start_time;
+ if (raster_stats)
+ start_time = base::TimeTicks::HighResNow();
+
+ (*i)->Raster(canvas, callback, content_clip, contents_scale);
+
+ if (raster_stats) {
+ base::TimeDelta duration = base::TimeTicks::HighResNow() - start_time;
+ total_duration += duration;
+ best_duration = std::min(best_duration, duration);
+ }
+ }
+
+ if (raster_stats) {
+ gfx::Rect raster_rect = canvas_rect;
+ raster_rect.Intersect(content_clip);
+ raster_stats->total_pixels_rasterized +=
+ repeat_count * raster_rect.width() * raster_rect.height();
+ raster_stats->total_rasterize_time += total_duration;
+ raster_stats->best_rasterize_time += best_duration;
+ }
+
+ if (show_debug_picture_borders_) {
+ gfx::Rect border = content_clip;
+ border.Inset(0, 0, 1, 1);
+
+ SkPaint picture_border_paint;
+ picture_border_paint.setColor(DebugColors::PictureBorderColor());
+ canvas->drawLine(border.x(), border.y(), border.right(), border.y(),
+ picture_border_paint);
+ canvas->drawLine(border.right(), border.y(), border.right(),
+ border.bottom(), picture_border_paint);
+ canvas->drawLine(border.right(), border.bottom(), border.x(),
+ border.bottom(), picture_border_paint);
+ canvas->drawLine(border.x(), border.bottom(), border.x(), border.y(),
+ picture_border_paint);
+ }
+
+ // Don't allow pictures underneath to draw where this picture did.
+ canvas->clipRect(
+ gfx::RectToSkRect(content_clip),
+ SkRegion::kDifference_Op);
+ unclipped.Subtract(content_clip);
+ }
+ }
+
+#ifndef NDEBUG
+ // Fill the remaining clip with debug color. This allows us to
+ // distinguish between non painted areas and problems with missing
+ // pictures.
+ SkPaint paint;
+ paint.setColor(DebugColors::MissingPictureFillColor());
+ paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+ canvas->drawPaint(paint);
+#endif // NDEBUG
+
+ // We should always paint some part of |content_rect|.
+ DCHECK(!unclipped.Contains(content_rect));
+}
+
+skia::RefPtr<SkPicture> PicturePileImpl::GetFlattenedPicture() {
+ TRACE_EVENT0("cc", "PicturePileImpl::GetFlattenedPicture");
+
+ gfx::Rect layer_rect(tiling_.total_size());
+ skia::RefPtr<SkPicture> picture = skia::AdoptRef(new SkPicture);
+ if (layer_rect.IsEmpty())
+ return picture;
+
+ SkCanvas* canvas = picture->beginRecording(
+ layer_rect.width(),
+ layer_rect.height(),
+ SkPicture::kUsePathBoundsForClip_RecordingFlag);
+
+ RasterToBitmap(canvas, layer_rect, 1.0, NULL);
+ picture->endRecording();
+
+ return picture;
+}
+
+void PicturePileImpl::AnalyzeInRect(gfx::Rect content_rect,
+ float contents_scale,
+ PicturePileImpl::Analysis* analysis) {
+ DCHECK(analysis);
+ TRACE_EVENT0("cc", "PicturePileImpl::AnalyzeInRect");
+
+ gfx::Rect layer_rect = gfx::ScaleToEnclosingRect(
+ content_rect, 1.0f / contents_scale);
+
+ layer_rect.Intersect(gfx::Rect(tiling_.total_size()));
+
+ SkBitmap empty_bitmap;
+ empty_bitmap.setConfig(SkBitmap::kNo_Config,
+ layer_rect.width(),
+ layer_rect.height());
+ skia::AnalysisDevice device(empty_bitmap);
+ skia::AnalysisCanvas canvas(&device);
+
+ RasterForAnalysis(&canvas, layer_rect, 1.0f);
+
+ analysis->is_solid_color = canvas.GetColorIfSolid(&analysis->solid_color);
+ analysis->has_text = canvas.HasText();
+}
+
+PicturePileImpl::Analysis::Analysis()
+ : is_solid_color(false),
+ has_text(false) {
+}
+
+PicturePileImpl::Analysis::~Analysis() {
+}
+
+PicturePileImpl::PixelRefIterator::PixelRefIterator(
+ gfx::Rect content_rect,
+ float contents_scale,
+ const PicturePileImpl* picture_pile)
+ : picture_pile_(picture_pile),
+ layer_rect_(gfx::ScaleToEnclosingRect(
+ content_rect, 1.f / contents_scale)),
+ tile_iterator_(&picture_pile_->tiling_, layer_rect_),
+ picture_list_(NULL) {
+ // Early out if there isn't a single tile.
+ if (!tile_iterator_)
+ return;
+
+ if (AdvanceToTileWithPictures())
+ AdvanceToPictureWithPixelRefs();
+}
+
+PicturePileImpl::PixelRefIterator::~PixelRefIterator() {
+}
+
+PicturePileImpl::PixelRefIterator&
+ PicturePileImpl::PixelRefIterator::operator++() {
+ ++pixel_ref_iterator_;
+ if (pixel_ref_iterator_)
+ return *this;
+
+ ++picture_list_iterator_;
+ AdvanceToPictureWithPixelRefs();
+ return *this;
+}
+
+bool PicturePileImpl::PixelRefIterator::AdvanceToTileWithPictures() {
+ for (; tile_iterator_; ++tile_iterator_) {
+ PictureListMap::const_iterator map_iterator =
+ picture_pile_->picture_list_map_.find(tile_iterator_.index());
+ if (map_iterator != picture_pile_->picture_list_map_.end()) {
+ picture_list_ = &map_iterator->second;
+ picture_list_iterator_ = picture_list_->begin();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void PicturePileImpl::PixelRefIterator::AdvanceToPictureWithPixelRefs() {
+ DCHECK(tile_iterator_);
+ do {
+ for (;
+ picture_list_iterator_ != picture_list_->end();
+ ++picture_list_iterator_) {
+ pixel_ref_iterator_ =
+ Picture::PixelRefIterator(layer_rect_, picture_list_iterator_->get());
+ if (pixel_ref_iterator_)
+ return;
+ }
+ ++tile_iterator_;
+ } while (AdvanceToTileWithPictures());
+}
+
+void PicturePileImpl::DidBeginTracing() {
+ gfx::Rect layer_rect(tiling_.total_size());
+ for (PictureListMap::iterator pli = picture_list_map_.begin();
+ pli != picture_list_map_.end();
+ pli++) {
+ PictureList& picture_list = (*pli).second;
+ for (PictureList::iterator picture = picture_list.begin();
+ picture != picture_list.end();
+ picture++) {
+ (*picture)->EmitTraceSnapshot();
+ }
+ }
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/picture_pile_impl.h b/chromium/cc/resources/picture_pile_impl.h
new file mode 100644
index 00000000000..c6c3ab3fbe4
--- /dev/null
+++ b/chromium/cc/resources/picture_pile_impl.h
@@ -0,0 +1,153 @@
+// 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.
+
+#ifndef CC_RESOURCES_PICTURE_PILE_IMPL_H_
+#define CC_RESOURCES_PICTURE_PILE_IMPL_H_
+
+#include <list>
+#include <map>
+#include <vector>
+
+#include "base/time/time.h"
+#include "cc/base/cc_export.h"
+#include "cc/resources/picture_pile_base.h"
+#include "skia/ext/analysis_canvas.h"
+#include "skia/ext/refptr.h"
+#include "third_party/skia/include/core/SkPicture.h"
+
+namespace cc {
+
+class CC_EXPORT PicturePileImpl : public PicturePileBase {
+ public:
+ static scoped_refptr<PicturePileImpl> Create();
+ static scoped_refptr<PicturePileImpl> CreateFromOther(
+ const PicturePileBase* other);
+
+ // Get paint-safe version of this picture for a specific thread.
+ PicturePileImpl* GetCloneForDrawingOnThread(unsigned thread_index) const;
+
+ struct CC_EXPORT RasterStats {
+ // Minimum rasterize time from N runs
+ // N=max(1,slow-down-raster-scale-factor)
+ base::TimeDelta best_rasterize_time;
+ // Total rasterize time for all N runs
+ base::TimeDelta total_rasterize_time;
+ // Total number of pixels rasterize in all N runs
+ int64 total_pixels_rasterized;
+ };
+
+ // Raster a subrect of this PicturePileImpl into the given canvas.
+ // It's only safe to call paint on a cloned version. It is assumed
+ // that contents_scale has already been applied to this canvas.
+ // Writes the total number of pixels rasterized and the time spent
+ // rasterizing to the stats if the respective pointer is not
+ // NULL. When slow-down-raster-scale-factor is set to a value
+ // greater than 1, the reported rasterize time is the minimum
+ // measured value over all runs.
+ void RasterDirect(
+ SkCanvas* canvas,
+ gfx::Rect canvas_rect,
+ float contents_scale,
+ RasterStats* raster_stats);
+
+ // Similar to the above RasterDirect method, but this is a convenience method
+ // for when it is known that the raster is going to an intermediate bitmap
+ // that itself will then be blended and thus that a canvas clear is required.
+ void RasterToBitmap(
+ SkCanvas* canvas,
+ gfx::Rect canvas_rect,
+ float contents_scale,
+ RasterStats* raster_stats);
+
+ // Called when analyzing a tile. We can use AnalysisCanvas as
+ // SkDrawPictureCallback, which allows us to early out from analysis.
+ void RasterForAnalysis(
+ skia::AnalysisCanvas* canvas,
+ gfx::Rect canvas_rect,
+ float contents_scale);
+
+
+ skia::RefPtr<SkPicture> GetFlattenedPicture();
+
+ struct CC_EXPORT Analysis {
+ Analysis();
+ ~Analysis();
+
+ bool is_solid_color;
+ bool has_text;
+ SkColor solid_color;
+ };
+
+ void AnalyzeInRect(gfx::Rect content_rect,
+ float contents_scale,
+ Analysis* analysis);
+
+ class CC_EXPORT PixelRefIterator {
+ public:
+ PixelRefIterator(gfx::Rect content_rect,
+ float contents_scale,
+ const PicturePileImpl* picture_pile);
+ ~PixelRefIterator();
+
+ skia::LazyPixelRef* operator->() const { return *pixel_ref_iterator_; }
+ skia::LazyPixelRef* operator*() const { return *pixel_ref_iterator_; }
+ PixelRefIterator& operator++();
+ operator bool() const { return pixel_ref_iterator_; }
+
+ private:
+ bool AdvanceToTileWithPictures();
+ void AdvanceToPictureWithPixelRefs();
+
+ const PicturePileImpl* picture_pile_;
+ gfx::Rect layer_rect_;
+ TilingData::Iterator tile_iterator_;
+ Picture::PixelRefIterator pixel_ref_iterator_;
+ const PictureList* picture_list_;
+ PictureList::const_iterator picture_list_iterator_;
+ };
+
+ void DidBeginTracing();
+
+ protected:
+ friend class PicturePile;
+ friend class PixelRefIterator;
+
+ PicturePileImpl();
+ explicit PicturePileImpl(const PicturePileBase* other);
+ virtual ~PicturePileImpl();
+
+ private:
+ class ClonesForDrawing {
+ public:
+ ClonesForDrawing(const PicturePileImpl* pile, int num_threads);
+ ~ClonesForDrawing();
+
+ typedef std::vector<scoped_refptr<PicturePileImpl> > PicturePileVector;
+ PicturePileVector clones_;
+ };
+
+ static scoped_refptr<PicturePileImpl> CreateCloneForDrawing(
+ const PicturePileImpl* other, unsigned thread_index);
+
+ PicturePileImpl(const PicturePileImpl* other, unsigned thread_index);
+
+ void RasterCommon(
+ SkCanvas* canvas,
+ SkDrawPictureCallback* callback,
+ gfx::Rect canvas_rect,
+ float contents_scale,
+ RasterStats* raster_stats);
+
+ // Once instantiated, |clones_for_drawing_| can't be modified. This
+ // guarantees thread-safe access during the life time of a PicturePileImpl
+ // instance. This member variable must be last so that other member
+ // variables have already been initialized and can be clonable.
+ const ClonesForDrawing clones_for_drawing_;
+
+ DISALLOW_COPY_AND_ASSIGN(PicturePileImpl);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_PICTURE_PILE_IMPL_H_
diff --git a/chromium/cc/resources/picture_pile_impl_unittest.cc b/chromium/cc/resources/picture_pile_impl_unittest.cc
new file mode 100644
index 00000000000..6f33691b5da
--- /dev/null
+++ b/chromium/cc/resources/picture_pile_impl_unittest.cc
@@ -0,0 +1,800 @@
+// Copyright 2013 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 "base/memory/scoped_ptr.h"
+#include "cc/test/fake_picture_pile_impl.h"
+#include "cc/test/fake_rendering_stats_instrumentation.h"
+#include "cc/test/skia_common.h"
+#include "skia/ext/lazy_pixel_ref.h"
+#include "skia/ext/refptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkPixelRef.h"
+#include "third_party/skia/include/core/SkShader.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size_conversions.h"
+
+namespace cc {
+namespace {
+
+TEST(PicturePileImplTest, AnalyzeIsSolidUnscaled) {
+ gfx::Size tile_size(100, 100);
+ gfx::Size layer_bounds(400, 400);
+
+ scoped_refptr<FakePicturePileImpl> pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+
+ SkColor solid_color = SkColorSetARGB(255, 12, 23, 34);
+ SkPaint solid_paint;
+ solid_paint.setColor(solid_color);
+
+ SkColor non_solid_color = SkColorSetARGB(128, 45, 56, 67);
+ SkPaint non_solid_paint;
+ non_solid_paint.setColor(non_solid_color);
+
+ pile->add_draw_rect_with_paint(gfx::Rect(0, 0, 400, 400), solid_paint);
+ pile->RerecordPile();
+
+ // Ensure everything is solid
+ for (int y = 0; y <= 300; y += 100) {
+ for (int x = 0; x <= 300; x += 100) {
+ PicturePileImpl::Analysis analysis;
+ gfx::Rect rect(x, y, 100, 100);
+ pile->AnalyzeInRect(rect, 1.0, &analysis);
+ EXPECT_TRUE(analysis.is_solid_color) << rect.ToString();
+ EXPECT_EQ(analysis.solid_color, solid_color) << rect.ToString();
+ }
+ }
+
+ // One pixel non solid
+ pile->add_draw_rect_with_paint(gfx::Rect(50, 50, 1, 1), non_solid_paint);
+ pile->RerecordPile();
+
+ PicturePileImpl::Analysis analysis;
+ pile->AnalyzeInRect(gfx::Rect(0, 0, 100, 100), 1.0, &analysis);
+ EXPECT_FALSE(analysis.is_solid_color);
+
+ pile->AnalyzeInRect(gfx::Rect(100, 0, 100, 100), 1.0, &analysis);
+ EXPECT_TRUE(analysis.is_solid_color);
+ EXPECT_EQ(analysis.solid_color, solid_color);
+
+ // Boundaries should be clipped
+ analysis.is_solid_color = false;
+ pile->AnalyzeInRect(gfx::Rect(350, 0, 100, 100), 1.0, &analysis);
+ EXPECT_TRUE(analysis.is_solid_color);
+ EXPECT_EQ(analysis.solid_color, solid_color);
+
+ analysis.is_solid_color = false;
+ pile->AnalyzeInRect(gfx::Rect(0, 350, 100, 100), 1.0, &analysis);
+ EXPECT_TRUE(analysis.is_solid_color);
+ EXPECT_EQ(analysis.solid_color, solid_color);
+
+ analysis.is_solid_color = false;
+ pile->AnalyzeInRect(gfx::Rect(350, 350, 100, 100), 1.0, &analysis);
+ EXPECT_TRUE(analysis.is_solid_color);
+ EXPECT_EQ(analysis.solid_color, solid_color);
+}
+
+TEST(PicturePileImplTest, AnalyzeIsSolidScaled) {
+ gfx::Size tile_size(100, 100);
+ gfx::Size layer_bounds(400, 400);
+
+ scoped_refptr<FakePicturePileImpl> pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+
+ SkColor solid_color = SkColorSetARGB(255, 12, 23, 34);
+ SkPaint solid_paint;
+ solid_paint.setColor(solid_color);
+
+ SkColor non_solid_color = SkColorSetARGB(128, 45, 56, 67);
+ SkPaint non_solid_paint;
+ non_solid_paint.setColor(non_solid_color);
+
+ pile->add_draw_rect_with_paint(gfx::Rect(0, 0, 400, 400), solid_paint);
+ pile->RerecordPile();
+
+ // Ensure everything is solid
+ for (int y = 0; y <= 30; y += 10) {
+ for (int x = 0; x <= 30; x += 10) {
+ PicturePileImpl::Analysis analysis;
+ gfx::Rect rect(x, y, 10, 10);
+ pile->AnalyzeInRect(rect, 0.1f, &analysis);
+ EXPECT_TRUE(analysis.is_solid_color) << rect.ToString();
+ EXPECT_EQ(analysis.solid_color, solid_color) << rect.ToString();
+ }
+ }
+
+ // One pixel non solid
+ pile->add_draw_rect_with_paint(gfx::Rect(50, 50, 1, 1), non_solid_paint);
+ pile->RerecordPile();
+
+ PicturePileImpl::Analysis analysis;
+ pile->AnalyzeInRect(gfx::Rect(0, 0, 10, 10), 0.1f, &analysis);
+ EXPECT_FALSE(analysis.is_solid_color);
+
+ pile->AnalyzeInRect(gfx::Rect(10, 0, 10, 10), 0.1f, &analysis);
+ EXPECT_TRUE(analysis.is_solid_color);
+ EXPECT_EQ(analysis.solid_color, solid_color);
+
+ // Boundaries should be clipped
+ analysis.is_solid_color = false;
+ pile->AnalyzeInRect(gfx::Rect(35, 0, 10, 10), 0.1f, &analysis);
+ EXPECT_TRUE(analysis.is_solid_color);
+ EXPECT_EQ(analysis.solid_color, solid_color);
+
+ analysis.is_solid_color = false;
+ pile->AnalyzeInRect(gfx::Rect(0, 35, 10, 10), 0.1f, &analysis);
+ EXPECT_TRUE(analysis.is_solid_color);
+ EXPECT_EQ(analysis.solid_color, solid_color);
+
+ analysis.is_solid_color = false;
+ pile->AnalyzeInRect(gfx::Rect(35, 35, 10, 10), 0.1f, &analysis);
+ EXPECT_TRUE(analysis.is_solid_color);
+ EXPECT_EQ(analysis.solid_color, solid_color);
+}
+
+TEST(PicturePileImplTest, AnalyzeIsSolidEmpty) {
+ gfx::Size tile_size(100, 100);
+ gfx::Size layer_bounds(400, 400);
+
+ scoped_refptr<FakePicturePileImpl> pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+ PicturePileImpl::Analysis analysis;
+ EXPECT_FALSE(analysis.is_solid_color);
+
+ pile->AnalyzeInRect(gfx::Rect(0, 0, 400, 400), 1.f, &analysis);
+
+ EXPECT_TRUE(analysis.is_solid_color);
+ EXPECT_EQ(analysis.solid_color, SkColorSetARGB(0, 0, 0, 0));
+}
+
+TEST(PicturePileImplTest, PixelRefIteratorEmpty) {
+ gfx::Size tile_size(128, 128);
+ gfx::Size layer_bounds(256, 256);
+
+ // Create a filled pile with no recording.
+ scoped_refptr<FakePicturePileImpl> pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+
+ // Tile sized iterators.
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 128, 128), 1.0, pile.get());
+ EXPECT_FALSE(iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 256, 256), 2.0, pile.get());
+ EXPECT_FALSE(iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 64, 64), 0.5, pile.get());
+ EXPECT_FALSE(iterator);
+ }
+ // Shifted tile sized iterators.
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(140, 140, 128, 128), 1.0, pile.get());
+ EXPECT_FALSE(iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(280, 280, 256, 256), 2.0, pile.get());
+ EXPECT_FALSE(iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(70, 70, 64, 64), 0.5, pile.get());
+ EXPECT_FALSE(iterator);
+ }
+ // Layer sized iterators.
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 256, 256), 1.0, pile.get());
+ EXPECT_FALSE(iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 512, 512), 2.0, pile.get());
+ EXPECT_FALSE(iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 128, 128), 0.5, pile.get());
+ EXPECT_FALSE(iterator);
+ }
+}
+
+TEST(PicturePileImplTest, PixelRefIteratorNoLazyRefs) {
+ gfx::Size tile_size(128, 128);
+ gfx::Size layer_bounds(256, 256);
+
+ scoped_refptr<FakePicturePileImpl> pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+
+ SkPaint simple_paint;
+ simple_paint.setColor(SkColorSetARGB(255, 12, 23, 34));
+
+ SkBitmap non_lazy_bitmap;
+ CreateBitmap(gfx::Size(128, 128), "notlazy", &non_lazy_bitmap);
+
+ pile->add_draw_rect_with_paint(gfx::Rect(0, 0, 256, 256), simple_paint);
+ pile->add_draw_rect_with_paint(gfx::Rect(128, 128, 512, 512), simple_paint);
+ pile->add_draw_rect_with_paint(gfx::Rect(512, 0, 256, 256), simple_paint);
+ pile->add_draw_rect_with_paint(gfx::Rect(0, 512, 256, 256), simple_paint);
+ pile->add_draw_bitmap(non_lazy_bitmap, gfx::Point(128, 0));
+ pile->add_draw_bitmap(non_lazy_bitmap, gfx::Point(0, 128));
+ pile->add_draw_bitmap(non_lazy_bitmap, gfx::Point(150, 150));
+
+ pile->RerecordPile();
+
+ // Tile sized iterators.
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 128, 128), 1.0, pile.get());
+ EXPECT_FALSE(iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 256, 256), 2.0, pile.get());
+ EXPECT_FALSE(iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 64, 64), 0.5, pile.get());
+ EXPECT_FALSE(iterator);
+ }
+ // Shifted tile sized iterators.
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(140, 140, 128, 128), 1.0, pile.get());
+ EXPECT_FALSE(iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(280, 280, 256, 256), 2.0, pile.get());
+ EXPECT_FALSE(iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(70, 70, 64, 64), 0.5, pile.get());
+ EXPECT_FALSE(iterator);
+ }
+ // Layer sized iterators.
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 256, 256), 1.0, pile.get());
+ EXPECT_FALSE(iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 512, 512), 2.0, pile.get());
+ EXPECT_FALSE(iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 128, 128), 0.5, pile.get());
+ EXPECT_FALSE(iterator);
+ }
+}
+
+TEST(PicturePileImplTest, PixelRefIteratorLazyRefs) {
+ gfx::Size tile_size(128, 128);
+ gfx::Size layer_bounds(256, 256);
+
+ scoped_refptr<FakePicturePileImpl> pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+
+ SkBitmap lazy_bitmap[2][2];
+ CreateBitmap(gfx::Size(32, 32), "lazy", &lazy_bitmap[0][0]);
+ CreateBitmap(gfx::Size(32, 32), "lazy", &lazy_bitmap[1][0]);
+ CreateBitmap(gfx::Size(32, 32), "lazy", &lazy_bitmap[1][1]);
+
+ // Lazy pixel refs are found in the following cells:
+ // |---|---|
+ // | x | |
+ // |---|---|
+ // | x | x |
+ // |---|---|
+ pile->add_draw_bitmap(lazy_bitmap[0][0], gfx::Point(0, 0));
+ pile->add_draw_bitmap(lazy_bitmap[1][0], gfx::Point(0, 130));
+ pile->add_draw_bitmap(lazy_bitmap[1][1], gfx::Point(140, 140));
+
+ pile->RerecordPile();
+
+ // Tile sized iterators. These should find only one pixel ref.
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 128, 128), 1.0, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][0].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 256, 256), 2.0, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][0].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 64, 64), 0.5, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][0].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ // Shifted tile sized iterators. These should find only one pixel ref.
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(140, 140, 128, 128), 1.0, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][1].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(280, 280, 256, 256), 2.0, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][1].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(70, 70, 64, 64), 0.5, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][1].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ // Ensure there's no lazy pixel refs in the empty cell
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(140, 0, 128, 128), 1.0, pile.get());
+ EXPECT_FALSE(iterator);
+ }
+ // Layer sized iterators. These should find all 3 pixel refs.
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 256, 256), 1.0, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][0].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][0].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][1].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 512, 512), 2.0, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][0].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][0].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][1].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 128, 128), 0.5, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][0].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][0].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][1].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+}
+
+TEST(PicturePileImplTest, PixelRefIteratorLazyRefsOneTile) {
+ gfx::Size tile_size(256, 256);
+ gfx::Size layer_bounds(512, 512);
+
+ scoped_refptr<FakePicturePileImpl> pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+
+ SkBitmap lazy_bitmap[2][2];
+ CreateBitmap(gfx::Size(32, 32), "lazy", &lazy_bitmap[0][0]);
+ CreateBitmap(gfx::Size(32, 32), "lazy", &lazy_bitmap[0][1]);
+ CreateBitmap(gfx::Size(32, 32), "lazy", &lazy_bitmap[1][1]);
+
+ // Lazy pixel refs are found in the following cells:
+ // |---|---|
+ // | x | x |
+ // |---|---|
+ // | | x |
+ // |---|---|
+ pile->add_draw_bitmap(lazy_bitmap[0][0], gfx::Point(0, 0));
+ pile->add_draw_bitmap(lazy_bitmap[0][1], gfx::Point(260, 0));
+ pile->add_draw_bitmap(lazy_bitmap[1][1], gfx::Point(260, 260));
+
+ pile->RerecordPile();
+
+ // Tile sized iterators. These should find only one pixel ref.
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 256, 256), 1.0, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][0].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 512, 512), 2.0, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][0].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 128, 128), 0.5, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][0].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ // Shifted tile sized iterators. These should find only one pixel ref.
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(260, 260, 256, 256), 1.0, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][1].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(520, 520, 512, 512), 2.0, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][1].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(130, 130, 128, 128), 0.5, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][1].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ // Ensure there's no lazy pixel refs in the empty cell
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 256, 256, 256), 1.0, pile.get());
+ EXPECT_FALSE(iterator);
+ }
+ // Layer sized iterators. These should find three pixel ref.
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 512, 512), 1.0, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][0].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][1].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][1].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 1024, 1024), 2.0, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][0].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][1].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][1].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 256, 256), 0.5, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][0].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][1].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][1].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+
+ // Copy test.
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 512, 512), 1.0, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][0].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][1].pixelRef());
+
+ // copy now points to the same spot as iterator,
+ // but both can be incremented independently.
+ PicturePileImpl::PixelRefIterator copy = iterator;
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][1].pixelRef());
+ EXPECT_FALSE(++iterator);
+
+ EXPECT_TRUE(copy);
+ EXPECT_TRUE(*copy == lazy_bitmap[0][1].pixelRef());
+ EXPECT_TRUE(++copy);
+ EXPECT_TRUE(*copy == lazy_bitmap[1][1].pixelRef());
+ EXPECT_FALSE(++copy);
+}
+
+TEST(PicturePileImplTest, PixelRefIteratorLazyRefsBaseNonLazy) {
+ gfx::Size tile_size(256, 256);
+ gfx::Size layer_bounds(512, 512);
+
+ scoped_refptr<FakePicturePileImpl> pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+
+ SkBitmap non_lazy_bitmap;
+ CreateBitmap(gfx::Size(512, 512), "notlazy", &non_lazy_bitmap);
+
+ SkBitmap lazy_bitmap[2][2];
+ CreateBitmap(gfx::Size(128, 128), "lazy", &lazy_bitmap[0][0]);
+ CreateBitmap(gfx::Size(128, 128), "lazy", &lazy_bitmap[0][1]);
+ CreateBitmap(gfx::Size(128, 128), "lazy", &lazy_bitmap[1][1]);
+
+ // One large non-lazy bitmap covers the whole grid.
+ // Lazy pixel refs are found in the following cells:
+ // |---|---|
+ // | x | x |
+ // |---|---|
+ // | | x |
+ // |---|---|
+ pile->add_draw_bitmap(non_lazy_bitmap, gfx::Point(0, 0));
+ pile->add_draw_bitmap(lazy_bitmap[0][0], gfx::Point(0, 0));
+ pile->add_draw_bitmap(lazy_bitmap[0][1], gfx::Point(260, 0));
+ pile->add_draw_bitmap(lazy_bitmap[1][1], gfx::Point(260, 260));
+
+ pile->RerecordPile();
+
+ // Tile sized iterators. These should find only one pixel ref.
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 256, 256), 1.0, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][0].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 512, 512), 2.0, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][0].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 128, 128), 0.5, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][0].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ // Shifted tile sized iterators. These should find only one pixel ref.
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(260, 260, 256, 256), 1.0, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][1].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(520, 520, 512, 512), 2.0, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][1].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(130, 130, 128, 128), 0.5, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][1].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ // Ensure there's no lazy pixel refs in the empty cell
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 256, 256, 256), 1.0, pile.get());
+ EXPECT_FALSE(iterator);
+ }
+ // Layer sized iterators. These should find three pixel ref.
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 512, 512), 1.0, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][0].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][1].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][1].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 1024, 1024), 2.0, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][0].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][1].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][1].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 256, 256), 0.5, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][0].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][1].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][1].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+}
+
+TEST(PicturePileImplTest, PixelRefIteratorMultiplePictures) {
+ gfx::Size tile_size(256, 256);
+ gfx::Size layer_bounds(256, 256);
+
+ SkTileGridPicture::TileGridInfo tile_grid_info;
+ tile_grid_info.fTileInterval = SkISize::Make(256, 256);
+ tile_grid_info.fMargin.setEmpty();
+ tile_grid_info.fOffset.setZero();
+
+ scoped_refptr<FakePicturePileImpl> pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+
+ SkBitmap lazy_bitmap[2][2];
+ CreateBitmap(gfx::Size(32, 32), "lazy", &lazy_bitmap[0][0]);
+ CreateBitmap(gfx::Size(32, 32), "lazy", &lazy_bitmap[0][1]);
+ CreateBitmap(gfx::Size(32, 32), "lazy", &lazy_bitmap[1][1]);
+ SkBitmap non_lazy_bitmap;
+ CreateBitmap(gfx::Size(256, 256), "notlazy", &non_lazy_bitmap);
+
+ // Each bitmap goes into its own picture, the final layout
+ // has lazy pixel refs in the following regions:
+ // ||=======||
+ // ||x| |x||
+ // ||-- --||
+ // || |x||
+ // ||=======||
+ pile->add_draw_bitmap(non_lazy_bitmap, gfx::Point(0, 0));
+ pile->RerecordPile();
+
+ FakeContentLayerClient content_layer_clients[2][2];
+ FakeRenderingStatsInstrumentation stats_instrumentation;
+ scoped_refptr<Picture> pictures[2][2];
+ for (int y = 0; y < 2; ++y) {
+ for (int x = 0; x < 2; ++x) {
+ if (x == 0 && y == 1)
+ continue;
+ content_layer_clients[y][x].add_draw_bitmap(
+ lazy_bitmap[y][x],
+ gfx::Point(x * 128 + 10, y * 128 + 10));
+ pictures[y][x] = Picture::Create(
+ gfx::Rect(x * 128 + 10, y * 128 + 10, 64, 64));
+ pictures[y][x]->Record(
+ &content_layer_clients[y][x],
+ tile_grid_info,
+ &stats_instrumentation);
+ pictures[y][x]->GatherPixelRefs(tile_grid_info, &stats_instrumentation);
+ pile->AddPictureToRecording(0, 0, pictures[y][x]);
+ }
+ }
+
+ // These should find only one pixel ref.
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 0, 128, 128), 1.0, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][0].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(128, 0, 128, 128), 1.0, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[0][1].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(128, 128, 128, 128), 1.0, pile.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][1].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+ // This one should not find any refs
+ {
+ PicturePileImpl::PixelRefIterator iterator(
+ gfx::Rect(0, 128, 128, 128), 1.0, pile.get());
+ EXPECT_FALSE(iterator);
+ }
+}
+
+TEST(PicturePileImpl, RasterContentsOpaque) {
+ gfx::Size tile_size(1000, 1000);
+ gfx::Size layer_bounds(3, 5);
+ float contents_scale = 1.5f;
+
+ scoped_refptr<FakePicturePileImpl> pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+ // Because the caller sets content opaque, it also promises that it
+ // has at least filled in layer_bounds opaquely.
+ SkPaint red_paint;
+ red_paint.setColor(SK_ColorRED);
+ pile->add_draw_rect_with_paint(gfx::Rect(layer_bounds), red_paint);
+
+ pile->SetMinContentsScale(contents_scale);
+ pile->set_background_color(SK_ColorRED);
+ pile->set_contents_opaque(true);
+ pile->RerecordPile();
+
+ gfx::Size content_bounds(
+ gfx::ToCeiledSize(gfx::ScaleSize(layer_bounds, contents_scale)));
+
+ // Simulate a canvas rect larger than the content bounds. Every pixel
+ // up to one pixel outside the content bounds is guaranteed to be opaque.
+ // Outside of that is undefined.
+ gfx::Rect canvas_rect(content_bounds);
+ canvas_rect.Inset(0, 0, -1, -1);
+
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config,
+ canvas_rect.width(),
+ canvas_rect.height());
+ bitmap.allocPixels();
+ SkCanvas canvas(bitmap);
+
+ PicturePileImpl::RasterStats raster_stats;
+ pile->RasterToBitmap(
+ &canvas, canvas_rect, contents_scale, &raster_stats);
+
+ SkColor* pixels = reinterpret_cast<SkColor*>(bitmap.getPixels());
+ int num_pixels = bitmap.width() * bitmap.height();
+ for (int i = 0; i < num_pixels; ++i) {
+ EXPECT_EQ(SkColorGetA(pixels[i]), 255u);
+ }
+}
+
+TEST(PicturePileImpl, RasterContentsTransparent) {
+ gfx::Size tile_size(1000, 1000);
+ gfx::Size layer_bounds(5, 3);
+ float contents_scale = 0.5f;
+
+ scoped_refptr<FakePicturePileImpl> pile =
+ FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds);
+ pile->set_background_color(SK_ColorTRANSPARENT);
+ pile->set_contents_opaque(false);
+ pile->SetMinContentsScale(contents_scale);
+ pile->RerecordPile();
+
+ gfx::Size content_bounds(
+ gfx::ToCeiledSize(gfx::ScaleSize(layer_bounds, contents_scale)));
+
+ gfx::Rect canvas_rect(content_bounds);
+ canvas_rect.Inset(0, 0, -1, -1);
+
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config,
+ canvas_rect.width(),
+ canvas_rect.height());
+ bitmap.allocPixels();
+ SkCanvas canvas(bitmap);
+
+ PicturePileImpl::RasterStats raster_stats;
+ pile->RasterToBitmap(
+ &canvas, canvas_rect, contents_scale, &raster_stats);
+
+ SkColor* pixels = reinterpret_cast<SkColor*>(bitmap.getPixels());
+ int num_pixels = bitmap.width() * bitmap.height();
+ for (int i = 0; i < num_pixels; ++i) {
+ EXPECT_EQ(SkColorGetA(pixels[i]), 0u);
+ }
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/resources/picture_pile_unittest.cc b/chromium/cc/resources/picture_pile_unittest.cc
new file mode 100644
index 00000000000..1eeb5ad5fe5
--- /dev/null
+++ b/chromium/cc/resources/picture_pile_unittest.cc
@@ -0,0 +1,211 @@
+// Copyright 2013 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 "cc/resources/picture_pile.h"
+#include "cc/test/fake_content_layer_client.h"
+#include "cc/test/fake_rendering_stats_instrumentation.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/size_conversions.h"
+
+namespace cc {
+namespace {
+
+class TestPicturePile : public PicturePile {
+ public:
+ using PicturePile::buffer_pixels;
+
+ PictureListMap& picture_list_map() { return picture_list_map_; }
+
+ typedef PicturePile::PictureList PictureList;
+ typedef PicturePile::PictureListMapKey PictureListMapKey;
+ typedef PicturePile::PictureListMap PictureListMap;
+
+ protected:
+ virtual ~TestPicturePile() {}
+};
+
+TEST(PicturePileTest, SmallInvalidateInflated) {
+ FakeContentLayerClient client;
+ FakeRenderingStatsInstrumentation stats_instrumentation;
+ scoped_refptr<TestPicturePile> pile = new TestPicturePile;
+ SkColor background_color = SK_ColorBLUE;
+
+ float min_scale = 0.125;
+ gfx::Size base_picture_size = pile->tiling().max_texture_size();
+
+ gfx::Size layer_size = base_picture_size;
+ pile->Resize(layer_size);
+ pile->SetTileGridSize(gfx::Size(1000, 1000));
+ pile->SetMinContentsScale(min_scale);
+
+ // Update the whole layer.
+ pile->Update(&client,
+ background_color,
+ false,
+ gfx::Rect(layer_size),
+ gfx::Rect(layer_size),
+ &stats_instrumentation);
+
+ // Invalidate something inside a tile.
+ gfx::Rect invalidate_rect(50, 50, 1, 1);
+ pile->Update(&client,
+ background_color,
+ false,
+ invalidate_rect,
+ gfx::Rect(layer_size),
+ &stats_instrumentation);
+
+ EXPECT_EQ(1, pile->tiling().num_tiles_x());
+ EXPECT_EQ(1, pile->tiling().num_tiles_y());
+
+ TestPicturePile::PictureList& picture_list =
+ pile->picture_list_map().find(
+ TestPicturePile::PictureListMapKey(0, 0))->second;
+ EXPECT_EQ(2u, picture_list.size());
+ for (TestPicturePile::PictureList::iterator it = picture_list.begin();
+ it != picture_list.end();
+ ++it) {
+ scoped_refptr<Picture> picture = *it;
+ gfx::Rect picture_rect =
+ gfx::ScaleToEnclosedRect(picture->LayerRect(), min_scale);
+
+ // The invalidation in each tile should have been made large enough
+ // that scaling it never makes a rect smaller than 1 px wide or tall.
+ EXPECT_FALSE(picture_rect.IsEmpty()) << "Picture rect " <<
+ picture_rect.ToString();
+ }
+}
+
+TEST(PicturePileTest, LargeInvalidateInflated) {
+ FakeContentLayerClient client;
+ FakeRenderingStatsInstrumentation stats_instrumentation;
+ scoped_refptr<TestPicturePile> pile = new TestPicturePile;
+ SkColor background_color = SK_ColorBLUE;
+
+ float min_scale = 0.125;
+ gfx::Size base_picture_size = pile->tiling().max_texture_size();
+
+ gfx::Size layer_size = base_picture_size;
+ pile->Resize(layer_size);
+ pile->SetTileGridSize(gfx::Size(1000, 1000));
+ pile->SetMinContentsScale(min_scale);
+
+ // Update the whole layer.
+ pile->Update(&client,
+ background_color,
+ false,
+ gfx::Rect(layer_size),
+ gfx::Rect(layer_size),
+ &stats_instrumentation);
+
+ // Invalidate something inside a tile.
+ gfx::Rect invalidate_rect(50, 50, 100, 100);
+ pile->Update(&client,
+ background_color,
+ false,
+ invalidate_rect,
+ gfx::Rect(layer_size),
+ &stats_instrumentation);
+
+ EXPECT_EQ(1, pile->tiling().num_tiles_x());
+ EXPECT_EQ(1, pile->tiling().num_tiles_y());
+
+ TestPicturePile::PictureList& picture_list =
+ pile->picture_list_map().find(
+ TestPicturePile::PictureListMapKey(0, 0))->second;
+ EXPECT_EQ(2u, picture_list.size());
+
+ int expected_inflation = pile->buffer_pixels();
+
+ scoped_refptr<Picture> base_picture = *picture_list.begin();
+ gfx::Rect base_picture_rect(layer_size);
+ base_picture_rect.Inset(-expected_inflation, -expected_inflation);
+ EXPECT_EQ(base_picture_rect.ToString(),
+ base_picture->LayerRect().ToString());
+
+ scoped_refptr<Picture> picture = *(++picture_list.begin());
+ gfx::Rect picture_rect(invalidate_rect);
+ picture_rect.Inset(-expected_inflation, -expected_inflation);
+ EXPECT_EQ(picture_rect.ToString(),
+ picture->LayerRect().ToString());
+}
+
+TEST(PicturePileTest, InvalidateOnTileBoundaryInflated) {
+ FakeContentLayerClient client;
+ FakeRenderingStatsInstrumentation stats_instrumentation;
+ scoped_refptr<TestPicturePile> pile = new TestPicturePile;
+ SkColor background_color = SK_ColorBLUE;
+
+ float min_scale = 0.125;
+ gfx::Size base_picture_size = pile->tiling().max_texture_size();
+
+ gfx::Size layer_size =
+ gfx::ToFlooredSize(gfx::ScaleSize(base_picture_size, 2.f));
+ pile->Resize(layer_size);
+ pile->SetTileGridSize(gfx::Size(1000, 1000));
+ pile->SetMinContentsScale(min_scale);
+
+ // Due to border pixels, we should have 3 tiles.
+ EXPECT_EQ(3, pile->tiling().num_tiles_x());
+ EXPECT_EQ(3, pile->tiling().num_tiles_y());
+
+ // We should have 1/.125 - 1 = 7 border pixels.
+ EXPECT_EQ(7, pile->buffer_pixels());
+ EXPECT_EQ(7, pile->tiling().border_texels());
+
+ // Update the whole layer.
+ pile->Update(&client,
+ background_color,
+ false,
+ gfx::Rect(layer_size),
+ gfx::Rect(layer_size),
+ &stats_instrumentation);
+
+ // Invalidate something just over a tile boundary by a single pixel.
+ // This will invalidate the tile (1, 1), as well as 1 row of pixels in (1, 0).
+ gfx::Rect invalidate_rect(
+ pile->tiling().TileBoundsWithBorder(0, 0).right(),
+ pile->tiling().TileBoundsWithBorder(0, 0).bottom() - 1,
+ 50,
+ 50);
+ pile->Update(&client,
+ background_color,
+ false,
+ invalidate_rect,
+ gfx::Rect(layer_size),
+ &stats_instrumentation);
+
+ for (int i = 0; i < pile->tiling().num_tiles_x(); ++i) {
+ for (int j = 0; j < pile->tiling().num_tiles_y(); ++j) {
+ // (1, 0) and (1, 1) should be invalidated partially.
+ bool expect_invalidated = i == 1 && (j == 0 || j == 1);
+
+ TestPicturePile::PictureList& picture_list =
+ pile->picture_list_map().find(
+ TestPicturePile::PictureListMapKey(i, j))->second;
+ if (!expect_invalidated) {
+ EXPECT_EQ(1u, picture_list.size()) << "For i,j " << i << "," << j;
+ continue;
+ }
+
+ EXPECT_EQ(2u, picture_list.size()) << "For i,j " << i << "," << j;
+ for (TestPicturePile::PictureList::iterator it = picture_list.begin();
+ it != picture_list.end();
+ ++it) {
+ scoped_refptr<Picture> picture = *it;
+ gfx::Rect picture_rect =
+ gfx::ScaleToEnclosedRect(picture->LayerRect(), min_scale);
+
+ // The invalidation in each tile should have been made large enough
+ // that scaling it never makes a rect smaller than 1 px wide or tall.
+ EXPECT_FALSE(picture_rect.IsEmpty()) << "Picture rect " <<
+ picture_rect.ToString();
+ }
+ }
+ }
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/resources/picture_unittest.cc b/chromium/cc/resources/picture_unittest.cc
new file mode 100644
index 00000000000..ee5e07dbdd6
--- /dev/null
+++ b/chromium/cc/resources/picture_unittest.cc
@@ -0,0 +1,381 @@
+// Copyright 2013 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 "cc/resources/picture.h"
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "cc/test/fake_content_layer_client.h"
+#include "cc/test/fake_rendering_stats_instrumentation.h"
+#include "cc/test/skia_common.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkDevice.h"
+#include "third_party/skia/include/core/SkGraphics.h"
+#include "third_party/skia/include/core/SkPixelRef.h"
+#include "third_party/skia/include/core/SkTileGridPicture.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/skia_util.h"
+
+namespace cc {
+namespace {
+
+TEST(PictureTest, AsBase64String) {
+ SkGraphics::Init();
+
+ gfx::Rect layer_rect(100, 100);
+
+ SkTileGridPicture::TileGridInfo tile_grid_info;
+ tile_grid_info.fTileInterval = SkISize::Make(100, 100);
+ tile_grid_info.fMargin.setEmpty();
+ tile_grid_info.fOffset.setZero();
+
+ FakeContentLayerClient content_layer_client;
+ FakeRenderingStatsInstrumentation stats_instrumentation;
+
+ scoped_ptr<base::Value> tmp;
+
+ SkPaint red_paint;
+ red_paint.setColor(SkColorSetARGB(255, 255, 0, 0));
+ SkPaint green_paint;
+ green_paint.setColor(SkColorSetARGB(255, 0, 255, 0));
+
+ // Invalid picture (not a dict).
+ tmp.reset(new base::StringValue("abc!@#$%"));
+ scoped_refptr<Picture> invalid_picture =
+ Picture::CreateFromValue(tmp.get());
+ EXPECT_TRUE(!invalid_picture.get());
+
+ // Single full-size rect picture.
+ content_layer_client.add_draw_rect(layer_rect, red_paint);
+ scoped_refptr<Picture> one_rect_picture = Picture::Create(layer_rect);
+ one_rect_picture->Record(&content_layer_client,
+ tile_grid_info,
+ &stats_instrumentation);
+ scoped_ptr<base::Value> serialized_one_rect(
+ one_rect_picture->AsValue());
+
+ // Reconstruct the picture.
+ scoped_refptr<Picture> one_rect_picture_check =
+ Picture::CreateFromValue(serialized_one_rect.get());
+ EXPECT_TRUE(!!one_rect_picture_check.get());
+
+ // Check for equivalence.
+ unsigned char one_rect_buffer[4 * 100 * 100] = {0};
+ DrawPicture(one_rect_buffer, layer_rect, one_rect_picture);
+ unsigned char one_rect_buffer_check[4 * 100 * 100] = {0};
+ DrawPicture(one_rect_buffer_check, layer_rect, one_rect_picture_check);
+
+ EXPECT_EQ(one_rect_picture->LayerRect(),
+ one_rect_picture_check->LayerRect());
+ EXPECT_EQ(one_rect_picture->OpaqueRect(),
+ one_rect_picture_check->OpaqueRect());
+ EXPECT_TRUE(
+ memcmp(one_rect_buffer, one_rect_buffer_check, 4 * 100 * 100) == 0);
+
+ // Two rect picture.
+ content_layer_client.add_draw_rect(gfx::Rect(25, 25, 50, 50), green_paint);
+ scoped_refptr<Picture> two_rect_picture = Picture::Create(layer_rect);
+ two_rect_picture->Record(&content_layer_client,
+ tile_grid_info,
+ &stats_instrumentation);
+
+ scoped_ptr<base::Value> serialized_two_rect(
+ two_rect_picture->AsValue());
+
+ // Reconstruct the picture.
+ scoped_refptr<Picture> two_rect_picture_check =
+ Picture::CreateFromValue(serialized_two_rect.get());
+ EXPECT_TRUE(!!two_rect_picture_check.get());
+
+ // Check for equivalence.
+ unsigned char two_rect_buffer[4 * 100 * 100] = {0};
+ DrawPicture(two_rect_buffer, layer_rect, two_rect_picture);
+ unsigned char two_rect_buffer_check[4 * 100 * 100] = {0};
+ DrawPicture(two_rect_buffer_check, layer_rect, two_rect_picture_check);
+
+ EXPECT_EQ(two_rect_picture->LayerRect(),
+ two_rect_picture_check->LayerRect());
+ EXPECT_EQ(two_rect_picture->OpaqueRect(),
+ two_rect_picture_check->OpaqueRect());
+ EXPECT_TRUE(
+ memcmp(two_rect_buffer, two_rect_buffer_check, 4 * 100 * 100) == 0);
+}
+
+TEST(PictureTest, PixelRefIterator) {
+ gfx::Rect layer_rect(2048, 2048);
+
+ SkTileGridPicture::TileGridInfo tile_grid_info;
+ tile_grid_info.fTileInterval = SkISize::Make(512, 512);
+ tile_grid_info.fMargin.setEmpty();
+ tile_grid_info.fOffset.setZero();
+
+ FakeContentLayerClient content_layer_client;
+ FakeRenderingStatsInstrumentation stats_instrumentation;
+
+ // Lazy pixel refs are found in the following grids:
+ // |---|---|---|---|
+ // | | x | | x |
+ // |---|---|---|---|
+ // | x | | x | |
+ // |---|---|---|---|
+ // | | x | | x |
+ // |---|---|---|---|
+ // | x | | x | |
+ // |---|---|---|---|
+ SkBitmap lazy_bitmap[4][4];
+ for (int y = 0; y < 4; ++y) {
+ for (int x = 0; x < 4; ++x) {
+ if ((x + y) & 1) {
+ CreateBitmap(gfx::Size(500, 500), "lazy", &lazy_bitmap[y][x]);
+ content_layer_client.add_draw_bitmap(
+ lazy_bitmap[y][x],
+ gfx::Point(x * 512 + 6, y * 512 + 6));
+ }
+ }
+ }
+
+ scoped_refptr<Picture> picture = Picture::Create(layer_rect);
+ picture->Record(&content_layer_client,
+ tile_grid_info,
+ &stats_instrumentation);
+ picture->GatherPixelRefs(tile_grid_info, &stats_instrumentation);
+
+ // Default iterator does not have any pixel refs
+ {
+ Picture::PixelRefIterator iterator;
+ EXPECT_FALSE(iterator);
+ }
+ for (int y = 0; y < 4; ++y) {
+ for (int x = 0; x < 4; ++x) {
+ Picture::PixelRefIterator iterator(gfx::Rect(x * 512, y * 512, 500, 500),
+ picture.get());
+ if ((x + y) & 1) {
+ EXPECT_TRUE(iterator) << x << " " << y;
+ EXPECT_TRUE(*iterator == lazy_bitmap[y][x].pixelRef()) << x << " " << y;
+ EXPECT_FALSE(++iterator) << x << " " << y;
+ } else {
+ EXPECT_FALSE(iterator) << x << " " << y;
+ }
+ }
+ }
+ // Capture 4 pixel refs.
+ {
+ Picture::PixelRefIterator iterator(gfx::Rect(512, 512, 2048, 2048),
+ picture.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][2].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[2][1].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[2][3].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[3][2].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+
+ // Copy test.
+ Picture::PixelRefIterator iterator(gfx::Rect(512, 512, 2048, 2048),
+ picture.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][2].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[2][1].pixelRef());
+
+ // copy now points to the same spot as iterator,
+ // but both can be incremented independently.
+ Picture::PixelRefIterator copy = iterator;
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[2][3].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[3][2].pixelRef());
+ EXPECT_FALSE(++iterator);
+
+ EXPECT_TRUE(copy);
+ EXPECT_TRUE(*copy == lazy_bitmap[2][1].pixelRef());
+ EXPECT_TRUE(++copy);
+ EXPECT_TRUE(*copy == lazy_bitmap[2][3].pixelRef());
+ EXPECT_TRUE(++copy);
+ EXPECT_TRUE(*copy == lazy_bitmap[3][2].pixelRef());
+ EXPECT_FALSE(++copy);
+}
+
+TEST(PictureTest, PixelRefIteratorNonZeroLayer) {
+ gfx::Rect layer_rect(1024, 0, 2048, 2048);
+
+ SkTileGridPicture::TileGridInfo tile_grid_info;
+ tile_grid_info.fTileInterval = SkISize::Make(512, 512);
+ tile_grid_info.fMargin.setEmpty();
+ tile_grid_info.fOffset.setZero();
+
+ FakeContentLayerClient content_layer_client;
+ FakeRenderingStatsInstrumentation stats_instrumentation;
+
+ // Lazy pixel refs are found in the following grids:
+ // |---|---|---|---|
+ // | | x | | x |
+ // |---|---|---|---|
+ // | x | | x | |
+ // |---|---|---|---|
+ // | | x | | x |
+ // |---|---|---|---|
+ // | x | | x | |
+ // |---|---|---|---|
+ SkBitmap lazy_bitmap[4][4];
+ for (int y = 0; y < 4; ++y) {
+ for (int x = 0; x < 4; ++x) {
+ if ((x + y) & 1) {
+ CreateBitmap(gfx::Size(500, 500), "lazy", &lazy_bitmap[y][x]);
+ content_layer_client.add_draw_bitmap(
+ lazy_bitmap[y][x],
+ gfx::Point(1024 + x * 512 + 6, y * 512 + 6));
+ }
+ }
+ }
+
+ scoped_refptr<Picture> picture = Picture::Create(layer_rect);
+ picture->Record(&content_layer_client,
+ tile_grid_info,
+ &stats_instrumentation);
+ picture->GatherPixelRefs(tile_grid_info, &stats_instrumentation);
+
+ // Default iterator does not have any pixel refs
+ {
+ Picture::PixelRefIterator iterator;
+ EXPECT_FALSE(iterator);
+ }
+ for (int y = 0; y < 4; ++y) {
+ for (int x = 0; x < 4; ++x) {
+ Picture::PixelRefIterator iterator(
+ gfx::Rect(1024 + x * 512, y * 512, 500, 500), picture.get());
+ if ((x + y) & 1) {
+ EXPECT_TRUE(iterator) << x << " " << y;
+ EXPECT_TRUE(*iterator == lazy_bitmap[y][x].pixelRef());
+ EXPECT_FALSE(++iterator) << x << " " << y;
+ } else {
+ EXPECT_FALSE(iterator) << x << " " << y;
+ }
+ }
+ }
+ // Capture 4 pixel refs.
+ {
+ Picture::PixelRefIterator iterator(gfx::Rect(1024 + 512, 512, 2048, 2048),
+ picture.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][2].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[2][1].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[2][3].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[3][2].pixelRef());
+ EXPECT_FALSE(++iterator);
+ }
+
+ // Copy test.
+ {
+ Picture::PixelRefIterator iterator(gfx::Rect(1024 + 512, 512, 2048, 2048),
+ picture.get());
+ EXPECT_TRUE(iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[1][2].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[2][1].pixelRef());
+
+ // copy now points to the same spot as iterator,
+ // but both can be incremented independently.
+ Picture::PixelRefIterator copy = iterator;
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[2][3].pixelRef());
+ EXPECT_TRUE(++iterator);
+ EXPECT_TRUE(*iterator == lazy_bitmap[3][2].pixelRef());
+ EXPECT_FALSE(++iterator);
+
+ EXPECT_TRUE(copy);
+ EXPECT_TRUE(*copy == lazy_bitmap[2][1].pixelRef());
+ EXPECT_TRUE(++copy);
+ EXPECT_TRUE(*copy == lazy_bitmap[2][3].pixelRef());
+ EXPECT_TRUE(++copy);
+ EXPECT_TRUE(*copy == lazy_bitmap[3][2].pixelRef());
+ EXPECT_FALSE(++copy);
+ }
+
+ // Non intersecting rects
+ {
+ Picture::PixelRefIterator iterator(gfx::Rect(0, 0, 1000, 1000),
+ picture.get());
+ EXPECT_FALSE(iterator);
+ }
+ {
+ Picture::PixelRefIterator iterator(gfx::Rect(3500, 0, 1000, 1000),
+ picture.get());
+ EXPECT_FALSE(iterator);
+ }
+ {
+ Picture::PixelRefIterator iterator(gfx::Rect(0, 1100, 1000, 1000),
+ picture.get());
+ EXPECT_FALSE(iterator);
+ }
+ {
+ Picture::PixelRefIterator iterator(gfx::Rect(3500, 1100, 1000, 1000),
+ picture.get());
+ EXPECT_FALSE(iterator);
+ }
+}
+
+TEST(PictureTest, PixelRefIteratorOnePixelQuery) {
+ gfx::Rect layer_rect(2048, 2048);
+
+ SkTileGridPicture::TileGridInfo tile_grid_info;
+ tile_grid_info.fTileInterval = SkISize::Make(512, 512);
+ tile_grid_info.fMargin.setEmpty();
+ tile_grid_info.fOffset.setZero();
+
+ FakeContentLayerClient content_layer_client;
+ FakeRenderingStatsInstrumentation stats_instrumentation;
+
+ // Lazy pixel refs are found in the following grids:
+ // |---|---|---|---|
+ // | | x | | x |
+ // |---|---|---|---|
+ // | x | | x | |
+ // |---|---|---|---|
+ // | | x | | x |
+ // |---|---|---|---|
+ // | x | | x | |
+ // |---|---|---|---|
+ SkBitmap lazy_bitmap[4][4];
+ for (int y = 0; y < 4; ++y) {
+ for (int x = 0; x < 4; ++x) {
+ if ((x + y) & 1) {
+ CreateBitmap(gfx::Size(500, 500), "lazy", &lazy_bitmap[y][x]);
+ content_layer_client.add_draw_bitmap(
+ lazy_bitmap[y][x],
+ gfx::Point(x * 512 + 6, y * 512 + 6));
+ }
+ }
+ }
+
+ scoped_refptr<Picture> picture = Picture::Create(layer_rect);
+ picture->Record(&content_layer_client,
+ tile_grid_info,
+ &stats_instrumentation);
+ picture->GatherPixelRefs(tile_grid_info, &stats_instrumentation);
+
+ for (int y = 0; y < 4; ++y) {
+ for (int x = 0; x < 4; ++x) {
+ Picture::PixelRefIterator iterator(
+ gfx::Rect(x * 512, y * 512 + 256, 1, 1), picture.get());
+ if ((x + y) & 1) {
+ EXPECT_TRUE(iterator) << x << " " << y;
+ EXPECT_TRUE(*iterator == lazy_bitmap[y][x].pixelRef());
+ EXPECT_FALSE(++iterator) << x << " " << y;
+ } else {
+ EXPECT_FALSE(iterator) << x << " " << y;
+ }
+ }
+ }
+}
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/resources/pixel_buffer_raster_worker_pool.cc b/chromium/cc/resources/pixel_buffer_raster_worker_pool.cc
new file mode 100644
index 00000000000..a03ed81f4e1
--- /dev/null
+++ b/chromium/cc/resources/pixel_buffer_raster_worker_pool.cc
@@ -0,0 +1,683 @@
+// Copyright 2013 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 "cc/resources/pixel_buffer_raster_worker_pool.h"
+
+#include "base/containers/stack_container.h"
+#include "base/debug/trace_event.h"
+#include "base/values.h"
+#include "cc/debug/traced_value.h"
+#include "cc/resources/resource.h"
+#include "third_party/skia/include/core/SkDevice.h"
+
+namespace cc {
+
+namespace {
+
+class PixelBufferWorkerPoolTaskImpl : public internal::WorkerPoolTask {
+ public:
+ typedef base::Callback<void(bool was_canceled, bool needs_upload)> Reply;
+
+ PixelBufferWorkerPoolTaskImpl(internal::RasterWorkerPoolTask* task,
+ uint8_t* buffer,
+ const Reply& reply)
+ : task_(task),
+ buffer_(buffer),
+ reply_(reply),
+ needs_upload_(false) {
+ }
+
+ // Overridden from internal::WorkerPoolTask:
+ virtual void RunOnWorkerThread(unsigned thread_index) OVERRIDE {
+ // |buffer_| can be NULL in lost context situations.
+ if (!buffer_) {
+ // |needs_upload_| still needs to be true as task has not
+ // been canceled.
+ needs_upload_ = true;
+ return;
+ }
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config,
+ task_->resource()->size().width(),
+ task_->resource()->size().height());
+ bitmap.setPixels(buffer_);
+ SkDevice device(bitmap);
+ needs_upload_ = task_->RunOnWorkerThread(&device, thread_index);
+ }
+ virtual void CompleteOnOriginThread() OVERRIDE {
+ // |needs_upload_| must be be false if task didn't run.
+ DCHECK(HasFinishedRunning() || !needs_upload_);
+ reply_.Run(!HasFinishedRunning(), needs_upload_);
+ }
+
+ private:
+ virtual ~PixelBufferWorkerPoolTaskImpl() {}
+
+ scoped_refptr<internal::RasterWorkerPoolTask> task_;
+ uint8_t* buffer_;
+ const Reply reply_;
+ bool needs_upload_;
+
+ DISALLOW_COPY_AND_ASSIGN(PixelBufferWorkerPoolTaskImpl);
+};
+
+// If we raster too fast we become upload bound, and pending
+// uploads consume memory. For maximum upload throughput, we would
+// want to allow for upload_throughput * pipeline_time of pending
+// uploads, after which we are just wasting memory. Since we don't
+// know our upload throughput yet, this just caps our memory usage.
+#if defined(OS_ANDROID)
+// For reference Nexus10 can upload 1MB in about 2.5ms.
+const size_t kMaxBytesUploadedPerMs = (2 * 1024 * 1024) / 5;
+#else
+// For reference Chromebook Pixel can upload 1MB in about 0.5ms.
+const size_t kMaxBytesUploadedPerMs = 1024 * 1024 * 2;
+#endif
+
+// Assuming a two frame deep pipeline.
+const size_t kMaxPendingUploadBytes = 16 * 2 * kMaxBytesUploadedPerMs;
+
+const int kCheckForCompletedRasterTasksDelayMs = 6;
+
+const size_t kMaxScheduledRasterTasks = 48;
+
+typedef base::StackVector<internal::GraphNode*,
+ kMaxScheduledRasterTasks> NodeVector;
+
+void AddDependenciesToGraphNode(
+ internal::GraphNode* node,
+ const NodeVector::ContainerType& dependencies) {
+ for (NodeVector::ContainerType::const_iterator it = dependencies.begin();
+ it != dependencies.end(); ++it) {
+ internal::GraphNode* dependency = *it;
+
+ node->add_dependency();
+ dependency->add_dependent(node);
+ }
+}
+
+// Only used as std::find_if predicate for DCHECKs.
+bool WasCanceled(const internal::RasterWorkerPoolTask* task) {
+ return task->WasCanceled();
+}
+
+} // namespace
+
+PixelBufferRasterWorkerPool::PixelBufferRasterWorkerPool(
+ ResourceProvider* resource_provider,
+ size_t num_threads)
+ : RasterWorkerPool(resource_provider, num_threads),
+ shutdown_(false),
+ scheduled_raster_task_count_(0),
+ bytes_pending_upload_(0),
+ has_performed_uploads_since_last_flush_(false),
+ check_for_completed_raster_tasks_pending_(false),
+ should_notify_client_if_no_tasks_are_pending_(false),
+ should_notify_client_if_no_tasks_required_for_activation_are_pending_(
+ false) {
+}
+
+PixelBufferRasterWorkerPool::~PixelBufferRasterWorkerPool() {
+ DCHECK(shutdown_);
+ DCHECK(!check_for_completed_raster_tasks_pending_);
+ DCHECK_EQ(0u, pixel_buffer_tasks_.size());
+ DCHECK_EQ(0u, tasks_with_pending_upload_.size());
+ DCHECK_EQ(0u, completed_tasks_.size());
+}
+
+void PixelBufferRasterWorkerPool::Shutdown() {
+ shutdown_ = true;
+ RasterWorkerPool::Shutdown();
+ RasterWorkerPool::CheckForCompletedTasks();
+ CheckForCompletedUploads();
+ check_for_completed_raster_tasks_callback_.Cancel();
+ check_for_completed_raster_tasks_pending_ = false;
+ for (TaskMap::iterator it = pixel_buffer_tasks_.begin();
+ it != pixel_buffer_tasks_.end(); ++it) {
+ internal::RasterWorkerPoolTask* task = it->first;
+ internal::WorkerPoolTask* pixel_buffer_task = it->second.get();
+
+ // All inactive tasks needs to be canceled.
+ if (!pixel_buffer_task && !task->HasFinishedRunning()) {
+ task->DidRun(true);
+ completed_tasks_.push_back(task);
+ }
+ }
+ DCHECK_EQ(completed_tasks_.size(), pixel_buffer_tasks_.size());
+}
+
+void PixelBufferRasterWorkerPool::ScheduleTasks(RasterTask::Queue* queue) {
+ TRACE_EVENT0("cc", "PixelBufferRasterWorkerPool::ScheduleTasks");
+
+ RasterWorkerPool::SetRasterTasks(queue);
+
+ if (!should_notify_client_if_no_tasks_are_pending_)
+ TRACE_EVENT_ASYNC_BEGIN0("cc", "ScheduledTasks", this);
+
+ should_notify_client_if_no_tasks_are_pending_ = true;
+ should_notify_client_if_no_tasks_required_for_activation_are_pending_ = true;
+
+ // Build new pixel buffer task set.
+ TaskMap new_pixel_buffer_tasks;
+ for (RasterTaskVector::const_iterator it = raster_tasks().begin();
+ it != raster_tasks().end(); ++it) {
+ internal::RasterWorkerPoolTask* task = it->get();
+ DCHECK(new_pixel_buffer_tasks.find(task) == new_pixel_buffer_tasks.end());
+ DCHECK(!task->HasCompleted());
+
+ // Use existing pixel buffer task if available.
+ TaskMap::iterator pixel_buffer_it = pixel_buffer_tasks_.find(task);
+ if (pixel_buffer_it == pixel_buffer_tasks_.end()) {
+ new_pixel_buffer_tasks[task] = NULL;
+ continue;
+ }
+
+ new_pixel_buffer_tasks[task] = pixel_buffer_it->second;
+ pixel_buffer_tasks_.erase(task);
+ }
+
+ // Transfer remaining pixel buffer tasks to |new_pixel_buffer_tasks|
+ // and cancel all remaining inactive tasks.
+ for (TaskMap::iterator it = pixel_buffer_tasks_.begin();
+ it != pixel_buffer_tasks_.end(); ++it) {
+ internal::RasterWorkerPoolTask* task = it->first;
+ internal::WorkerPoolTask* pixel_buffer_task = it->second.get();
+
+ // Move task to |new_pixel_buffer_tasks|
+ new_pixel_buffer_tasks[task] = pixel_buffer_task;
+
+ // Inactive task can be canceled.
+ if (!pixel_buffer_task && !task->HasFinishedRunning()) {
+ task->DidRun(true);
+ DCHECK(std::find(completed_tasks_.begin(),
+ completed_tasks_.end(),
+ task) == completed_tasks_.end());
+ completed_tasks_.push_back(task);
+ }
+ }
+
+ tasks_required_for_activation_.clear();
+ for (TaskMap::iterator it = new_pixel_buffer_tasks.begin();
+ it != new_pixel_buffer_tasks.end(); ++it) {
+ internal::RasterWorkerPoolTask* task = it->first;
+ if (IsRasterTaskRequiredForActivation(task))
+ tasks_required_for_activation_.insert(task);
+ }
+
+ // |tasks_required_for_activation_| contains all tasks that need to
+ // complete before we can send a "ready to activate" signal. Tasks
+ // that have already completed should not be part of this set.
+ for (TaskDeque::const_iterator it = completed_tasks_.begin();
+ it != completed_tasks_.end(); ++it) {
+ tasks_required_for_activation_.erase(*it);
+ }
+
+ pixel_buffer_tasks_.swap(new_pixel_buffer_tasks);
+
+ // Check for completed tasks when ScheduleTasks() is called as
+ // priorities might have changed and this maximizes the number
+ // of top priority tasks that are scheduled.
+ RasterWorkerPool::CheckForCompletedTasks();
+ CheckForCompletedUploads();
+ FlushUploads();
+
+ // Schedule new tasks.
+ ScheduleMoreTasks();
+
+ // Cancel any pending check for completed raster tasks and schedule
+ // another check.
+ check_for_completed_raster_tasks_callback_.Cancel();
+ check_for_completed_raster_tasks_pending_ = false;
+ ScheduleCheckForCompletedRasterTasks();
+
+ TRACE_EVENT_ASYNC_STEP1(
+ "cc", "ScheduledTasks", this, StateName(),
+ "state", TracedValue::FromValue(StateAsValue().release()));
+}
+
+void PixelBufferRasterWorkerPool::CheckForCompletedTasks() {
+ TRACE_EVENT0("cc", "PixelBufferRasterWorkerPool::CheckForCompletedTasks");
+
+ RasterWorkerPool::CheckForCompletedTasks();
+ CheckForCompletedUploads();
+ FlushUploads();
+
+ TaskDeque completed_tasks;
+ completed_tasks_.swap(completed_tasks);
+
+ while (!completed_tasks.empty()) {
+ internal::RasterWorkerPoolTask* task = completed_tasks.front().get();
+ DCHECK(pixel_buffer_tasks_.find(task) != pixel_buffer_tasks_.end());
+
+ pixel_buffer_tasks_.erase(task);
+
+ task->WillComplete();
+ task->CompleteOnOriginThread();
+ task->DidComplete();
+
+ completed_tasks.pop_front();
+ }
+}
+
+void PixelBufferRasterWorkerPool::OnRasterTasksFinished() {
+ // |should_notify_client_if_no_tasks_are_pending_| can be set to false as
+ // a result of a scheduled CheckForCompletedRasterTasks() call. No need to
+ // perform another check in that case as we've already notified the client.
+ if (!should_notify_client_if_no_tasks_are_pending_)
+ return;
+
+ // Call CheckForCompletedRasterTasks() when we've finished running all
+ // raster tasks needed since last time ScheduleTasks() was called.
+ // This reduces latency between the time when all tasks have finished
+ // running and the time when the client is notified.
+ CheckForCompletedRasterTasks();
+}
+
+void PixelBufferRasterWorkerPool::OnRasterTasksRequiredForActivationFinished() {
+ // Analogous to OnRasterTasksFinished(), there's no need to call
+ // CheckForCompletedRasterTasks() if the client has already been notified.
+ if (!should_notify_client_if_no_tasks_required_for_activation_are_pending_)
+ return;
+
+ // This reduces latency between the time when all tasks required for
+ // activation have finished running and the time when the client is
+ // notified.
+ CheckForCompletedRasterTasks();
+}
+
+void PixelBufferRasterWorkerPool::FlushUploads() {
+ if (!has_performed_uploads_since_last_flush_)
+ return;
+
+ resource_provider()->ShallowFlushIfSupported();
+ has_performed_uploads_since_last_flush_ = false;
+}
+
+void PixelBufferRasterWorkerPool::CheckForCompletedUploads() {
+ TaskDeque tasks_with_completed_uploads;
+
+ // First check if any have completed.
+ while (!tasks_with_pending_upload_.empty()) {
+ internal::RasterWorkerPoolTask* task =
+ tasks_with_pending_upload_.front().get();
+
+ // Uploads complete in the order they are issued.
+ if (!resource_provider()->DidSetPixelsComplete(task->resource()->id()))
+ break;
+
+ tasks_with_completed_uploads.push_back(task);
+ tasks_with_pending_upload_.pop_front();
+ }
+
+ DCHECK(client());
+ bool should_force_some_uploads_to_complete =
+ shutdown_ || client()->ShouldForceTasksRequiredForActivationToComplete();
+
+ if (should_force_some_uploads_to_complete) {
+ TaskDeque tasks_with_uploads_to_force;
+ TaskDeque::iterator it = tasks_with_pending_upload_.begin();
+ while (it != tasks_with_pending_upload_.end()) {
+ internal::RasterWorkerPoolTask* task = it->get();
+ DCHECK(pixel_buffer_tasks_.find(task) != pixel_buffer_tasks_.end());
+
+ // Force all uploads required for activation to complete.
+ // During shutdown, force all pending uploads to complete.
+ if (shutdown_ || IsRasterTaskRequiredForActivation(task)) {
+ tasks_with_uploads_to_force.push_back(task);
+ tasks_with_completed_uploads.push_back(task);
+ it = tasks_with_pending_upload_.erase(it);
+ continue;
+ }
+
+ ++it;
+ }
+
+ // Force uploads in reverse order. Since forcing can cause a wait on
+ // all previous uploads, we would rather wait only once downstream.
+ for (TaskDeque::reverse_iterator it = tasks_with_uploads_to_force.rbegin();
+ it != tasks_with_uploads_to_force.rend();
+ ++it) {
+ resource_provider()->ForceSetPixelsToComplete((*it)->resource()->id());
+ has_performed_uploads_since_last_flush_ = true;
+ }
+ }
+
+ // Release shared memory and move tasks with completed uploads
+ // to |completed_tasks_|.
+ while (!tasks_with_completed_uploads.empty()) {
+ internal::RasterWorkerPoolTask* task =
+ tasks_with_completed_uploads.front().get();
+
+ // It's now safe to release the pixel buffer and the shared memory.
+ resource_provider()->ReleasePixelBuffer(task->resource()->id());
+
+ bytes_pending_upload_ -= task->resource()->bytes();
+
+ task->DidRun(false);
+
+ DCHECK(std::find(completed_tasks_.begin(),
+ completed_tasks_.end(),
+ task) == completed_tasks_.end());
+ completed_tasks_.push_back(task);
+
+ tasks_required_for_activation_.erase(task);
+
+ tasks_with_completed_uploads.pop_front();
+ }
+}
+
+void PixelBufferRasterWorkerPool::ScheduleCheckForCompletedRasterTasks() {
+ if (check_for_completed_raster_tasks_pending_)
+ return;
+
+ check_for_completed_raster_tasks_callback_.Reset(
+ base::Bind(&PixelBufferRasterWorkerPool::CheckForCompletedRasterTasks,
+ base::Unretained(this)));
+ base::MessageLoopProxy::current()->PostDelayedTask(
+ FROM_HERE,
+ check_for_completed_raster_tasks_callback_.callback(),
+ base::TimeDelta::FromMilliseconds(kCheckForCompletedRasterTasksDelayMs));
+ check_for_completed_raster_tasks_pending_ = true;
+}
+
+void PixelBufferRasterWorkerPool::CheckForCompletedRasterTasks() {
+ TRACE_EVENT0(
+ "cc", "PixelBufferRasterWorkerPool::CheckForCompletedRasterTasks");
+
+ DCHECK(should_notify_client_if_no_tasks_are_pending_);
+
+ check_for_completed_raster_tasks_callback_.Cancel();
+ check_for_completed_raster_tasks_pending_ = false;
+
+ RasterWorkerPool::CheckForCompletedTasks();
+ CheckForCompletedUploads();
+ FlushUploads();
+
+ // Determine what client notifications to generate.
+ bool will_notify_client_that_no_tasks_required_for_activation_are_pending =
+ (should_notify_client_if_no_tasks_required_for_activation_are_pending_ &&
+ !HasPendingTasksRequiredForActivation());
+ bool will_notify_client_that_no_tasks_are_pending =
+ (should_notify_client_if_no_tasks_are_pending_ &&
+ !HasPendingTasks());
+
+ // Adjust the need to generate notifications before scheduling more tasks.
+ should_notify_client_if_no_tasks_required_for_activation_are_pending_ &=
+ !will_notify_client_that_no_tasks_required_for_activation_are_pending;
+ should_notify_client_if_no_tasks_are_pending_ &=
+ !will_notify_client_that_no_tasks_are_pending;
+
+ scheduled_raster_task_count_ = 0;
+ if (PendingRasterTaskCount())
+ ScheduleMoreTasks();
+
+ TRACE_EVENT_ASYNC_STEP1(
+ "cc", "ScheduledTasks", this, StateName(),
+ "state", TracedValue::FromValue(StateAsValue().release()));
+
+ // Schedule another check for completed raster tasks while there are
+ // pending raster tasks or pending uploads.
+ if (HasPendingTasks())
+ ScheduleCheckForCompletedRasterTasks();
+
+ // Generate client notifications.
+ if (will_notify_client_that_no_tasks_required_for_activation_are_pending) {
+ DCHECK(std::find_if(raster_tasks_required_for_activation().begin(),
+ raster_tasks_required_for_activation().end(),
+ WasCanceled) ==
+ raster_tasks_required_for_activation().end());
+ client()->DidFinishRunningTasksRequiredForActivation();
+ }
+ if (will_notify_client_that_no_tasks_are_pending) {
+ TRACE_EVENT_ASYNC_END0("cc", "ScheduledTasks", this);
+ DCHECK(!HasPendingTasksRequiredForActivation());
+ client()->DidFinishRunningTasks();
+ }
+}
+
+void PixelBufferRasterWorkerPool::ScheduleMoreTasks() {
+ TRACE_EVENT0("cc", "PixelBufferRasterWorkerPool::ScheduleMoreTasks");
+
+ enum RasterTaskType {
+ PREPAINT_TYPE = 0,
+ REQUIRED_FOR_ACTIVATION_TYPE = 1,
+ NUM_TYPES = 2
+ };
+ NodeVector tasks[NUM_TYPES];
+ unsigned priority = 2u; // 0-1 reserved for RasterFinished tasks.
+ TaskGraph graph;
+
+ size_t bytes_pending_upload = bytes_pending_upload_;
+
+ for (RasterTaskVector::const_iterator it = raster_tasks().begin();
+ it != raster_tasks().end(); ++it) {
+ internal::RasterWorkerPoolTask* task = it->get();
+
+ // |pixel_buffer_tasks_| contains all tasks that have not yet completed.
+ TaskMap::iterator pixel_buffer_it = pixel_buffer_tasks_.find(task);
+ if (pixel_buffer_it == pixel_buffer_tasks_.end())
+ continue;
+
+ // HasFinishedRunning() will return true when set pixels has completed.
+ if (task->HasFinishedRunning()) {
+ DCHECK(std::find(completed_tasks_.begin(),
+ completed_tasks_.end(),
+ task) != completed_tasks_.end());
+ continue;
+ }
+
+ // All raster tasks need to be throttled by bytes of pending uploads.
+ size_t new_bytes_pending_upload = bytes_pending_upload;
+ new_bytes_pending_upload += task->resource()->bytes();
+ if (new_bytes_pending_upload > kMaxPendingUploadBytes)
+ break;
+
+ internal::WorkerPoolTask* pixel_buffer_task = pixel_buffer_it->second.get();
+
+ // If raster has finished, just update |bytes_pending_upload|.
+ if (pixel_buffer_task && pixel_buffer_task->HasCompleted()) {
+ bytes_pending_upload = new_bytes_pending_upload;
+ continue;
+ }
+
+ // Throttle raster tasks based on kMaxScheduledRasterTasks.
+ size_t scheduled_raster_task_count =
+ tasks[PREPAINT_TYPE].container().size() +
+ tasks[REQUIRED_FOR_ACTIVATION_TYPE].container().size();
+ if (scheduled_raster_task_count >= kMaxScheduledRasterTasks)
+ break;
+
+ // Update |bytes_pending_upload| now that task has cleared all
+ // throttling limits.
+ bytes_pending_upload = new_bytes_pending_upload;
+
+ RasterTaskType type = IsRasterTaskRequiredForActivation(task) ?
+ REQUIRED_FOR_ACTIVATION_TYPE :
+ PREPAINT_TYPE;
+
+ // Use existing pixel buffer task if available.
+ if (pixel_buffer_task) {
+ tasks[type].container().push_back(
+ CreateGraphNodeForRasterTask(pixel_buffer_task,
+ task->dependencies(),
+ priority++,
+ &graph));
+ continue;
+ }
+
+ // Request a pixel buffer. This will reserve shared memory.
+ resource_provider()->AcquirePixelBuffer(task->resource()->id());
+
+ // MapPixelBuffer() returns NULL if context was lost at the time
+ // AcquirePixelBuffer() was called. For simplicity we still post
+ // a raster task that is essentially a noop in these situations.
+ uint8* buffer = resource_provider()->MapPixelBuffer(
+ task->resource()->id());
+
+ scoped_refptr<internal::WorkerPoolTask> new_pixel_buffer_task(
+ new PixelBufferWorkerPoolTaskImpl(
+ task,
+ buffer,
+ base::Bind(&PixelBufferRasterWorkerPool::OnRasterTaskCompleted,
+ base::Unretained(this),
+ make_scoped_refptr(task))));
+ pixel_buffer_tasks_[task] = new_pixel_buffer_task;
+ tasks[type].container().push_back(
+ CreateGraphNodeForRasterTask(new_pixel_buffer_task.get(),
+ task->dependencies(),
+ priority++,
+ &graph));
+ }
+
+ scoped_refptr<internal::WorkerPoolTask>
+ new_raster_required_for_activation_finished_task;
+
+ size_t scheduled_raster_task_required_for_activation_count =
+ tasks[REQUIRED_FOR_ACTIVATION_TYPE].container().size();
+ DCHECK_LE(scheduled_raster_task_required_for_activation_count,
+ tasks_required_for_activation_.size());
+ // Schedule OnRasterTasksRequiredForActivationFinished call only when
+ // notification is pending and throttling is not preventing all pending
+ // tasks required for activation from being scheduled.
+ if (scheduled_raster_task_required_for_activation_count ==
+ tasks_required_for_activation_.size() &&
+ should_notify_client_if_no_tasks_required_for_activation_are_pending_) {
+ new_raster_required_for_activation_finished_task =
+ CreateRasterRequiredForActivationFinishedTask();
+ internal::GraphNode* raster_required_for_activation_finished_node =
+ CreateGraphNodeForTask(
+ new_raster_required_for_activation_finished_task.get(),
+ 0u, // Priority 0
+ &graph);
+ AddDependenciesToGraphNode(
+ raster_required_for_activation_finished_node,
+ tasks[REQUIRED_FOR_ACTIVATION_TYPE].container());
+ }
+
+ scoped_refptr<internal::WorkerPoolTask> new_raster_finished_task;
+
+ size_t scheduled_raster_task_count =
+ tasks[PREPAINT_TYPE].container().size() +
+ tasks[REQUIRED_FOR_ACTIVATION_TYPE].container().size();
+ DCHECK_LE(scheduled_raster_task_count, PendingRasterTaskCount());
+ // Schedule OnRasterTasksFinished call only when notification is pending
+ // and throttling is not preventing all pending tasks from being scheduled.
+ if (scheduled_raster_task_count == PendingRasterTaskCount() &&
+ should_notify_client_if_no_tasks_are_pending_) {
+ new_raster_finished_task = CreateRasterFinishedTask();
+ internal::GraphNode* raster_finished_node =
+ CreateGraphNodeForTask(new_raster_finished_task.get(),
+ 1u, // Priority 1
+ &graph);
+ for (unsigned type = 0; type < NUM_TYPES; ++type) {
+ AddDependenciesToGraphNode(
+ raster_finished_node,
+ tasks[type].container());
+ }
+ }
+
+ SetTaskGraph(&graph);
+
+ scheduled_raster_task_count_ = scheduled_raster_task_count;
+
+ set_raster_finished_task(new_raster_finished_task);
+ set_raster_required_for_activation_finished_task(
+ new_raster_required_for_activation_finished_task);
+}
+
+void PixelBufferRasterWorkerPool::OnRasterTaskCompleted(
+ scoped_refptr<internal::RasterWorkerPoolTask> task,
+ bool was_canceled,
+ bool needs_upload) {
+ DCHECK(pixel_buffer_tasks_.find(task.get()) != pixel_buffer_tasks_.end());
+
+ // Balanced with MapPixelBuffer() call in ScheduleMoreTasks().
+ resource_provider()->UnmapPixelBuffer(task->resource()->id());
+
+ if (!needs_upload) {
+ resource_provider()->ReleasePixelBuffer(task->resource()->id());
+
+ if (was_canceled) {
+ // When priorites change, a raster task can be canceled as a result of
+ // no longer being of high enough priority to fit in our throttled
+ // raster task budget. The task has not yet completed in this case.
+ RasterTaskVector::const_iterator it = std::find(raster_tasks().begin(),
+ raster_tasks().end(),
+ task);
+ if (it != raster_tasks().end()) {
+ pixel_buffer_tasks_[task.get()] = NULL;
+ return;
+ }
+ }
+
+ task->DidRun(was_canceled);
+ DCHECK(std::find(completed_tasks_.begin(),
+ completed_tasks_.end(),
+ task) == completed_tasks_.end());
+ completed_tasks_.push_back(task);
+ tasks_required_for_activation_.erase(task);
+ return;
+ }
+
+ DCHECK(!was_canceled);
+
+ resource_provider()->BeginSetPixels(task->resource()->id());
+ has_performed_uploads_since_last_flush_ = true;
+
+ bytes_pending_upload_ += task->resource()->bytes();
+ tasks_with_pending_upload_.push_back(task);
+}
+
+unsigned PixelBufferRasterWorkerPool::PendingRasterTaskCount() const {
+ unsigned num_completed_raster_tasks =
+ tasks_with_pending_upload_.size() + completed_tasks_.size();
+ DCHECK_GE(pixel_buffer_tasks_.size(), num_completed_raster_tasks);
+ return pixel_buffer_tasks_.size() - num_completed_raster_tasks;
+}
+
+bool PixelBufferRasterWorkerPool::HasPendingTasks() const {
+ return PendingRasterTaskCount() || !tasks_with_pending_upload_.empty();
+}
+
+bool PixelBufferRasterWorkerPool::HasPendingTasksRequiredForActivation() const {
+ return !tasks_required_for_activation_.empty();
+}
+
+const char* PixelBufferRasterWorkerPool::StateName() const {
+ if (scheduled_raster_task_count_)
+ return "rasterizing";
+ if (PendingRasterTaskCount())
+ return "throttled";
+ if (!tasks_with_pending_upload_.empty())
+ return "waiting_for_uploads";
+
+ return "finishing";
+}
+
+scoped_ptr<base::Value> PixelBufferRasterWorkerPool::StateAsValue() const {
+ scoped_ptr<base::DictionaryValue> state(new base::DictionaryValue);
+
+ state->SetInteger("completed_count", completed_tasks_.size());
+ state->SetInteger("pending_count", pixel_buffer_tasks_.size());
+ state->SetInteger("pending_upload_count", tasks_with_pending_upload_.size());
+ state->SetInteger("required_for_activation_count",
+ tasks_required_for_activation_.size());
+ state->Set("scheduled_state", ScheduledStateAsValue().release());
+ state->Set("throttle_state", ThrottleStateAsValue().release());
+ return state.PassAs<base::Value>();
+}
+
+scoped_ptr<base::Value> PixelBufferRasterWorkerPool::ThrottleStateAsValue()
+ const {
+ scoped_ptr<base::DictionaryValue> throttle_state(new base::DictionaryValue);
+
+ throttle_state->SetInteger("bytes_available_for_upload",
+ kMaxPendingUploadBytes - bytes_pending_upload_);
+ throttle_state->SetInteger("bytes_pending_upload", bytes_pending_upload_);
+ throttle_state->SetInteger("scheduled_raster_task_count",
+ scheduled_raster_task_count_);
+ return throttle_state.PassAs<base::Value>();
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/pixel_buffer_raster_worker_pool.h b/chromium/cc/resources/pixel_buffer_raster_worker_pool.h
new file mode 100644
index 00000000000..d9613f7634d
--- /dev/null
+++ b/chromium/cc/resources/pixel_buffer_raster_worker_pool.h
@@ -0,0 +1,82 @@
+// Copyright 2013 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.
+
+#ifndef CC_RESOURCES_PIXEL_BUFFER_RASTER_WORKER_POOL_H_
+#define CC_RESOURCES_PIXEL_BUFFER_RASTER_WORKER_POOL_H_
+
+#include <deque>
+#include <set>
+#include <vector>
+
+#include "cc/resources/raster_worker_pool.h"
+
+namespace cc {
+
+class CC_EXPORT PixelBufferRasterWorkerPool : public RasterWorkerPool {
+ public:
+ virtual ~PixelBufferRasterWorkerPool();
+
+ static scoped_ptr<RasterWorkerPool> Create(
+ ResourceProvider* resource_provider, size_t num_threads) {
+ return make_scoped_ptr<RasterWorkerPool>(
+ new PixelBufferRasterWorkerPool(resource_provider, num_threads));
+ }
+
+ // Overridden from WorkerPool:
+ virtual void Shutdown() OVERRIDE;
+ virtual void CheckForCompletedTasks() OVERRIDE;
+
+ // Overridden from RasterWorkerPool:
+ virtual void ScheduleTasks(RasterTask::Queue* queue) OVERRIDE;
+ virtual void OnRasterTasksFinished() OVERRIDE;
+ virtual void OnRasterTasksRequiredForActivationFinished() OVERRIDE;
+
+ private:
+ PixelBufferRasterWorkerPool(ResourceProvider* resource_provider,
+ size_t num_threads);
+
+ void FlushUploads();
+ void CheckForCompletedUploads();
+ void ScheduleCheckForCompletedRasterTasks();
+ void CheckForCompletedRasterTasks();
+ void ScheduleMoreTasks();
+ void OnRasterTaskCompleted(
+ scoped_refptr<internal::RasterWorkerPoolTask> task,
+ bool was_canceled,
+ bool needs_upload);
+ void DidCompleteRasterTask(internal::RasterWorkerPoolTask* task);
+ unsigned PendingRasterTaskCount() const;
+ bool HasPendingTasks() const;
+ bool HasPendingTasksRequiredForActivation() const;
+
+ const char* StateName() const;
+ scoped_ptr<base::Value> StateAsValue() const;
+ scoped_ptr<base::Value> ThrottleStateAsValue() const;
+
+ bool shutdown_;
+
+ TaskMap pixel_buffer_tasks_;
+
+ typedef std::deque<scoped_refptr<internal::RasterWorkerPoolTask> > TaskDeque;
+ TaskDeque tasks_with_pending_upload_;
+ TaskDeque completed_tasks_;
+
+ typedef std::set<internal::RasterWorkerPoolTask*> TaskSet;
+ TaskSet tasks_required_for_activation_;
+
+ size_t scheduled_raster_task_count_;
+ size_t bytes_pending_upload_;
+ bool has_performed_uploads_since_last_flush_;
+ base::CancelableClosure check_for_completed_raster_tasks_callback_;
+ bool check_for_completed_raster_tasks_pending_;
+
+ bool should_notify_client_if_no_tasks_are_pending_;
+ bool should_notify_client_if_no_tasks_required_for_activation_are_pending_;
+
+ DISALLOW_COPY_AND_ASSIGN(PixelBufferRasterWorkerPool);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_PIXEL_BUFFER_RASTER_WORKER_POOL_H_
diff --git a/chromium/cc/resources/platform_color.h b/chromium/cc/resources/platform_color.h
new file mode 100644
index 00000000000..09a606ae5a5
--- /dev/null
+++ b/chromium/cc/resources/platform_color.h
@@ -0,0 +1,64 @@
+// Copyright 2011 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.
+
+#ifndef CC_RESOURCES_PLATFORM_COLOR_H_
+#define CC_RESOURCES_PLATFORM_COLOR_H_
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "third_party/khronos/GLES2/gl2ext.h"
+#include "third_party/skia/include/core/SkTypes.h"
+
+namespace cc {
+
+class PlatformColor {
+ public:
+ enum SourceDataFormat {
+ SOURCE_FORMAT_RGBA8,
+ SOURCE_FORMAT_BGRA8
+ };
+
+ static SourceDataFormat Format() {
+ return SK_B32_SHIFT ? SOURCE_FORMAT_RGBA8 : SOURCE_FORMAT_BGRA8;
+ }
+
+ // Returns the most efficient texture format for this platform.
+ static GLenum BestTextureFormat(bool supports_bgra8888) {
+ GLenum texture_format = GL_RGBA;
+ switch (Format()) {
+ case SOURCE_FORMAT_RGBA8:
+ break;
+ case SOURCE_FORMAT_BGRA8:
+ if (supports_bgra8888)
+ texture_format = GL_BGRA_EXT;
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ return texture_format;
+ }
+
+ // Return true if the given texture format has the same component order
+ // as the color on this platform.
+ static bool SameComponentOrder(GLenum texture_format) {
+ switch (Format()) {
+ case SOURCE_FORMAT_RGBA8:
+ return texture_format == GL_RGBA;
+ case SOURCE_FORMAT_BGRA8:
+ return texture_format == GL_BGRA_EXT;
+ default:
+ NOTREACHED();
+ return false;
+ }
+ }
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(PlatformColor);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_PLATFORM_COLOR_H_
diff --git a/chromium/cc/resources/prioritized_resource.cc b/chromium/cc/resources/prioritized_resource.cc
new file mode 100644
index 00000000000..78fd90055a9
--- /dev/null
+++ b/chromium/cc/resources/prioritized_resource.cc
@@ -0,0 +1,201 @@
+// 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 "cc/resources/prioritized_resource.h"
+
+#include <algorithm>
+
+#include "cc/resources/platform_color.h"
+#include "cc/resources/prioritized_resource_manager.h"
+#include "cc/resources/priority_calculator.h"
+#include "cc/trees/proxy.h"
+
+namespace cc {
+
+PrioritizedResource::PrioritizedResource(PrioritizedResourceManager* manager,
+ gfx::Size size,
+ GLenum format)
+ : size_(size),
+ format_(format),
+ bytes_(0),
+ contents_swizzled_(false),
+ priority_(PriorityCalculator::LowestPriority()),
+ is_above_priority_cutoff_(false),
+ is_self_managed_(false),
+ backing_(NULL),
+ manager_(NULL) {
+ // manager_ is set in RegisterTexture() so validity can be checked.
+ DCHECK(format || size.IsEmpty());
+ if (format)
+ bytes_ = Resource::MemorySizeBytes(size, format);
+ if (manager)
+ manager->RegisterTexture(this);
+}
+
+PrioritizedResource::~PrioritizedResource() {
+ if (manager_)
+ manager_->UnregisterTexture(this);
+}
+
+void PrioritizedResource::SetTextureManager(
+ PrioritizedResourceManager* manager) {
+ if (manager_ == manager)
+ return;
+ if (manager_)
+ manager_->UnregisterTexture(this);
+ if (manager)
+ manager->RegisterTexture(this);
+}
+
+void PrioritizedResource::SetDimensions(gfx::Size size, GLenum format) {
+ if (format_ != format || size_ != size) {
+ is_above_priority_cutoff_ = false;
+ format_ = format;
+ size_ = size;
+ bytes_ = Resource::MemorySizeBytes(size, format);
+ DCHECK(manager_ || !backing_);
+ if (manager_)
+ manager_->ReturnBackingTexture(this);
+ }
+}
+
+bool PrioritizedResource::RequestLate() {
+ if (!manager_)
+ return false;
+ return manager_->RequestLate(this);
+}
+
+bool PrioritizedResource::BackingResourceWasEvicted() const {
+ return backing_ ? backing_->ResourceHasBeenDeleted() : false;
+}
+
+void PrioritizedResource::AcquireBackingTexture(
+ ResourceProvider* resource_provider) {
+ DCHECK(is_above_priority_cutoff_);
+ if (is_above_priority_cutoff_)
+ manager_->AcquireBackingTextureIfNeeded(this, resource_provider);
+}
+
+void PrioritizedResource::SetPixels(ResourceProvider* resource_provider,
+ const uint8_t* image,
+ gfx::Rect image_rect,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset) {
+ DCHECK(is_above_priority_cutoff_);
+ if (is_above_priority_cutoff_)
+ AcquireBackingTexture(resource_provider);
+ DCHECK(backing_);
+ resource_provider->SetPixels(
+ resource_id(), image, image_rect, source_rect, dest_offset);
+
+ // The component order may be bgra if we uploaded bgra pixels to rgba
+ // texture. Mark contents as swizzled if image component order is
+ // different than texture format.
+ contents_swizzled_ = !PlatformColor::SameComponentOrder(format_);
+}
+
+void PrioritizedResource::Link(Backing* backing) {
+ DCHECK(backing);
+ DCHECK(!backing->owner_);
+ DCHECK(!backing_);
+
+ backing_ = backing;
+ backing_->owner_ = this;
+}
+
+void PrioritizedResource::Unlink() {
+ DCHECK(backing_);
+ DCHECK(backing_->owner_ == this);
+
+ backing_->owner_ = NULL;
+ backing_ = NULL;
+}
+
+void PrioritizedResource::SetToSelfManagedMemoryPlaceholder(size_t bytes) {
+ SetDimensions(gfx::Size(), GL_RGBA);
+ set_is_self_managed(true);
+ bytes_ = bytes;
+}
+
+PrioritizedResource::Backing::Backing(unsigned id,
+ ResourceProvider* resource_provider,
+ gfx::Size size,
+ GLenum format)
+ : Resource(id, size, format),
+ owner_(NULL),
+ priority_at_last_priority_update_(PriorityCalculator::LowestPriority()),
+ was_above_priority_cutoff_at_last_priority_update_(false),
+ in_drawing_impl_tree_(false),
+#ifdef NDEBUG
+ resource_has_been_deleted_(false) {}
+#else
+ resource_has_been_deleted_(false),
+ resource_provider_(resource_provider) {}
+#endif
+
+PrioritizedResource::Backing::~Backing() {
+ DCHECK(!owner_);
+ DCHECK(resource_has_been_deleted_);
+}
+
+void PrioritizedResource::Backing::DeleteResource(
+ ResourceProvider* resource_provider) {
+ DCHECK(!proxy() || proxy()->IsImplThread());
+ DCHECK(!resource_has_been_deleted_);
+#ifndef NDEBUG
+ DCHECK(resource_provider == resource_provider_);
+#endif
+
+ resource_provider->DeleteResource(id());
+ set_id(0);
+ resource_has_been_deleted_ = true;
+}
+
+bool PrioritizedResource::Backing::ResourceHasBeenDeleted() const {
+ DCHECK(!proxy() || proxy()->IsImplThread());
+ return resource_has_been_deleted_;
+}
+
+bool PrioritizedResource::Backing::CanBeRecycled() const {
+ DCHECK(!proxy() || proxy()->IsImplThread());
+ return !was_above_priority_cutoff_at_last_priority_update_ &&
+ !in_drawing_impl_tree_;
+}
+
+void PrioritizedResource::Backing::UpdatePriority() {
+ DCHECK(!proxy() ||
+ (proxy()->IsImplThread() && proxy()->IsMainThreadBlocked()));
+ if (owner_) {
+ priority_at_last_priority_update_ = owner_->request_priority();
+ was_above_priority_cutoff_at_last_priority_update_ =
+ owner_->is_above_priority_cutoff();
+ } else {
+ priority_at_last_priority_update_ = PriorityCalculator::LowestPriority();
+ was_above_priority_cutoff_at_last_priority_update_ = false;
+ }
+}
+
+void PrioritizedResource::Backing::UpdateInDrawingImplTree() {
+ DCHECK(!proxy() ||
+ (proxy()->IsImplThread() && proxy()->IsMainThreadBlocked()));
+ in_drawing_impl_tree_ = !!owner();
+ if (!in_drawing_impl_tree_) {
+ DCHECK_EQ(priority_at_last_priority_update_,
+ PriorityCalculator::LowestPriority());
+ }
+}
+
+void PrioritizedResource::ReturnBackingTexture() {
+ DCHECK(manager_ || !backing_);
+ if (manager_)
+ manager_->ReturnBackingTexture(this);
+}
+
+const Proxy* PrioritizedResource::Backing::proxy() const {
+ if (!owner_ || !owner_->resource_manager())
+ return NULL;
+ return owner_->resource_manager()->ProxyForDebug();
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/prioritized_resource.h b/chromium/cc/resources/prioritized_resource.h
new file mode 100644
index 00000000000..07e07872205
--- /dev/null
+++ b/chromium/cc/resources/prioritized_resource.h
@@ -0,0 +1,180 @@
+// 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.
+
+#ifndef CC_RESOURCES_PRIORITIZED_RESOURCE_H_
+#define CC_RESOURCES_PRIORITIZED_RESOURCE_H_
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/resources/priority_calculator.h"
+#include "cc/resources/resource.h"
+#include "cc/resources/resource_provider.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size.h"
+#include "ui/gfx/vector2d.h"
+
+namespace cc {
+
+class PrioritizedResourceManager;
+class Proxy;
+
+class CC_EXPORT PrioritizedResource {
+ public:
+ static scoped_ptr<PrioritizedResource>
+ Create(PrioritizedResourceManager* manager, gfx::Size size, GLenum format) {
+ return make_scoped_ptr(new PrioritizedResource(manager, size, format));
+ }
+ static scoped_ptr<PrioritizedResource> Create(
+ PrioritizedResourceManager* manager) {
+ return make_scoped_ptr(new PrioritizedResource(manager, gfx::Size(), 0));
+ }
+ ~PrioritizedResource();
+
+ // Texture properties. Changing these causes the backing texture to be lost.
+ // Setting these to the same value is a no-op.
+ void SetTextureManager(PrioritizedResourceManager* manager);
+ PrioritizedResourceManager* resource_manager() { return manager_; }
+ void SetDimensions(gfx::Size size, GLenum format);
+ GLenum format() const { return format_; }
+ gfx::Size size() const { return size_; }
+ size_t bytes() const { return bytes_; }
+ bool contents_swizzled() const { return contents_swizzled_; }
+
+ // Set priority for the requested texture.
+ void set_request_priority(int priority) { priority_ = priority; }
+ int request_priority() const { return priority_; }
+
+ // After PrioritizedResource::PrioritizeTextures() is called, this returns
+ // if the the request succeeded and this texture can be acquired for use.
+ bool can_acquire_backing_texture() const { return is_above_priority_cutoff_; }
+
+ // This returns whether we still have a backing texture. This can continue
+ // to be true even after CanAcquireBackingTexture() becomes false. In this
+ // case the texture can be used but shouldn't be updated since it will get
+ // taken away "soon".
+ bool have_backing_texture() const { return !!backing(); }
+
+ bool BackingResourceWasEvicted() const;
+
+ // If CanAcquireBackingTexture() is true AcquireBackingTexture() will acquire
+ // a backing texture for use. Call this whenever the texture is actually
+ // needed.
+ void AcquireBackingTexture(ResourceProvider* resource_provider);
+
+ // TODO(epenner): Request late is really a hack for when we are totally out of
+ // memory (all textures are visible) but we can still squeeze into the limit
+ // by not painting occluded textures. In this case the manager refuses all
+ // visible textures and RequestLate() will enable CanAcquireBackingTexture()
+ // on a call-order basis. We might want to just remove this in the future
+ // (carefully) and just make sure we don't regress OOMs situations.
+ bool RequestLate();
+
+ // Update pixels of backing resource from image. This functions will aquire
+ // the backing if needed.
+ void SetPixels(ResourceProvider* resource_provider,
+ const uint8_t* image,
+ gfx::Rect image_rect,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset);
+
+ ResourceProvider::ResourceId resource_id() const {
+ return backing_ ? backing_->id() : 0;
+ }
+
+ // Self-managed textures are accounted for when prioritizing other textures,
+ // but they are not allocated/recycled/deleted, so this needs to be done
+ // externally. CanAcquireBackingTexture() indicates if the texture would have
+ // been allowed given its priority.
+ void set_is_self_managed(bool is_self_managed) {
+ is_self_managed_ = is_self_managed;
+ }
+ bool is_self_managed() { return is_self_managed_; }
+ void SetToSelfManagedMemoryPlaceholder(size_t bytes);
+
+ void ReturnBackingTexture();
+
+ private:
+ friend class PrioritizedResourceManager;
+ friend class PrioritizedResourceTest;
+
+ class Backing : public Resource {
+ public:
+ Backing(unsigned id,
+ ResourceProvider* resource_provider,
+ gfx::Size size,
+ GLenum format);
+ ~Backing();
+ void UpdatePriority();
+ void UpdateInDrawingImplTree();
+
+ PrioritizedResource* owner() { return owner_; }
+ bool CanBeRecycled() const;
+ int request_priority_at_last_priority_update() const {
+ return priority_at_last_priority_update_;
+ }
+ bool was_above_priority_cutoff_at_last_priority_update() const {
+ return was_above_priority_cutoff_at_last_priority_update_;
+ }
+ bool in_drawing_impl_tree() const { return in_drawing_impl_tree_; }
+
+ void DeleteResource(ResourceProvider* resource_provider);
+ bool ResourceHasBeenDeleted() const;
+
+ private:
+ const Proxy* proxy() const;
+
+ friend class PrioritizedResource;
+ friend class PrioritizedResourceManager;
+ PrioritizedResource* owner_;
+ int priority_at_last_priority_update_;
+ bool was_above_priority_cutoff_at_last_priority_update_;
+
+ // Set if this is currently-drawing impl tree.
+ bool in_drawing_impl_tree_;
+
+ bool resource_has_been_deleted_;
+
+#ifndef NDEBUG
+ ResourceProvider* resource_provider_;
+#endif
+ DISALLOW_COPY_AND_ASSIGN(Backing);
+ };
+
+ PrioritizedResource(PrioritizedResourceManager* resource_manager,
+ gfx::Size size,
+ GLenum format);
+
+ bool is_above_priority_cutoff() { return is_above_priority_cutoff_; }
+ void set_above_priority_cutoff(bool is_above_priority_cutoff) {
+ is_above_priority_cutoff_ = is_above_priority_cutoff;
+ }
+ void set_manager_internal(PrioritizedResourceManager* manager) {
+ manager_ = manager;
+ }
+
+ Backing* backing() const { return backing_; }
+ void Link(Backing* backing);
+ void Unlink();
+
+ gfx::Size size_;
+ GLenum format_;
+ size_t bytes_;
+ bool contents_swizzled_;
+
+ int priority_;
+ bool is_above_priority_cutoff_;
+ bool is_self_managed_;
+
+ Backing* backing_;
+ PrioritizedResourceManager* manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrioritizedResource);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_PRIORITIZED_RESOURCE_H_
diff --git a/chromium/cc/resources/prioritized_resource_manager.cc b/chromium/cc/resources/prioritized_resource_manager.cc
new file mode 100644
index 00000000000..81188851901
--- /dev/null
+++ b/chromium/cc/resources/prioritized_resource_manager.cc
@@ -0,0 +1,550 @@
+// 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 "cc/resources/prioritized_resource_manager.h"
+
+#include <algorithm>
+
+#include "base/debug/trace_event.h"
+#include "base/stl_util.h"
+#include "cc/resources/prioritized_resource.h"
+#include "cc/resources/priority_calculator.h"
+#include "cc/trees/proxy.h"
+
+namespace cc {
+
+PrioritizedResourceManager::PrioritizedResourceManager(const Proxy* proxy)
+ : max_memory_limit_bytes_(DefaultMemoryAllocationLimit()),
+ external_priority_cutoff_(PriorityCalculator::AllowEverythingCutoff()),
+ memory_use_bytes_(0),
+ memory_above_cutoff_bytes_(0),
+ max_memory_needed_bytes_(0),
+ memory_available_bytes_(0),
+ proxy_(proxy),
+ backings_tail_not_sorted_(false),
+ memory_visible_bytes_(0),
+ memory_visible_and_nearby_bytes_(0),
+ memory_visible_last_pushed_bytes_(0),
+ memory_visible_and_nearby_last_pushed_bytes_(0) {}
+
+PrioritizedResourceManager::~PrioritizedResourceManager() {
+ while (textures_.size() > 0)
+ UnregisterTexture(*textures_.begin());
+
+ UnlinkAndClearEvictedBackings();
+ DCHECK(evicted_backings_.empty());
+
+ // Each remaining backing is a leaked opengl texture. There should be none.
+ DCHECK(backings_.empty());
+}
+
+size_t PrioritizedResourceManager::MemoryVisibleBytes() const {
+ DCHECK(proxy_->IsImplThread());
+ return memory_visible_last_pushed_bytes_;
+}
+
+size_t PrioritizedResourceManager::MemoryVisibleAndNearbyBytes() const {
+ DCHECK(proxy_->IsImplThread());
+ return memory_visible_and_nearby_last_pushed_bytes_;
+}
+
+void PrioritizedResourceManager::PrioritizeTextures() {
+ TRACE_EVENT0("cc", "PrioritizedResourceManager::PrioritizeTextures");
+ DCHECK(proxy_->IsMainThread());
+
+ // Sorting textures in this function could be replaced by a slightly
+ // modified O(n) quick-select to partition textures rather than
+ // sort them (if performance of the sort becomes an issue).
+
+ TextureVector& sorted_textures = temp_texture_vector_;
+ sorted_textures.clear();
+
+ // Copy all textures into a vector, sort them, and collect memory requirements
+ // statistics.
+ memory_visible_bytes_ = 0;
+ memory_visible_and_nearby_bytes_ = 0;
+ for (TextureSet::iterator it = textures_.begin(); it != textures_.end();
+ ++it) {
+ PrioritizedResource* texture = (*it);
+ sorted_textures.push_back(texture);
+ if (PriorityCalculator::priority_is_higher(
+ texture->request_priority(),
+ PriorityCalculator::AllowVisibleOnlyCutoff()))
+ memory_visible_bytes_ += texture->bytes();
+ if (PriorityCalculator::priority_is_higher(
+ texture->request_priority(),
+ PriorityCalculator::AllowVisibleAndNearbyCutoff()))
+ memory_visible_and_nearby_bytes_ += texture->bytes();
+ }
+ std::sort(sorted_textures.begin(), sorted_textures.end(), CompareTextures);
+
+ // Compute a priority cutoff based on memory pressure
+ memory_available_bytes_ = max_memory_limit_bytes_;
+ priority_cutoff_ = external_priority_cutoff_;
+ size_t memory_bytes = 0;
+ for (TextureVector::iterator it = sorted_textures.begin();
+ it != sorted_textures.end();
+ ++it) {
+ if ((*it)->is_self_managed()) {
+ // Account for self-managed memory immediately by reducing the memory
+ // available (since it never gets acquired).
+ size_t new_memory_bytes = memory_bytes + (*it)->bytes();
+ if (new_memory_bytes > memory_available_bytes_) {
+ priority_cutoff_ = (*it)->request_priority();
+ memory_available_bytes_ = memory_bytes;
+ break;
+ }
+ memory_available_bytes_ -= (*it)->bytes();
+ } else {
+ size_t new_memory_bytes = memory_bytes + (*it)->bytes();
+ if (new_memory_bytes > memory_available_bytes_) {
+ priority_cutoff_ = (*it)->request_priority();
+ break;
+ }
+ memory_bytes = new_memory_bytes;
+ }
+ }
+
+ // Disallow any textures with priority below the external cutoff to have
+ // backings.
+ for (TextureVector::iterator it = sorted_textures.begin();
+ it != sorted_textures.end();
+ ++it) {
+ PrioritizedResource* texture = (*it);
+ if (!PriorityCalculator::priority_is_higher(texture->request_priority(),
+ external_priority_cutoff_) &&
+ texture->have_backing_texture())
+ texture->Unlink();
+ }
+
+ // Only allow textures if they are higher than the cutoff. All textures
+ // of the same priority are accepted or rejected together, rather than
+ // being partially allowed randomly.
+ max_memory_needed_bytes_ = 0;
+ memory_above_cutoff_bytes_ = 0;
+ for (TextureVector::iterator it = sorted_textures.begin();
+ it != sorted_textures.end();
+ ++it) {
+ PrioritizedResource* resource = *it;
+ bool is_above_priority_cutoff = PriorityCalculator::priority_is_higher(
+ resource->request_priority(), priority_cutoff_);
+ resource->set_above_priority_cutoff(is_above_priority_cutoff);
+ if (!resource->is_self_managed()) {
+ max_memory_needed_bytes_ += resource->bytes();
+ if (is_above_priority_cutoff)
+ memory_above_cutoff_bytes_ += resource->bytes();
+ }
+ }
+ sorted_textures.clear();
+
+ DCHECK_LE(memory_above_cutoff_bytes_, memory_available_bytes_);
+ DCHECK_LE(MemoryAboveCutoffBytes(), MaxMemoryLimitBytes());
+}
+
+void PrioritizedResourceManager::PushTexturePrioritiesToBackings() {
+ TRACE_EVENT0("cc",
+ "PrioritizedResourceManager::PushTexturePrioritiesToBackings");
+ DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked());
+
+ AssertInvariants();
+ for (BackingList::iterator it = backings_.begin(); it != backings_.end();
+ ++it)
+ (*it)->UpdatePriority();
+ SortBackings();
+ AssertInvariants();
+
+ // Push memory requirements to the impl thread structure.
+ memory_visible_last_pushed_bytes_ = memory_visible_bytes_;
+ memory_visible_and_nearby_last_pushed_bytes_ =
+ memory_visible_and_nearby_bytes_;
+}
+
+void PrioritizedResourceManager::UpdateBackingsInDrawingImplTree() {
+ TRACE_EVENT0("cc",
+ "PrioritizedResourceManager::UpdateBackingsInDrawingImplTree");
+ DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked());
+
+ AssertInvariants();
+ for (BackingList::iterator it = backings_.begin(); it != backings_.end();
+ ++it) {
+ PrioritizedResource::Backing* backing = (*it);
+ backing->UpdateInDrawingImplTree();
+ }
+ SortBackings();
+ AssertInvariants();
+}
+
+void PrioritizedResourceManager::SortBackings() {
+ TRACE_EVENT0("cc", "PrioritizedResourceManager::SortBackings");
+ DCHECK(proxy_->IsImplThread());
+
+ // Put backings in eviction/recycling order.
+ backings_.sort(CompareBackings);
+ backings_tail_not_sorted_ = false;
+}
+
+void PrioritizedResourceManager::ClearPriorities() {
+ DCHECK(proxy_->IsMainThread());
+ for (TextureSet::iterator it = textures_.begin(); it != textures_.end();
+ ++it) {
+ // TODO(reveman): We should remove this and just set all priorities to
+ // PriorityCalculator::lowestPriority() once we have priorities for all
+ // textures (we can't currently calculate distances for off-screen
+ // textures).
+ (*it)->set_request_priority(
+ PriorityCalculator::LingeringPriority((*it)->request_priority()));
+ }
+}
+
+bool PrioritizedResourceManager::RequestLate(PrioritizedResource* texture) {
+ DCHECK(proxy_->IsMainThread());
+
+ // This is already above cutoff, so don't double count it's memory below.
+ if (texture->is_above_priority_cutoff())
+ return true;
+
+ // Allow textures that have priority equal to the cutoff, but not strictly
+ // lower.
+ if (PriorityCalculator::priority_is_lower(texture->request_priority(),
+ priority_cutoff_))
+ return false;
+
+ // Disallow textures that do not have a priority strictly higher than the
+ // external cutoff.
+ if (!PriorityCalculator::priority_is_higher(texture->request_priority(),
+ external_priority_cutoff_))
+ return false;
+
+ size_t new_memory_bytes = memory_above_cutoff_bytes_ + texture->bytes();
+ if (new_memory_bytes > memory_available_bytes_)
+ return false;
+
+ memory_above_cutoff_bytes_ = new_memory_bytes;
+ texture->set_above_priority_cutoff(true);
+ return true;
+}
+
+void PrioritizedResourceManager::AcquireBackingTextureIfNeeded(
+ PrioritizedResource* texture,
+ ResourceProvider* resource_provider) {
+ DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked());
+ DCHECK(!texture->is_self_managed());
+ DCHECK(texture->is_above_priority_cutoff());
+ if (texture->backing() || !texture->is_above_priority_cutoff())
+ return;
+
+ // Find a backing below, by either recycling or allocating.
+ PrioritizedResource::Backing* backing = NULL;
+
+ // First try to recycle
+ for (BackingList::iterator it = backings_.begin(); it != backings_.end();
+ ++it) {
+ if (!(*it)->CanBeRecycled())
+ break;
+ if (resource_provider->InUseByConsumer((*it)->id()))
+ continue;
+ if ((*it)->size() == texture->size() &&
+ (*it)->format() == texture->format()) {
+ backing = (*it);
+ backings_.erase(it);
+ break;
+ }
+ }
+
+ // Otherwise reduce memory and just allocate a new backing texures.
+ if (!backing) {
+ EvictBackingsToReduceMemory(memory_available_bytes_ - texture->bytes(),
+ PriorityCalculator::AllowEverythingCutoff(),
+ EVICT_ONLY_RECYCLABLE,
+ DO_NOT_UNLINK_BACKINGS,
+ resource_provider);
+ backing =
+ CreateBacking(texture->size(), texture->format(), resource_provider);
+ }
+
+ // Move the used backing to the end of the eviction list, and note that
+ // the tail is not sorted.
+ if (backing->owner())
+ backing->owner()->Unlink();
+ texture->Link(backing);
+ backings_.push_back(backing);
+ backings_tail_not_sorted_ = true;
+
+ // Update the backing's priority from its new owner.
+ backing->UpdatePriority();
+}
+
+bool PrioritizedResourceManager::EvictBackingsToReduceMemory(
+ size_t limit_bytes,
+ int priority_cutoff,
+ EvictionPolicy eviction_policy,
+ UnlinkPolicy unlink_policy,
+ ResourceProvider* resource_provider) {
+ DCHECK(proxy_->IsImplThread());
+ if (unlink_policy == UNLINK_BACKINGS)
+ DCHECK(proxy_->IsMainThreadBlocked());
+ if (MemoryUseBytes() <= limit_bytes &&
+ PriorityCalculator::AllowEverythingCutoff() == priority_cutoff)
+ return false;
+
+ // Destroy backings until we are below the limit,
+ // or until all backings remaining are above the cutoff.
+ bool evicted_anything = false;
+ while (backings_.size() > 0) {
+ PrioritizedResource::Backing* backing = backings_.front();
+ if (MemoryUseBytes() <= limit_bytes &&
+ PriorityCalculator::priority_is_higher(
+ backing->request_priority_at_last_priority_update(),
+ priority_cutoff))
+ break;
+ if (eviction_policy == EVICT_ONLY_RECYCLABLE && !backing->CanBeRecycled())
+ break;
+ if (unlink_policy == UNLINK_BACKINGS && backing->owner())
+ backing->owner()->Unlink();
+ EvictFirstBackingResource(resource_provider);
+ evicted_anything = true;
+ }
+ return evicted_anything;
+}
+
+void PrioritizedResourceManager::ReduceWastedMemory(
+ ResourceProvider* resource_provider) {
+ // We currently collect backings from deleted textures for later recycling.
+ // However, if we do that forever we will always use the max limit even if
+ // we really need very little memory. This should probably be solved by
+ // reducing the limit externally, but until then this just does some "clean
+ // up" of unused backing textures (any more than 10%).
+ size_t wasted_memory = 0;
+ for (BackingList::iterator it = backings_.begin(); it != backings_.end();
+ ++it) {
+ if ((*it)->owner())
+ break;
+ wasted_memory += (*it)->bytes();
+ }
+ size_t ten_percent_of_memory = memory_available_bytes_ / 10;
+ if (wasted_memory > ten_percent_of_memory)
+ EvictBackingsToReduceMemory(MemoryUseBytes() -
+ (wasted_memory - ten_percent_of_memory),
+ PriorityCalculator::AllowEverythingCutoff(),
+ EVICT_ONLY_RECYCLABLE,
+ DO_NOT_UNLINK_BACKINGS,
+ resource_provider);
+}
+
+void PrioritizedResourceManager::ReduceMemory(
+ ResourceProvider* resource_provider) {
+ DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked());
+ EvictBackingsToReduceMemory(memory_available_bytes_,
+ PriorityCalculator::AllowEverythingCutoff(),
+ EVICT_ANYTHING,
+ UNLINK_BACKINGS,
+ resource_provider);
+ DCHECK_LE(MemoryUseBytes(), memory_available_bytes_);
+
+ ReduceWastedMemory(resource_provider);
+}
+
+void PrioritizedResourceManager::ClearAllMemory(
+ ResourceProvider* resource_provider) {
+ DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked());
+ if (!resource_provider) {
+ DCHECK(backings_.empty());
+ return;
+ }
+ EvictBackingsToReduceMemory(0,
+ PriorityCalculator::AllowEverythingCutoff(),
+ EVICT_ANYTHING,
+ DO_NOT_UNLINK_BACKINGS,
+ resource_provider);
+}
+
+bool PrioritizedResourceManager::ReduceMemoryOnImplThread(
+ size_t limit_bytes,
+ int priority_cutoff,
+ ResourceProvider* resource_provider) {
+ DCHECK(proxy_->IsImplThread());
+ DCHECK(resource_provider);
+ // If we are in the process of uploading a new frame then the backings at the
+ // very end of the list are not sorted by priority. Sort them before doing the
+ // eviction.
+ if (backings_tail_not_sorted_)
+ SortBackings();
+ return EvictBackingsToReduceMemory(limit_bytes,
+ priority_cutoff,
+ EVICT_ANYTHING,
+ DO_NOT_UNLINK_BACKINGS,
+ resource_provider);
+}
+
+void PrioritizedResourceManager::ReduceWastedMemoryOnImplThread(
+ ResourceProvider* resource_provider) {
+ DCHECK(proxy_->IsImplThread());
+ DCHECK(resource_provider);
+ // If we are in the process of uploading a new frame then the backings at the
+ // very end of the list are not sorted by priority. Sort them before doing the
+ // eviction.
+ if (backings_tail_not_sorted_)
+ SortBackings();
+ ReduceWastedMemory(resource_provider);
+}
+
+void PrioritizedResourceManager::UnlinkAndClearEvictedBackings() {
+ DCHECK(proxy_->IsMainThread());
+ base::AutoLock scoped_lock(evicted_backings_lock_);
+ for (BackingList::const_iterator it = evicted_backings_.begin();
+ it != evicted_backings_.end();
+ ++it) {
+ PrioritizedResource::Backing* backing = (*it);
+ if (backing->owner())
+ backing->owner()->Unlink();
+ delete backing;
+ }
+ evicted_backings_.clear();
+}
+
+bool PrioritizedResourceManager::LinkedEvictedBackingsExist() const {
+ DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked());
+ base::AutoLock scoped_lock(evicted_backings_lock_);
+ for (BackingList::const_iterator it = evicted_backings_.begin();
+ it != evicted_backings_.end();
+ ++it) {
+ if ((*it)->owner())
+ return true;
+ }
+ return false;
+}
+
+void PrioritizedResourceManager::RegisterTexture(PrioritizedResource* texture) {
+ DCHECK(proxy_->IsMainThread());
+ DCHECK(texture);
+ DCHECK(!texture->resource_manager());
+ DCHECK(!texture->backing());
+ DCHECK(!ContainsKey(textures_, texture));
+
+ texture->set_manager_internal(this);
+ textures_.insert(texture);
+}
+
+void PrioritizedResourceManager::UnregisterTexture(
+ PrioritizedResource* texture) {
+ DCHECK(proxy_->IsMainThread() ||
+ (proxy_->IsImplThread() && proxy_->IsMainThreadBlocked()));
+ DCHECK(texture);
+ DCHECK(ContainsKey(textures_, texture));
+
+ ReturnBackingTexture(texture);
+ texture->set_manager_internal(NULL);
+ textures_.erase(texture);
+ texture->set_above_priority_cutoff(false);
+}
+
+void PrioritizedResourceManager::ReturnBackingTexture(
+ PrioritizedResource* texture) {
+ DCHECK(proxy_->IsMainThread() ||
+ (proxy_->IsImplThread() && proxy_->IsMainThreadBlocked()));
+ if (texture->backing())
+ texture->Unlink();
+}
+
+PrioritizedResource::Backing* PrioritizedResourceManager::CreateBacking(
+ gfx::Size size,
+ GLenum format,
+ ResourceProvider* resource_provider) {
+ DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked());
+ DCHECK(resource_provider);
+ ResourceProvider::ResourceId resource_id =
+ resource_provider->CreateManagedResource(
+ size, format, ResourceProvider::TextureUsageAny);
+ PrioritizedResource::Backing* backing = new PrioritizedResource::Backing(
+ resource_id, resource_provider, size, format);
+ memory_use_bytes_ += backing->bytes();
+ return backing;
+}
+
+void PrioritizedResourceManager::EvictFirstBackingResource(
+ ResourceProvider* resource_provider) {
+ DCHECK(proxy_->IsImplThread());
+ DCHECK(resource_provider);
+ DCHECK(!backings_.empty());
+ PrioritizedResource::Backing* backing = backings_.front();
+
+ // Note that we create a backing and its resource at the same time, but we
+ // delete the backing structure and its resource in two steps. This is because
+ // we can delete the resource while the main thread is running, but we cannot
+ // unlink backings while the main thread is running.
+ backing->DeleteResource(resource_provider);
+ memory_use_bytes_ -= backing->bytes();
+ backings_.pop_front();
+ base::AutoLock scoped_lock(evicted_backings_lock_);
+ evicted_backings_.push_back(backing);
+}
+
+void PrioritizedResourceManager::AssertInvariants() {
+#ifndef NDEBUG
+ DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked());
+
+ // If we hit any of these asserts, there is a bug in this class. To see
+ // where the bug is, call this function at the beginning and end of
+ // every public function.
+
+ // Backings/textures must be doubly-linked and only to other backings/textures
+ // in this manager.
+ for (BackingList::iterator it = backings_.begin(); it != backings_.end();
+ ++it) {
+ if ((*it)->owner()) {
+ DCHECK(ContainsKey(textures_, (*it)->owner()));
+ DCHECK((*it)->owner()->backing() == (*it));
+ }
+ }
+ for (TextureSet::iterator it = textures_.begin(); it != textures_.end();
+ ++it) {
+ PrioritizedResource* texture = (*it);
+ PrioritizedResource::Backing* backing = texture->backing();
+ base::AutoLock scoped_lock(evicted_backings_lock_);
+ if (backing) {
+ if (backing->ResourceHasBeenDeleted()) {
+ DCHECK(std::find(backings_.begin(), backings_.end(), backing) ==
+ backings_.end());
+ DCHECK(std::find(evicted_backings_.begin(),
+ evicted_backings_.end(),
+ backing) != evicted_backings_.end());
+ } else {
+ DCHECK(std::find(backings_.begin(), backings_.end(), backing) !=
+ backings_.end());
+ DCHECK(std::find(evicted_backings_.begin(),
+ evicted_backings_.end(),
+ backing) == evicted_backings_.end());
+ }
+ DCHECK(backing->owner() == texture);
+ }
+ }
+
+ // At all times, backings that can be evicted must always come before
+ // backings that can't be evicted in the backing texture list (otherwise
+ // ReduceMemory will not find all textures available for eviction/recycling).
+ bool reached_unrecyclable = false;
+ PrioritizedResource::Backing* previous_backing = NULL;
+ for (BackingList::iterator it = backings_.begin(); it != backings_.end();
+ ++it) {
+ PrioritizedResource::Backing* backing = *it;
+ if (previous_backing &&
+ (!backings_tail_not_sorted_ ||
+ !backing->was_above_priority_cutoff_at_last_priority_update()))
+ DCHECK(CompareBackings(previous_backing, backing));
+ if (!backing->CanBeRecycled())
+ reached_unrecyclable = true;
+ if (reached_unrecyclable)
+ DCHECK(!backing->CanBeRecycled());
+ else
+ DCHECK(backing->CanBeRecycled());
+ previous_backing = backing;
+ }
+#endif
+}
+
+const Proxy* PrioritizedResourceManager::ProxyForDebug() const {
+ return proxy_;
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/prioritized_resource_manager.h b/chromium/cc/resources/prioritized_resource_manager.h
new file mode 100644
index 00000000000..73967727c2c
--- /dev/null
+++ b/chromium/cc/resources/prioritized_resource_manager.h
@@ -0,0 +1,241 @@
+// 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.
+
+#ifndef CC_RESOURCES_PRIORITIZED_RESOURCE_MANAGER_H_
+#define CC_RESOURCES_PRIORITIZED_RESOURCE_MANAGER_H_
+
+#include <list>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/lock.h"
+#include "cc/base/cc_export.h"
+#include "cc/resources/prioritized_resource.h"
+#include "cc/resources/priority_calculator.h"
+#include "cc/resources/resource.h"
+#include "cc/trees/proxy.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "ui/gfx/size.h"
+
+#if defined(COMPILER_GCC)
+namespace BASE_HASH_NAMESPACE {
+template <> struct hash<cc::PrioritizedResource*> {
+ size_t operator()(cc::PrioritizedResource* ptr) const {
+ return hash<size_t>()(reinterpret_cast<size_t>(ptr));
+ }
+};
+} // namespace BASE_HASH_NAMESPACE
+#endif // COMPILER
+
+namespace cc {
+
+class PriorityCalculator;
+class Proxy;
+
+class CC_EXPORT PrioritizedResourceManager {
+ public:
+ static scoped_ptr<PrioritizedResourceManager> Create(const Proxy* proxy) {
+ return make_scoped_ptr(new PrioritizedResourceManager(proxy));
+ }
+ scoped_ptr<PrioritizedResource> CreateTexture(gfx::Size size, GLenum format) {
+ return make_scoped_ptr(new PrioritizedResource(this, size, format));
+ }
+ ~PrioritizedResourceManager();
+
+ typedef std::list<PrioritizedResource::Backing*> BackingList;
+
+ // TODO(epenner): (http://crbug.com/137094) This 64MB default is a straggler
+ // from the old texture manager and is just to give us a default memory
+ // allocation before we get a callback from the GPU memory manager. We
+ // should probaby either:
+ // - wait for the callback before rendering anything instead
+ // - push this into the GPU memory manager somehow.
+ static size_t DefaultMemoryAllocationLimit() { return 64 * 1024 * 1024; }
+
+ // MemoryUseBytes() describes the number of bytes used by existing allocated
+ // textures.
+ size_t MemoryUseBytes() const { return memory_use_bytes_; }
+ // MemoryAboveCutoffBytes() describes the number of bytes that
+ // would be used if all textures that are above the cutoff were allocated.
+ // MemoryUseBytes() <= MemoryAboveCutoffBytes() should always be true.
+ size_t MemoryAboveCutoffBytes() const { return memory_above_cutoff_bytes_; }
+ // MaxMemoryNeededBytes() describes the number of bytes that would be used
+ // by textures if there were no limit on memory usage.
+ size_t MaxMemoryNeededBytes() const { return max_memory_needed_bytes_; }
+ size_t MemoryForSelfManagedTextures() const {
+ return max_memory_limit_bytes_ - memory_available_bytes_;
+ }
+
+ void SetMaxMemoryLimitBytes(size_t bytes) { max_memory_limit_bytes_ = bytes; }
+ size_t MaxMemoryLimitBytes() const { return max_memory_limit_bytes_; }
+
+ // Sepecify a external priority cutoff. Only textures that have a strictly
+ // higher priority than this cutoff will be allowed.
+ void SetExternalPriorityCutoff(int priority_cutoff) {
+ external_priority_cutoff_ = priority_cutoff;
+ }
+
+ // Return the amount of texture memory required at particular cutoffs.
+ size_t MemoryVisibleBytes() const;
+ size_t MemoryVisibleAndNearbyBytes() const;
+
+ void PrioritizeTextures();
+ void ClearPriorities();
+
+ // Delete contents textures' backing resources until they use only
+ // limit_bytes bytes. This may be called on the impl thread while the main
+ // thread is running. Returns true if resources are indeed evicted as a
+ // result of this call.
+ bool ReduceMemoryOnImplThread(size_t limit_bytes,
+ int priority_cutoff,
+ ResourceProvider* resource_provider);
+
+ // Delete contents textures' backing resources that can be recycled. This
+ // may be called on the impl thread while the main thread is running.
+ void ReduceWastedMemoryOnImplThread(ResourceProvider* resource_provider);
+
+ // Returns true if there exist any textures that are linked to backings that
+ // have had their resources evicted. Only when we commit a tree that has no
+ // textures linked to evicted backings may we allow drawing. After an
+ // eviction, this will not become true until unlinkAndClearEvictedBackings
+ // is called.
+ bool LinkedEvictedBackingsExist() const;
+
+ // Unlink the list of contents textures' backings from their owning textures
+ // and delete the evicted backings' structures. This is called just before
+ // updating layers, and is only ever called on the main thread.
+ void UnlinkAndClearEvictedBackings();
+
+ bool RequestLate(PrioritizedResource* texture);
+
+ void ReduceWastedMemory(ResourceProvider* resource_provider);
+ void ReduceMemory(ResourceProvider* resource_provider);
+ void ClearAllMemory(ResourceProvider* resource_provider);
+
+ void AcquireBackingTextureIfNeeded(PrioritizedResource* texture,
+ ResourceProvider* resource_provider);
+
+ void RegisterTexture(PrioritizedResource* texture);
+ void UnregisterTexture(PrioritizedResource* texture);
+ void ReturnBackingTexture(PrioritizedResource* texture);
+
+ // Update all backings' priorities from their owning texture.
+ void PushTexturePrioritiesToBackings();
+
+ // Mark all textures' backings as being in the drawing impl tree.
+ void UpdateBackingsInDrawingImplTree();
+
+ const Proxy* ProxyForDebug() const;
+
+ private:
+ friend class PrioritizedResourceTest;
+
+ enum EvictionPolicy {
+ EVICT_ONLY_RECYCLABLE,
+ EVICT_ANYTHING,
+ };
+ enum UnlinkPolicy {
+ DO_NOT_UNLINK_BACKINGS,
+ UNLINK_BACKINGS,
+ };
+
+ // Compare textures. Highest priority first.
+ static inline bool CompareTextures(PrioritizedResource* a,
+ PrioritizedResource* b) {
+ if (a->request_priority() == b->request_priority())
+ return a < b;
+ return PriorityCalculator::priority_is_higher(a->request_priority(),
+ b->request_priority());
+ }
+ // Compare backings. Lowest priority first.
+ static inline bool CompareBackings(PrioritizedResource::Backing* a,
+ PrioritizedResource::Backing* b) {
+ // Make textures that can be recycled appear first
+ if (a->CanBeRecycled() != b->CanBeRecycled())
+ return (a->CanBeRecycled() > b->CanBeRecycled());
+ // Then sort by being above or below the priority cutoff.
+ if (a->was_above_priority_cutoff_at_last_priority_update() !=
+ b->was_above_priority_cutoff_at_last_priority_update())
+ return (a->was_above_priority_cutoff_at_last_priority_update() <
+ b->was_above_priority_cutoff_at_last_priority_update());
+ // Then sort by priority (note that backings that no longer have owners will
+ // always have the lowest priority)
+ if (a->request_priority_at_last_priority_update() !=
+ b->request_priority_at_last_priority_update())
+ return PriorityCalculator::priority_is_lower(
+ a->request_priority_at_last_priority_update(),
+ b->request_priority_at_last_priority_update());
+ // Finally sort by being in the impl tree versus being completely
+ // unreferenced
+ if (a->in_drawing_impl_tree() != b->in_drawing_impl_tree())
+ return (a->in_drawing_impl_tree() < b->in_drawing_impl_tree());
+ return a < b;
+ }
+
+ explicit PrioritizedResourceManager(const Proxy* proxy);
+
+ bool EvictBackingsToReduceMemory(size_t limit_bytes,
+ int priority_cutoff,
+ EvictionPolicy eviction_policy,
+ UnlinkPolicy unlink_policy,
+ ResourceProvider* resource_provider);
+ PrioritizedResource::Backing* CreateBacking(
+ gfx::Size size,
+ GLenum format,
+ ResourceProvider* resource_provider);
+ void EvictFirstBackingResource(ResourceProvider* resource_provider);
+ void SortBackings();
+
+ void AssertInvariants();
+
+ size_t max_memory_limit_bytes_;
+ // The priority cutoff based on memory pressure. This is not a strict
+ // cutoff -- RequestLate allows textures with priority equal to this
+ // cutoff to be allowed.
+ int priority_cutoff_;
+ // The priority cutoff based on external memory policy. This is a strict
+ // cutoff -- no textures with priority equal to this cutoff will be allowed.
+ int external_priority_cutoff_;
+ size_t memory_use_bytes_;
+ size_t memory_above_cutoff_bytes_;
+ size_t max_memory_needed_bytes_;
+ size_t memory_available_bytes_;
+
+ typedef base::hash_set<PrioritizedResource*> TextureSet;
+ typedef std::vector<PrioritizedResource*> TextureVector;
+
+ const Proxy* proxy_;
+
+ TextureSet textures_;
+ // This list is always sorted in eviction order, with the exception the
+ // newly-allocated or recycled textures at the very end of the tail that
+ // are not sorted by priority.
+ BackingList backings_;
+ bool backings_tail_not_sorted_;
+
+ // The list of backings that have been evicted, but may still be linked
+ // to textures. This can be accessed concurrently by the main and impl
+ // threads, and may only be accessed while holding evicted_backings_lock_.
+ mutable base::Lock evicted_backings_lock_;
+ BackingList evicted_backings_;
+
+ TextureVector temp_texture_vector_;
+
+ // Statistics about memory usage at priority cutoffs, computed at
+ // PrioritizeTextures.
+ size_t memory_visible_bytes_;
+ size_t memory_visible_and_nearby_bytes_;
+
+ // Statistics copied at the time of PushTexturePrioritiesToBackings.
+ size_t memory_visible_last_pushed_bytes_;
+ size_t memory_visible_and_nearby_last_pushed_bytes_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrioritizedResourceManager);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_PRIORITIZED_RESOURCE_MANAGER_H_
diff --git a/chromium/cc/resources/prioritized_resource_unittest.cc b/chromium/cc/resources/prioritized_resource_unittest.cc
new file mode 100644
index 00000000000..92ce9ff7239
--- /dev/null
+++ b/chromium/cc/resources/prioritized_resource_unittest.cc
@@ -0,0 +1,801 @@
+// 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 "cc/resources/prioritized_resource.h"
+
+#include "cc/resources/prioritized_resource_manager.h"
+#include "cc/resources/resource.h"
+#include "cc/test/fake_output_surface.h"
+#include "cc/test/fake_proxy.h"
+#include "cc/test/tiled_layer_test_common.h"
+#include "cc/trees/single_thread_proxy.h" // For DebugScopedSetImplThread
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+
+class PrioritizedResourceTest : public testing::Test {
+ public:
+ PrioritizedResourceTest()
+ : texture_size_(256, 256),
+ texture_format_(GL_RGBA),
+ output_surface_(CreateFakeOutputSurface()) {
+ DebugScopedSetImplThread impl_thread(&proxy_);
+ resource_provider_ = cc::ResourceProvider::Create(output_surface_.get(), 0);
+ }
+
+ virtual ~PrioritizedResourceTest() {
+ DebugScopedSetImplThread impl_thread(&proxy_);
+ resource_provider_.reset();
+ }
+
+ size_t TexturesMemorySize(size_t texture_count) {
+ return Resource::MemorySizeBytes(texture_size_, texture_format_) *
+ texture_count;
+ }
+
+ scoped_ptr<PrioritizedResourceManager> CreateManager(size_t max_textures) {
+ scoped_ptr<PrioritizedResourceManager> manager =
+ PrioritizedResourceManager::Create(&proxy_);
+ manager->SetMaxMemoryLimitBytes(TexturesMemorySize(max_textures));
+ return manager.Pass();
+ }
+
+ bool ValidateTexture(PrioritizedResource* texture,
+ bool request_late) {
+ ResourceManagerAssertInvariants(texture->resource_manager());
+ if (request_late)
+ texture->RequestLate();
+ ResourceManagerAssertInvariants(texture->resource_manager());
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ bool success = texture->can_acquire_backing_texture();
+ if (success)
+ texture->AcquireBackingTexture(ResourceProvider());
+ return success;
+ }
+
+ void PrioritizeTexturesAndBackings(
+ PrioritizedResourceManager* resource_manager) {
+ resource_manager->PrioritizeTextures();
+ ResourceManagerUpdateBackingsPriorities(resource_manager);
+ }
+
+ void ResourceManagerUpdateBackingsPriorities(
+ PrioritizedResourceManager* resource_manager) {
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ resource_manager->PushTexturePrioritiesToBackings();
+ }
+
+ cc::ResourceProvider* ResourceProvider() { return resource_provider_.get(); }
+
+ void ResourceManagerAssertInvariants(
+ PrioritizedResourceManager* resource_manager) {
+#ifndef NDEBUG
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ resource_manager->AssertInvariants();
+#endif
+ }
+
+ bool TextureBackingIsAbovePriorityCutoff(PrioritizedResource* texture) {
+ return texture->backing()->
+ was_above_priority_cutoff_at_last_priority_update();
+ }
+
+ size_t EvictedBackingCount(PrioritizedResourceManager* resource_manager) {
+ return resource_manager->evicted_backings_.size();
+ }
+
+ protected:
+ FakeProxy proxy_;
+ const gfx::Size texture_size_;
+ const GLenum texture_format_;
+ scoped_ptr<OutputSurface> output_surface_;
+ scoped_ptr<cc::ResourceProvider> resource_provider_;
+};
+
+namespace {
+
+TEST_F(PrioritizedResourceTest, RequestTextureExceedingMaxLimit) {
+ const size_t kMaxTextures = 8;
+ scoped_ptr<PrioritizedResourceManager> resource_manager =
+ CreateManager(kMaxTextures);
+
+ // Create textures for double our memory limit.
+ scoped_ptr<PrioritizedResource> textures[kMaxTextures * 2];
+
+ for (size_t i = 0; i < kMaxTextures * 2; ++i)
+ textures[i] =
+ resource_manager->CreateTexture(texture_size_, texture_format_);
+
+ // Set decreasing priorities
+ for (size_t i = 0; i < kMaxTextures * 2; ++i)
+ textures[i]->set_request_priority(100 + i);
+
+ // Only lower half should be available.
+ PrioritizeTexturesAndBackings(resource_manager.get());
+ EXPECT_TRUE(ValidateTexture(textures[0].get(), false));
+ EXPECT_TRUE(ValidateTexture(textures[7].get(), false));
+ EXPECT_FALSE(ValidateTexture(textures[8].get(), false));
+ EXPECT_FALSE(ValidateTexture(textures[15].get(), false));
+
+ // Set increasing priorities
+ for (size_t i = 0; i < kMaxTextures * 2; ++i)
+ textures[i]->set_request_priority(100 - i);
+
+ // Only upper half should be available.
+ PrioritizeTexturesAndBackings(resource_manager.get());
+ EXPECT_FALSE(ValidateTexture(textures[0].get(), false));
+ EXPECT_FALSE(ValidateTexture(textures[7].get(), false));
+ EXPECT_TRUE(ValidateTexture(textures[8].get(), false));
+ EXPECT_TRUE(ValidateTexture(textures[15].get(), false));
+
+ EXPECT_EQ(TexturesMemorySize(kMaxTextures),
+ resource_manager->MemoryAboveCutoffBytes());
+ EXPECT_LE(resource_manager->MemoryUseBytes(),
+ resource_manager->MemoryAboveCutoffBytes());
+ EXPECT_EQ(TexturesMemorySize(2*kMaxTextures),
+ resource_manager->MaxMemoryNeededBytes());
+
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ resource_manager->ClearAllMemory(ResourceProvider());
+}
+
+TEST_F(PrioritizedResourceTest, ChangeMemoryLimits) {
+ const size_t kMaxTextures = 8;
+ scoped_ptr<PrioritizedResourceManager> resource_manager =
+ CreateManager(kMaxTextures);
+ scoped_ptr<PrioritizedResource> textures[kMaxTextures];
+
+ for (size_t i = 0; i < kMaxTextures; ++i) {
+ textures[i] =
+ resource_manager->CreateTexture(texture_size_, texture_format_);
+ }
+ for (size_t i = 0; i < kMaxTextures; ++i)
+ textures[i]->set_request_priority(100 + i);
+
+ // Set max limit to 8 textures
+ resource_manager->SetMaxMemoryLimitBytes(TexturesMemorySize(8));
+ PrioritizeTexturesAndBackings(resource_manager.get());
+ for (size_t i = 0; i < kMaxTextures; ++i)
+ ValidateTexture(textures[i].get(), false);
+ {
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ resource_manager->ReduceMemory(ResourceProvider());
+ }
+
+ EXPECT_EQ(TexturesMemorySize(8), resource_manager->MemoryAboveCutoffBytes());
+ EXPECT_LE(resource_manager->MemoryUseBytes(),
+ resource_manager->MemoryAboveCutoffBytes());
+
+ // Set max limit to 5 textures
+ resource_manager->SetMaxMemoryLimitBytes(TexturesMemorySize(5));
+ PrioritizeTexturesAndBackings(resource_manager.get());
+ for (size_t i = 0; i < kMaxTextures; ++i)
+ EXPECT_EQ(ValidateTexture(textures[i].get(), false), i < 5);
+ {
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ resource_manager->ReduceMemory(ResourceProvider());
+ }
+
+ EXPECT_EQ(TexturesMemorySize(5), resource_manager->MemoryAboveCutoffBytes());
+ EXPECT_LE(resource_manager->MemoryUseBytes(),
+ resource_manager->MemoryAboveCutoffBytes());
+ EXPECT_EQ(TexturesMemorySize(kMaxTextures),
+ resource_manager->MaxMemoryNeededBytes());
+
+ // Set max limit to 4 textures
+ resource_manager->SetMaxMemoryLimitBytes(TexturesMemorySize(4));
+ PrioritizeTexturesAndBackings(resource_manager.get());
+ for (size_t i = 0; i < kMaxTextures; ++i)
+ EXPECT_EQ(ValidateTexture(textures[i].get(), false), i < 4);
+ {
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ resource_manager->ReduceMemory(ResourceProvider());
+ }
+
+ EXPECT_EQ(TexturesMemorySize(4), resource_manager->MemoryAboveCutoffBytes());
+ EXPECT_LE(resource_manager->MemoryUseBytes(),
+ resource_manager->MemoryAboveCutoffBytes());
+ EXPECT_EQ(TexturesMemorySize(kMaxTextures),
+ resource_manager->MaxMemoryNeededBytes());
+
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ resource_manager->ClearAllMemory(ResourceProvider());
+}
+
+TEST_F(PrioritizedResourceTest, ChangePriorityCutoff) {
+ const size_t kMaxTextures = 8;
+ scoped_ptr<PrioritizedResourceManager> resource_manager =
+ CreateManager(kMaxTextures);
+ scoped_ptr<PrioritizedResource> textures[kMaxTextures];
+
+ for (size_t i = 0; i < kMaxTextures; ++i) {
+ textures[i] =
+ resource_manager->CreateTexture(texture_size_, texture_format_);
+ }
+ for (size_t i = 0; i < kMaxTextures; ++i)
+ textures[i]->set_request_priority(100 + i);
+
+ // Set the cutoff to drop two textures. Try to request_late on all textures,
+ // and make sure that request_late doesn't work on a texture with equal
+ // priority to the cutoff.
+ resource_manager->SetMaxMemoryLimitBytes(TexturesMemorySize(8));
+ resource_manager->SetExternalPriorityCutoff(106);
+ PrioritizeTexturesAndBackings(resource_manager.get());
+ for (size_t i = 0; i < kMaxTextures; ++i)
+ EXPECT_EQ(ValidateTexture(textures[i].get(), true), i < 6);
+ {
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ resource_manager->ReduceMemory(ResourceProvider());
+ }
+ EXPECT_EQ(TexturesMemorySize(6), resource_manager->MemoryAboveCutoffBytes());
+ EXPECT_LE(resource_manager->MemoryUseBytes(),
+ resource_manager->MemoryAboveCutoffBytes());
+
+ // Set the cutoff to drop two more textures.
+ resource_manager->SetExternalPriorityCutoff(104);
+ PrioritizeTexturesAndBackings(resource_manager.get());
+ for (size_t i = 0; i < kMaxTextures; ++i)
+ EXPECT_EQ(ValidateTexture(textures[i].get(), false), i < 4);
+ {
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ resource_manager->ReduceMemory(ResourceProvider());
+ }
+ EXPECT_EQ(TexturesMemorySize(4), resource_manager->MemoryAboveCutoffBytes());
+
+ // Do a one-time eviction for one more texture based on priority cutoff
+ PrioritizedResourceManager::BackingList evicted_backings;
+ resource_manager->UnlinkAndClearEvictedBackings();
+ {
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ resource_manager->ReduceMemoryOnImplThread(
+ TexturesMemorySize(8), 104, ResourceProvider());
+ EXPECT_EQ(0u, EvictedBackingCount(resource_manager.get()));
+ resource_manager->ReduceMemoryOnImplThread(
+ TexturesMemorySize(8), 103, ResourceProvider());
+ EXPECT_EQ(1u, EvictedBackingCount(resource_manager.get()));
+ }
+ resource_manager->UnlinkAndClearEvictedBackings();
+ EXPECT_EQ(TexturesMemorySize(3), resource_manager->MemoryUseBytes());
+
+ // Re-allocate the the texture after the one-time drop.
+ PrioritizeTexturesAndBackings(resource_manager.get());
+ for (size_t i = 0; i < kMaxTextures; ++i)
+ EXPECT_EQ(ValidateTexture(textures[i].get(), false), i < 4);
+ {
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ resource_manager->ReduceMemory(ResourceProvider());
+ }
+ EXPECT_EQ(TexturesMemorySize(4), resource_manager->MemoryAboveCutoffBytes());
+
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ resource_manager->ClearAllMemory(ResourceProvider());
+}
+
+TEST_F(PrioritizedResourceTest, ResourceManagerPartialUpdateTextures) {
+ const size_t kMaxTextures = 4;
+ const size_t kNumTextures = 4;
+ scoped_ptr<PrioritizedResourceManager> resource_manager =
+ CreateManager(kMaxTextures);
+ scoped_ptr<PrioritizedResource> textures[kNumTextures];
+ scoped_ptr<PrioritizedResource> more_textures[kNumTextures];
+
+ for (size_t i = 0; i < kNumTextures; ++i) {
+ textures[i] =
+ resource_manager->CreateTexture(texture_size_, texture_format_);
+ more_textures[i] =
+ resource_manager->CreateTexture(texture_size_, texture_format_);
+ }
+
+ for (size_t i = 0; i < kNumTextures; ++i)
+ textures[i]->set_request_priority(200 + i);
+ PrioritizeTexturesAndBackings(resource_manager.get());
+
+ // Allocate textures which are currently high priority.
+ EXPECT_TRUE(ValidateTexture(textures[0].get(), false));
+ EXPECT_TRUE(ValidateTexture(textures[1].get(), false));
+ EXPECT_TRUE(ValidateTexture(textures[2].get(), false));
+ EXPECT_TRUE(ValidateTexture(textures[3].get(), false));
+
+ EXPECT_TRUE(textures[0]->have_backing_texture());
+ EXPECT_TRUE(textures[1]->have_backing_texture());
+ EXPECT_TRUE(textures[2]->have_backing_texture());
+ EXPECT_TRUE(textures[3]->have_backing_texture());
+
+ for (size_t i = 0; i < kNumTextures; ++i)
+ more_textures[i]->set_request_priority(100 + i);
+ PrioritizeTexturesAndBackings(resource_manager.get());
+
+ // Textures are now below cutoff.
+ EXPECT_FALSE(ValidateTexture(textures[0].get(), false));
+ EXPECT_FALSE(ValidateTexture(textures[1].get(), false));
+ EXPECT_FALSE(ValidateTexture(textures[2].get(), false));
+ EXPECT_FALSE(ValidateTexture(textures[3].get(), false));
+
+ // But they are still valid to use.
+ EXPECT_TRUE(textures[0]->have_backing_texture());
+ EXPECT_TRUE(textures[1]->have_backing_texture());
+ EXPECT_TRUE(textures[2]->have_backing_texture());
+ EXPECT_TRUE(textures[3]->have_backing_texture());
+
+ // Higher priority textures are finally needed.
+ EXPECT_TRUE(ValidateTexture(more_textures[0].get(), false));
+ EXPECT_TRUE(ValidateTexture(more_textures[1].get(), false));
+ EXPECT_TRUE(ValidateTexture(more_textures[2].get(), false));
+ EXPECT_TRUE(ValidateTexture(more_textures[3].get(), false));
+
+ // Lower priority have been fully evicted.
+ EXPECT_FALSE(textures[0]->have_backing_texture());
+ EXPECT_FALSE(textures[1]->have_backing_texture());
+ EXPECT_FALSE(textures[2]->have_backing_texture());
+ EXPECT_FALSE(textures[3]->have_backing_texture());
+
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ resource_manager->ClearAllMemory(ResourceProvider());
+}
+
+TEST_F(PrioritizedResourceTest, ResourceManagerPrioritiesAreEqual) {
+ const size_t kMaxTextures = 16;
+ scoped_ptr<PrioritizedResourceManager> resource_manager =
+ CreateManager(kMaxTextures);
+ scoped_ptr<PrioritizedResource> textures[kMaxTextures];
+
+ for (size_t i = 0; i < kMaxTextures; ++i) {
+ textures[i] =
+ resource_manager->CreateTexture(texture_size_, texture_format_);
+ }
+
+ // All 16 textures have the same priority except 2 higher priority.
+ for (size_t i = 0; i < kMaxTextures; ++i)
+ textures[i]->set_request_priority(100);
+ textures[0]->set_request_priority(99);
+ textures[1]->set_request_priority(99);
+
+ // Set max limit to 8 textures
+ resource_manager->SetMaxMemoryLimitBytes(TexturesMemorySize(8));
+ PrioritizeTexturesAndBackings(resource_manager.get());
+
+ // The two high priority textures should be available, others should not.
+ for (size_t i = 0; i < 2; ++i)
+ EXPECT_TRUE(ValidateTexture(textures[i].get(), false));
+ for (size_t i = 2; i < kMaxTextures; ++i)
+ EXPECT_FALSE(ValidateTexture(textures[i].get(), false));
+ EXPECT_EQ(TexturesMemorySize(2), resource_manager->MemoryAboveCutoffBytes());
+ EXPECT_LE(resource_manager->MemoryUseBytes(),
+ resource_manager->MemoryAboveCutoffBytes());
+
+ // Manually reserving textures should only succeed on the higher priority
+ // textures, and on remaining textures up to the memory limit.
+ for (size_t i = 0; i < 8; i++)
+ EXPECT_TRUE(ValidateTexture(textures[i].get(), true));
+ for (size_t i = 9; i < kMaxTextures; i++)
+ EXPECT_FALSE(ValidateTexture(textures[i].get(), true));
+ EXPECT_EQ(TexturesMemorySize(8), resource_manager->MemoryAboveCutoffBytes());
+ EXPECT_LE(resource_manager->MemoryUseBytes(),
+ resource_manager->MemoryAboveCutoffBytes());
+
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ resource_manager->ClearAllMemory(ResourceProvider());
+}
+
+TEST_F(PrioritizedResourceTest, ResourceManagerDestroyedFirst) {
+ scoped_ptr<PrioritizedResourceManager> resource_manager = CreateManager(1);
+ scoped_ptr<PrioritizedResource> texture =
+ resource_manager->CreateTexture(texture_size_, texture_format_);
+
+ // Texture is initially invalid, but it will become available.
+ EXPECT_FALSE(texture->have_backing_texture());
+
+ texture->set_request_priority(100);
+ PrioritizeTexturesAndBackings(resource_manager.get());
+
+ EXPECT_TRUE(ValidateTexture(texture.get(), false));
+ EXPECT_TRUE(texture->can_acquire_backing_texture());
+ EXPECT_TRUE(texture->have_backing_texture());
+ {
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ resource_manager->ClearAllMemory(ResourceProvider());
+ }
+ resource_manager.reset();
+
+ EXPECT_FALSE(texture->can_acquire_backing_texture());
+ EXPECT_FALSE(texture->have_backing_texture());
+}
+
+TEST_F(PrioritizedResourceTest, TextureMovedToNewManager) {
+ scoped_ptr<PrioritizedResourceManager> resource_manager_one =
+ CreateManager(1);
+ scoped_ptr<PrioritizedResourceManager> resource_manager_two =
+ CreateManager(1);
+ scoped_ptr<PrioritizedResource> texture =
+ resource_manager_one->CreateTexture(texture_size_, texture_format_);
+
+ // Texture is initially invalid, but it will become available.
+ EXPECT_FALSE(texture->have_backing_texture());
+
+ texture->set_request_priority(100);
+ PrioritizeTexturesAndBackings(resource_manager_one.get());
+
+ EXPECT_TRUE(ValidateTexture(texture.get(), false));
+ EXPECT_TRUE(texture->can_acquire_backing_texture());
+ EXPECT_TRUE(texture->have_backing_texture());
+
+ texture->SetTextureManager(NULL);
+ {
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ resource_manager_one->ClearAllMemory(ResourceProvider());
+ }
+ resource_manager_one.reset();
+
+ EXPECT_FALSE(texture->can_acquire_backing_texture());
+ EXPECT_FALSE(texture->have_backing_texture());
+
+ texture->SetTextureManager(resource_manager_two.get());
+
+ PrioritizeTexturesAndBackings(resource_manager_two.get());
+
+ EXPECT_TRUE(ValidateTexture(texture.get(), false));
+ EXPECT_TRUE(texture->can_acquire_backing_texture());
+ EXPECT_TRUE(texture->have_backing_texture());
+
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ resource_manager_two->ClearAllMemory(ResourceProvider());
+}
+
+TEST_F(PrioritizedResourceTest,
+ RenderSurfacesReduceMemoryAvailableOutsideRootSurface) {
+ const size_t kMaxTextures = 8;
+ scoped_ptr<PrioritizedResourceManager> resource_manager =
+ CreateManager(kMaxTextures);
+
+ // Half of the memory is taken by surfaces (with high priority place-holder)
+ scoped_ptr<PrioritizedResource> render_surface_place_holder =
+ resource_manager->CreateTexture(texture_size_, texture_format_);
+ render_surface_place_holder->SetToSelfManagedMemoryPlaceholder(
+ TexturesMemorySize(4));
+ render_surface_place_holder->set_request_priority(
+ PriorityCalculator::RenderSurfacePriority());
+
+ // Create textures to fill our memory limit.
+ scoped_ptr<PrioritizedResource> textures[kMaxTextures];
+
+ for (size_t i = 0; i < kMaxTextures; ++i) {
+ textures[i] =
+ resource_manager->CreateTexture(texture_size_, texture_format_);
+ }
+
+ // Set decreasing non-visible priorities outside root surface.
+ for (size_t i = 0; i < kMaxTextures; ++i)
+ textures[i]->set_request_priority(100 + i);
+
+ // Only lower half should be available.
+ PrioritizeTexturesAndBackings(resource_manager.get());
+ EXPECT_TRUE(ValidateTexture(textures[0].get(), false));
+ EXPECT_TRUE(ValidateTexture(textures[3].get(), false));
+ EXPECT_FALSE(ValidateTexture(textures[4].get(), false));
+ EXPECT_FALSE(ValidateTexture(textures[7].get(), false));
+
+ // Set increasing non-visible priorities outside root surface.
+ for (size_t i = 0; i < kMaxTextures; ++i)
+ textures[i]->set_request_priority(100 - i);
+
+ // Only upper half should be available.
+ PrioritizeTexturesAndBackings(resource_manager.get());
+ EXPECT_FALSE(ValidateTexture(textures[0].get(), false));
+ EXPECT_FALSE(ValidateTexture(textures[3].get(), false));
+ EXPECT_TRUE(ValidateTexture(textures[4].get(), false));
+ EXPECT_TRUE(ValidateTexture(textures[7].get(), false));
+
+ EXPECT_EQ(TexturesMemorySize(4), resource_manager->MemoryAboveCutoffBytes());
+ EXPECT_EQ(TexturesMemorySize(4),
+ resource_manager->MemoryForSelfManagedTextures());
+ EXPECT_LE(resource_manager->MemoryUseBytes(),
+ resource_manager->MemoryAboveCutoffBytes());
+ EXPECT_EQ(TexturesMemorySize(8),
+ resource_manager->MaxMemoryNeededBytes());
+
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ resource_manager->ClearAllMemory(ResourceProvider());
+}
+
+TEST_F(PrioritizedResourceTest,
+ RenderSurfacesReduceMemoryAvailableForRequestLate) {
+ const size_t kMaxTextures = 8;
+ scoped_ptr<PrioritizedResourceManager> resource_manager =
+ CreateManager(kMaxTextures);
+
+ // Half of the memory is taken by surfaces (with high priority place-holder)
+ scoped_ptr<PrioritizedResource> render_surface_place_holder =
+ resource_manager->CreateTexture(texture_size_, texture_format_);
+ render_surface_place_holder->SetToSelfManagedMemoryPlaceholder(
+ TexturesMemorySize(4));
+ render_surface_place_holder->set_request_priority(
+ PriorityCalculator::RenderSurfacePriority());
+
+ // Create textures to fill our memory limit.
+ scoped_ptr<PrioritizedResource> textures[kMaxTextures];
+
+ for (size_t i = 0; i < kMaxTextures; ++i) {
+ textures[i] =
+ resource_manager->CreateTexture(texture_size_, texture_format_);
+ }
+
+ // Set equal priorities.
+ for (size_t i = 0; i < kMaxTextures; ++i)
+ textures[i]->set_request_priority(100);
+
+ // The first four to be requested late will be available.
+ PrioritizeTexturesAndBackings(resource_manager.get());
+ for (unsigned i = 0; i < kMaxTextures; ++i)
+ EXPECT_FALSE(ValidateTexture(textures[i].get(), false));
+ for (unsigned i = 0; i < kMaxTextures; i += 2)
+ EXPECT_TRUE(ValidateTexture(textures[i].get(), true));
+ for (unsigned i = 1; i < kMaxTextures; i += 2)
+ EXPECT_FALSE(ValidateTexture(textures[i].get(), true));
+
+ EXPECT_EQ(TexturesMemorySize(4), resource_manager->MemoryAboveCutoffBytes());
+ EXPECT_EQ(TexturesMemorySize(4),
+ resource_manager->MemoryForSelfManagedTextures());
+ EXPECT_LE(resource_manager->MemoryUseBytes(),
+ resource_manager->MemoryAboveCutoffBytes());
+ EXPECT_EQ(TexturesMemorySize(8),
+ resource_manager->MaxMemoryNeededBytes());
+
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ resource_manager->ClearAllMemory(ResourceProvider());
+}
+
+TEST_F(PrioritizedResourceTest,
+ WhenRenderSurfaceNotAvailableTexturesAlsoNotAvailable) {
+ const size_t kMaxTextures = 8;
+ scoped_ptr<PrioritizedResourceManager> resource_manager =
+ CreateManager(kMaxTextures);
+
+ // Half of the memory is taken by surfaces (with high priority place-holder)
+ scoped_ptr<PrioritizedResource> render_surface_place_holder =
+ resource_manager->CreateTexture(texture_size_, texture_format_);
+ render_surface_place_holder->SetToSelfManagedMemoryPlaceholder(
+ TexturesMemorySize(4));
+ render_surface_place_holder->set_request_priority(
+ PriorityCalculator::RenderSurfacePriority());
+
+ // Create textures to fill our memory limit.
+ scoped_ptr<PrioritizedResource> textures[kMaxTextures];
+
+ for (size_t i = 0; i < kMaxTextures; ++i)
+ textures[i] =
+ resource_manager->CreateTexture(texture_size_, texture_format_);
+
+ // Set 6 visible textures in the root surface, and 2 in a child surface.
+ for (size_t i = 0; i < 6; ++i) {
+ textures[i]->
+ set_request_priority(PriorityCalculator::VisiblePriority(true));
+ }
+ for (size_t i = 6; i < 8; ++i) {
+ textures[i]->
+ set_request_priority(PriorityCalculator::VisiblePriority(false));
+ }
+
+ PrioritizeTexturesAndBackings(resource_manager.get());
+
+ // Unable to request_late textures in the child surface.
+ EXPECT_FALSE(ValidateTexture(textures[6].get(), true));
+ EXPECT_FALSE(ValidateTexture(textures[7].get(), true));
+
+ // Root surface textures are valid.
+ for (size_t i = 0; i < 6; ++i)
+ EXPECT_TRUE(ValidateTexture(textures[i].get(), false));
+
+ EXPECT_EQ(TexturesMemorySize(6), resource_manager->MemoryAboveCutoffBytes());
+ EXPECT_EQ(TexturesMemorySize(2),
+ resource_manager->MemoryForSelfManagedTextures());
+ EXPECT_LE(resource_manager->MemoryUseBytes(),
+ resource_manager->MemoryAboveCutoffBytes());
+
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ resource_manager->ClearAllMemory(ResourceProvider());
+}
+
+TEST_F(PrioritizedResourceTest, RequestLateBackingsSorting) {
+ const size_t kMaxTextures = 8;
+ scoped_ptr<PrioritizedResourceManager> resource_manager =
+ CreateManager(kMaxTextures);
+ resource_manager->SetMaxMemoryLimitBytes(TexturesMemorySize(kMaxTextures));
+
+ // Create textures to fill our memory limit.
+ scoped_ptr<PrioritizedResource> textures[kMaxTextures];
+ for (size_t i = 0; i < kMaxTextures; ++i)
+ textures[i] =
+ resource_manager->CreateTexture(texture_size_, texture_format_);
+
+ // Set equal priorities, and allocate backings for all textures.
+ for (size_t i = 0; i < kMaxTextures; ++i)
+ textures[i]->set_request_priority(100);
+ PrioritizeTexturesAndBackings(resource_manager.get());
+ for (unsigned i = 0; i < kMaxTextures; ++i)
+ EXPECT_TRUE(ValidateTexture(textures[i].get(), false));
+
+ // Drop the memory limit and prioritize (none will be above the threshold,
+ // but they still have backings because ReduceMemory hasn't been called).
+ resource_manager->SetMaxMemoryLimitBytes(
+ TexturesMemorySize(kMaxTextures / 2));
+ PrioritizeTexturesAndBackings(resource_manager.get());
+
+ // Push half of them back over the limit.
+ for (size_t i = 0; i < kMaxTextures; i += 2)
+ EXPECT_TRUE(textures[i]->RequestLate());
+
+ // Push the priorities to the backings array and sort the backings array
+ ResourceManagerUpdateBackingsPriorities(resource_manager.get());
+
+ // Assert that the backings list be sorted with the below-limit backings
+ // before the above-limit backings.
+ ResourceManagerAssertInvariants(resource_manager.get());
+
+ // Make sure that we have backings for all of the textures.
+ for (size_t i = 0; i < kMaxTextures; ++i)
+ EXPECT_TRUE(textures[i]->have_backing_texture());
+
+ // Make sure that only the request_late textures are above the priority
+ // cutoff
+ for (size_t i = 0; i < kMaxTextures; i += 2)
+ EXPECT_TRUE(TextureBackingIsAbovePriorityCutoff(textures[i].get()));
+ for (size_t i = 1; i < kMaxTextures; i += 2)
+ EXPECT_FALSE(TextureBackingIsAbovePriorityCutoff(textures[i].get()));
+
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ resource_manager->ClearAllMemory(ResourceProvider());
+}
+
+TEST_F(PrioritizedResourceTest, ClearUploadsToEvictedResources) {
+ const size_t kMaxTextures = 4;
+ scoped_ptr<PrioritizedResourceManager> resource_manager =
+ CreateManager(kMaxTextures);
+ resource_manager->SetMaxMemoryLimitBytes(TexturesMemorySize(kMaxTextures));
+
+ // Create textures to fill our memory limit.
+ scoped_ptr<PrioritizedResource> textures[kMaxTextures];
+
+ for (size_t i = 0; i < kMaxTextures; ++i)
+ textures[i] =
+ resource_manager->CreateTexture(texture_size_, texture_format_);
+
+ // Set equal priorities, and allocate backings for all textures.
+ for (size_t i = 0; i < kMaxTextures; ++i)
+ textures[i]->set_request_priority(100);
+ PrioritizeTexturesAndBackings(resource_manager.get());
+ for (unsigned i = 0; i < kMaxTextures; ++i)
+ EXPECT_TRUE(ValidateTexture(textures[i].get(), false));
+
+ ResourceUpdateQueue queue;
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ for (size_t i = 0; i < kMaxTextures; ++i) {
+ const ResourceUpdate upload = ResourceUpdate::Create(
+ textures[i].get(), NULL, gfx::Rect(), gfx::Rect(), gfx::Vector2d());
+ queue.AppendFullUpload(upload);
+ }
+
+ // Make sure that we have backings for all of the textures.
+ for (size_t i = 0; i < kMaxTextures; ++i)
+ EXPECT_TRUE(textures[i]->have_backing_texture());
+
+ queue.ClearUploadsToEvictedResources();
+ EXPECT_EQ(4u, queue.FullUploadSize());
+
+ resource_manager->ReduceMemoryOnImplThread(
+ TexturesMemorySize(1),
+ PriorityCalculator::AllowEverythingCutoff(),
+ ResourceProvider());
+ queue.ClearUploadsToEvictedResources();
+ EXPECT_EQ(1u, queue.FullUploadSize());
+
+ resource_manager->ReduceMemoryOnImplThread(
+ 0, PriorityCalculator::AllowEverythingCutoff(), ResourceProvider());
+ queue.ClearUploadsToEvictedResources();
+ EXPECT_EQ(0u, queue.FullUploadSize());
+}
+
+TEST_F(PrioritizedResourceTest, UsageStatistics) {
+ const size_t kMaxTextures = 5;
+ scoped_ptr<PrioritizedResourceManager> resource_manager =
+ CreateManager(kMaxTextures);
+ scoped_ptr<PrioritizedResource> textures[kMaxTextures];
+
+ for (size_t i = 0; i < kMaxTextures; ++i) {
+ textures[i] =
+ resource_manager->CreateTexture(texture_size_, texture_format_);
+ }
+
+ textures[0]->
+ set_request_priority(PriorityCalculator::AllowVisibleOnlyCutoff() - 1);
+ textures[1]->
+ set_request_priority(PriorityCalculator::AllowVisibleOnlyCutoff());
+ textures[2]->set_request_priority(
+ PriorityCalculator::AllowVisibleAndNearbyCutoff() - 1);
+ textures[3]->
+ set_request_priority(PriorityCalculator::AllowVisibleAndNearbyCutoff());
+ textures[4]->set_request_priority(
+ PriorityCalculator::AllowVisibleAndNearbyCutoff() + 1);
+
+ // Set max limit to 2 textures.
+ resource_manager->SetMaxMemoryLimitBytes(TexturesMemorySize(2));
+ PrioritizeTexturesAndBackings(resource_manager.get());
+
+ // The first two textures should be available, others should not.
+ for (size_t i = 0; i < 2; ++i)
+ EXPECT_TRUE(ValidateTexture(textures[i].get(), false));
+ for (size_t i = 2; i < kMaxTextures; ++i)
+ EXPECT_FALSE(ValidateTexture(textures[i].get(), false));
+
+ // Validate the statistics.
+ {
+ DebugScopedSetImplThread impl_thread(&proxy_);
+ EXPECT_EQ(TexturesMemorySize(2), resource_manager->MemoryUseBytes());
+ EXPECT_EQ(TexturesMemorySize(1), resource_manager->MemoryVisibleBytes());
+ EXPECT_EQ(TexturesMemorySize(3),
+ resource_manager->MemoryVisibleAndNearbyBytes());
+ }
+
+ // Re-prioritize the textures, but do not push the values to backings.
+ textures[0]->
+ set_request_priority(PriorityCalculator::AllowVisibleOnlyCutoff() - 1);
+ textures[1]->
+ set_request_priority(PriorityCalculator::AllowVisibleOnlyCutoff() - 1);
+ textures[2]->
+ set_request_priority(PriorityCalculator::AllowVisibleOnlyCutoff() - 1);
+ textures[3]->set_request_priority(
+ PriorityCalculator::AllowVisibleAndNearbyCutoff() - 1);
+ textures[4]->
+ set_request_priority(PriorityCalculator::AllowVisibleAndNearbyCutoff());
+ resource_manager->PrioritizeTextures();
+
+ // Verify that we still see the old values.
+ {
+ DebugScopedSetImplThread impl_thread(&proxy_);
+ EXPECT_EQ(TexturesMemorySize(2), resource_manager->MemoryUseBytes());
+ EXPECT_EQ(TexturesMemorySize(1), resource_manager->MemoryVisibleBytes());
+ EXPECT_EQ(TexturesMemorySize(3),
+ resource_manager->MemoryVisibleAndNearbyBytes());
+ }
+
+ // Push priorities to backings, and verify we see the new values.
+ {
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ resource_manager->PushTexturePrioritiesToBackings();
+ EXPECT_EQ(TexturesMemorySize(2), resource_manager->MemoryUseBytes());
+ EXPECT_EQ(TexturesMemorySize(3), resource_manager->MemoryVisibleBytes());
+ EXPECT_EQ(TexturesMemorySize(4),
+ resource_manager->MemoryVisibleAndNearbyBytes());
+ }
+
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ resource_manager->ClearAllMemory(ResourceProvider());
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/resources/prioritized_tile_set.cc b/chromium/cc/resources/prioritized_tile_set.cc
new file mode 100644
index 00000000000..6255be94137
--- /dev/null
+++ b/chromium/cc/resources/prioritized_tile_set.cc
@@ -0,0 +1,145 @@
+// Copyright 2013 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 "cc/resources/prioritized_tile_set.h"
+
+#include <algorithm>
+
+#include "cc/resources/managed_tile_state.h"
+#include "cc/resources/tile.h"
+
+namespace cc {
+
+class BinComparator {
+ public:
+ bool operator()(const scoped_refptr<Tile>& a,
+ const scoped_refptr<Tile>& b) const {
+ const ManagedTileState& ams = a->managed_state();
+ const ManagedTileState& bms = b->managed_state();
+
+ if (ams.bin[LOW_PRIORITY_BIN] != bms.bin[LOW_PRIORITY_BIN])
+ return ams.bin[LOW_PRIORITY_BIN] < bms.bin[LOW_PRIORITY_BIN];
+
+ if (ams.required_for_activation != bms.required_for_activation)
+ return ams.required_for_activation;
+
+ if (ams.resolution != bms.resolution)
+ return ams.resolution < bms.resolution;
+
+ if (ams.time_to_needed_in_seconds != bms.time_to_needed_in_seconds)
+ return ams.time_to_needed_in_seconds < bms.time_to_needed_in_seconds;
+
+ if (ams.distance_to_visible_in_pixels !=
+ bms.distance_to_visible_in_pixels) {
+ return ams.distance_to_visible_in_pixels <
+ bms.distance_to_visible_in_pixels;
+ }
+
+ gfx::Rect a_rect = a->content_rect();
+ gfx::Rect b_rect = b->content_rect();
+ if (a_rect.y() != b_rect.y())
+ return a_rect.y() < b_rect.y();
+ return a_rect.x() < b_rect.x();
+ }
+};
+
+namespace {
+
+typedef std::vector<scoped_refptr<Tile> > TileVector;
+
+void SortBinTiles(ManagedTileBin bin, TileVector* tiles) {
+ switch (bin) {
+ case NOW_AND_READY_TO_DRAW_BIN:
+ break;
+ case NOW_BIN:
+ case SOON_BIN:
+ case EVENTUALLY_AND_ACTIVE_BIN:
+ case EVENTUALLY_BIN:
+ case NEVER_AND_ACTIVE_BIN:
+ case NEVER_BIN:
+ std::sort(tiles->begin(), tiles->end(), BinComparator());
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+} // namespace
+
+PrioritizedTileSet::PrioritizedTileSet() {
+ for (int bin = 0; bin < NUM_BINS; ++bin)
+ bin_sorted_[bin] = true;
+}
+
+PrioritizedTileSet::~PrioritizedTileSet() {}
+
+void PrioritizedTileSet::InsertTile(Tile* tile, ManagedTileBin bin) {
+ tiles_[bin].push_back(make_scoped_refptr(tile));
+ bin_sorted_[bin] = false;
+}
+
+void PrioritizedTileSet::Clear() {
+ for (int bin = 0; bin < NUM_BINS; ++bin) {
+ tiles_[bin].clear();
+ bin_sorted_[bin] = true;
+ }
+}
+
+void PrioritizedTileSet::SortBinIfNeeded(ManagedTileBin bin) {
+ if (!bin_sorted_[bin]) {
+ SortBinTiles(bin, &tiles_[bin]);
+ bin_sorted_[bin] = true;
+ }
+}
+
+PrioritizedTileSet::Iterator::Iterator(
+ PrioritizedTileSet* tile_set, bool use_priority_ordering)
+ : tile_set_(tile_set),
+ current_bin_(NOW_AND_READY_TO_DRAW_BIN),
+ use_priority_ordering_(use_priority_ordering) {
+ if (use_priority_ordering_)
+ tile_set_->SortBinIfNeeded(current_bin_);
+ iterator_ = tile_set->tiles_[current_bin_].begin();
+ if (iterator_ == tile_set_->tiles_[current_bin_].end())
+ AdvanceList();
+}
+
+PrioritizedTileSet::Iterator::~Iterator() {}
+
+void PrioritizedTileSet::Iterator::DisablePriorityOrdering() {
+ use_priority_ordering_ = false;
+}
+
+PrioritizedTileSet::Iterator&
+PrioritizedTileSet::Iterator::operator++() {
+ // We can't increment past the end of the tiles.
+ DCHECK(iterator_ != tile_set_->tiles_[current_bin_].end());
+
+ ++iterator_;
+ if (iterator_ == tile_set_->tiles_[current_bin_].end())
+ AdvanceList();
+ return *this;
+}
+
+Tile* PrioritizedTileSet::Iterator::operator*() {
+ DCHECK(iterator_ != tile_set_->tiles_[current_bin_].end());
+ return iterator_->get();
+}
+
+void PrioritizedTileSet::Iterator::AdvanceList() {
+ DCHECK(iterator_ == tile_set_->tiles_[current_bin_].end());
+
+ while (current_bin_ != NEVER_BIN) {
+ current_bin_ = static_cast<ManagedTileBin>(current_bin_ + 1);
+
+ if (use_priority_ordering_)
+ tile_set_->SortBinIfNeeded(current_bin_);
+
+ iterator_ = tile_set_->tiles_[current_bin_].begin();
+ if (iterator_ != tile_set_->tiles_[current_bin_].end())
+ break;
+ }
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/prioritized_tile_set.h b/chromium/cc/resources/prioritized_tile_set.h
new file mode 100644
index 00000000000..366179b05c8
--- /dev/null
+++ b/chromium/cc/resources/prioritized_tile_set.h
@@ -0,0 +1,62 @@
+// Copyright 2013 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.
+
+#ifndef CC_RESOURCES_PRIORITIZED_TILE_SET_H_
+#define CC_RESOURCES_PRIORITIZED_TILE_SET_H_
+
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/resources/managed_tile_state.h"
+
+namespace cc {
+class Tile;
+
+class CC_EXPORT PrioritizedTileSet {
+ public:
+ PrioritizedTileSet();
+ ~PrioritizedTileSet();
+
+ void InsertTile(Tile* tile, ManagedTileBin bin);
+ void Clear();
+
+ class CC_EXPORT Iterator {
+ public:
+ Iterator(PrioritizedTileSet* set, bool use_priority_ordering);
+
+ ~Iterator();
+
+ void DisablePriorityOrdering();
+
+ Iterator& operator++();
+ Tile* operator->() { return *(*this); }
+ Tile* operator*();
+ operator bool() const {
+ return iterator_ != tile_set_->tiles_[current_bin_].end();
+ }
+
+ private:
+ void AdvanceList();
+
+ PrioritizedTileSet* tile_set_;
+ ManagedTileBin current_bin_;
+ std::vector<scoped_refptr<Tile> >::iterator iterator_;
+ bool use_priority_ordering_;
+ };
+
+ private:
+ friend class Iterator;
+
+ void SortBinIfNeeded(ManagedTileBin bin);
+
+ typedef scoped_refptr<Tile> TileRef;
+ std::vector<TileRef> tiles_[NUM_BINS];
+ bool bin_sorted_[NUM_BINS];
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_PRIORITIZED_TILE_SET_H_
diff --git a/chromium/cc/resources/prioritized_tile_set_unittest.cc b/chromium/cc/resources/prioritized_tile_set_unittest.cc
new file mode 100644
index 00000000000..e244aefdbb9
--- /dev/null
+++ b/chromium/cc/resources/prioritized_tile_set_unittest.cc
@@ -0,0 +1,725 @@
+// Copyright 2013 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 <algorithm>
+#include <vector>
+
+#include "cc/resources/managed_tile_state.h"
+#include "cc/resources/prioritized_tile_set.h"
+#include "cc/resources/tile.h"
+#include "cc/test/fake_output_surface.h"
+#include "cc/test/fake_picture_pile_impl.h"
+#include "cc/test/fake_tile_manager.h"
+#include "cc/test/fake_tile_manager_client.h"
+#include "cc/test/test_tile_priorities.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+
+class BinComparator {
+ public:
+ bool operator()(const scoped_refptr<Tile>& a,
+ const scoped_refptr<Tile>& b) const {
+ const ManagedTileState& ams = a->managed_state();
+ const ManagedTileState& bms = b->managed_state();
+
+ if (ams.bin[LOW_PRIORITY_BIN] != bms.bin[LOW_PRIORITY_BIN])
+ return ams.bin[LOW_PRIORITY_BIN] < bms.bin[LOW_PRIORITY_BIN];
+
+ if (ams.required_for_activation != bms.required_for_activation)
+ return ams.required_for_activation;
+
+ if (ams.resolution != bms.resolution)
+ return ams.resolution < bms.resolution;
+
+ if (ams.time_to_needed_in_seconds != bms.time_to_needed_in_seconds)
+ return ams.time_to_needed_in_seconds < bms.time_to_needed_in_seconds;
+
+ if (ams.distance_to_visible_in_pixels !=
+ bms.distance_to_visible_in_pixels) {
+ return ams.distance_to_visible_in_pixels <
+ bms.distance_to_visible_in_pixels;
+ }
+
+ gfx::Rect a_rect = a->content_rect();
+ gfx::Rect b_rect = b->content_rect();
+ if (a_rect.y() != b_rect.y())
+ return a_rect.y() < b_rect.y();
+ return a_rect.x() < b_rect.x();
+ }
+};
+
+namespace {
+
+class PrioritizedTileSetTest : public testing::Test {
+ public:
+ PrioritizedTileSetTest()
+ : output_surface_(FakeOutputSurface::Create3d()),
+ resource_provider_(ResourceProvider::Create(output_surface_.get(), 0)),
+ tile_manager_(new FakeTileManager(&tile_manager_client_,
+ resource_provider_.get())),
+ picture_pile_(FakePicturePileImpl::CreatePile()) {}
+
+ scoped_refptr<Tile> CreateTile() {
+ return make_scoped_refptr(new Tile(tile_manager_.get(),
+ picture_pile_.get(),
+ settings_.default_tile_size,
+ gfx::Rect(),
+ gfx::Rect(),
+ 1.0,
+ 0,
+ 0,
+ true));
+ }
+
+ private:
+ FakeTileManagerClient tile_manager_client_;
+ LayerTreeSettings settings_;
+ scoped_ptr<FakeOutputSurface> output_surface_;
+ scoped_ptr<ResourceProvider> resource_provider_;
+ scoped_ptr<FakeTileManager> tile_manager_;
+ scoped_refptr<FakePicturePileImpl> picture_pile_;
+};
+
+TEST_F(PrioritizedTileSetTest, EmptyIterator) {
+ // Creating an iterator to an empty set should work (but create iterator that
+ // isn't valid).
+
+ PrioritizedTileSet set;
+
+ PrioritizedTileSet::Iterator it(&set, true);
+ EXPECT_FALSE(it);
+}
+
+TEST_F(PrioritizedTileSetTest, NonEmptyIterator) {
+ PrioritizedTileSet set;
+ scoped_refptr<Tile> tile = CreateTile();
+ set.InsertTile(tile, NOW_BIN);
+
+ PrioritizedTileSet::Iterator it(&set, true);
+ EXPECT_TRUE(it);
+ EXPECT_TRUE(*it == tile.get());
+ ++it;
+ EXPECT_FALSE(it);
+}
+
+TEST_F(PrioritizedTileSetTest, NowAndReadyToDrawBin) {
+ // Ensure that tiles in NOW_AND_READY_TO_DRAW_BIN aren't sorted.
+
+ PrioritizedTileSet set;
+ TilePriority priorities[4] = {
+ TilePriorityForEventualBin(),
+ TilePriorityForNowBin(),
+ TilePriority(),
+ TilePriorityForSoonBin()};
+
+ std::vector<scoped_refptr<Tile> > tiles;
+ for (int priority = 0; priority < 4; ++priority) {
+ for (int i = 0; i < 5; ++i) {
+ scoped_refptr<Tile> tile = CreateTile();
+ tile->SetPriority(ACTIVE_TREE, priorities[priority]);
+ tile->SetPriority(PENDING_TREE, priorities[priority]);
+ tiles.push_back(tile);
+ set.InsertTile(tile, NOW_AND_READY_TO_DRAW_BIN);
+ }
+ }
+
+ // Tiles should appear in the same order as inserted.
+ int i = 0;
+ for (PrioritizedTileSet::Iterator it(&set, true);
+ it;
+ ++it) {
+ EXPECT_TRUE(*it == tiles[i].get());
+ ++i;
+ }
+ EXPECT_EQ(20, i);
+}
+
+TEST_F(PrioritizedTileSetTest, NowBin) {
+ // Ensure that tiles in NOW_BIN are sorted according to BinComparator.
+
+ PrioritizedTileSet set;
+ TilePriority priorities[4] = {
+ TilePriorityForEventualBin(),
+ TilePriorityForNowBin(),
+ TilePriority(),
+ TilePriorityForSoonBin()};
+
+ std::vector<scoped_refptr<Tile> > tiles;
+ for (int priority = 0; priority < 4; ++priority) {
+ for (int i = 0; i < 5; ++i) {
+ scoped_refptr<Tile> tile = CreateTile();
+ tile->SetPriority(ACTIVE_TREE, priorities[priority]);
+ tile->SetPriority(PENDING_TREE, priorities[priority]);
+ tiles.push_back(tile);
+ set.InsertTile(tile, NOW_BIN);
+ }
+ }
+
+ // Tiles should appear in BinComparator order.
+ std::sort(tiles.begin(), tiles.end(), BinComparator());
+
+ int i = 0;
+ for (PrioritizedTileSet::Iterator it(&set, true);
+ it;
+ ++it) {
+ EXPECT_TRUE(*it == tiles[i].get());
+ ++i;
+ }
+ EXPECT_EQ(20, i);
+}
+
+TEST_F(PrioritizedTileSetTest, SoonBin) {
+ // Ensure that tiles in SOON_BIN are sorted according to BinComparator.
+
+ PrioritizedTileSet set;
+ TilePriority priorities[4] = {
+ TilePriorityForEventualBin(),
+ TilePriorityForNowBin(),
+ TilePriority(),
+ TilePriorityForSoonBin()};
+
+ std::vector<scoped_refptr<Tile> > tiles;
+ for (int priority = 0; priority < 4; ++priority) {
+ for (int i = 0; i < 5; ++i) {
+ scoped_refptr<Tile> tile = CreateTile();
+ tile->SetPriority(ACTIVE_TREE, priorities[priority]);
+ tile->SetPriority(PENDING_TREE, priorities[priority]);
+ tiles.push_back(tile);
+ set.InsertTile(tile, SOON_BIN);
+ }
+ }
+
+ // Tiles should appear in BinComparator order.
+ std::sort(tiles.begin(), tiles.end(), BinComparator());
+
+ int i = 0;
+ for (PrioritizedTileSet::Iterator it(&set, true);
+ it;
+ ++it) {
+ EXPECT_TRUE(*it == tiles[i].get());
+ ++i;
+ }
+ EXPECT_EQ(20, i);
+}
+
+TEST_F(PrioritizedTileSetTest, SoonBinNoPriority) {
+ // Ensure that when not using priority iterator, SOON_BIN tiles
+ // are not sorted.
+
+ PrioritizedTileSet set;
+ TilePriority priorities[4] = {
+ TilePriorityForEventualBin(),
+ TilePriorityForNowBin(),
+ TilePriority(),
+ TilePriorityForSoonBin()};
+
+ std::vector<scoped_refptr<Tile> > tiles;
+ for (int priority = 0; priority < 4; ++priority) {
+ for (int i = 0; i < 5; ++i) {
+ scoped_refptr<Tile> tile = CreateTile();
+ tile->SetPriority(ACTIVE_TREE, priorities[priority]);
+ tile->SetPriority(PENDING_TREE, priorities[priority]);
+ tiles.push_back(tile);
+ set.InsertTile(tile, SOON_BIN);
+ }
+ }
+
+ int i = 0;
+ for (PrioritizedTileSet::Iterator it(&set, false);
+ it;
+ ++it) {
+ EXPECT_TRUE(*it == tiles[i].get());
+ ++i;
+ }
+ EXPECT_EQ(20, i);
+}
+
+TEST_F(PrioritizedTileSetTest, EventuallyAndActiveBin) {
+ // Ensure that EVENTUALLY_AND_ACTIVE_BIN tiles are sorted.
+
+ PrioritizedTileSet set;
+ TilePriority priorities[4] = {
+ TilePriorityForEventualBin(),
+ TilePriorityForNowBin(),
+ TilePriority(),
+ TilePriorityForSoonBin()};
+
+ std::vector<scoped_refptr<Tile> > tiles;
+ for (int priority = 0; priority < 4; ++priority) {
+ for (int i = 0; i < 5; ++i) {
+ scoped_refptr<Tile> tile = CreateTile();
+ tile->SetPriority(ACTIVE_TREE, priorities[priority]);
+ tile->SetPriority(PENDING_TREE, priorities[priority]);
+ tiles.push_back(tile);
+ set.InsertTile(tile, EVENTUALLY_AND_ACTIVE_BIN);
+ }
+ }
+
+ // Tiles should appear in BinComparator order.
+ std::sort(tiles.begin(), tiles.end(), BinComparator());
+
+ int i = 0;
+ for (PrioritizedTileSet::Iterator it(&set, true);
+ it;
+ ++it) {
+ EXPECT_TRUE(*it == tiles[i].get());
+ ++i;
+ }
+ EXPECT_EQ(20, i);
+}
+
+TEST_F(PrioritizedTileSetTest, EventuallyBin) {
+ // Ensure that EVENTUALLY_BIN tiles are sorted.
+
+ PrioritizedTileSet set;
+ TilePriority priorities[4] = {
+ TilePriorityForEventualBin(),
+ TilePriorityForNowBin(),
+ TilePriority(),
+ TilePriorityForSoonBin()};
+
+ std::vector<scoped_refptr<Tile> > tiles;
+ for (int priority = 0; priority < 4; ++priority) {
+ for (int i = 0; i < 5; ++i) {
+ scoped_refptr<Tile> tile = CreateTile();
+ tile->SetPriority(ACTIVE_TREE, priorities[priority]);
+ tile->SetPriority(PENDING_TREE, priorities[priority]);
+ tiles.push_back(tile);
+ set.InsertTile(tile, EVENTUALLY_BIN);
+ }
+ }
+
+ // Tiles should appear in BinComparator order.
+ std::sort(tiles.begin(), tiles.end(), BinComparator());
+
+ int i = 0;
+ for (PrioritizedTileSet::Iterator it(&set, true);
+ it;
+ ++it) {
+ EXPECT_TRUE(*it == tiles[i].get());
+ ++i;
+ }
+ EXPECT_EQ(20, i);
+}
+
+TEST_F(PrioritizedTileSetTest, NeverAndActiveBin) {
+ // Ensure that NEVER_AND_ACTIVE_BIN tiles are sorted.
+
+ PrioritizedTileSet set;
+ TilePriority priorities[4] = {
+ TilePriorityForEventualBin(),
+ TilePriorityForNowBin(),
+ TilePriority(),
+ TilePriorityForSoonBin()};
+
+ std::vector<scoped_refptr<Tile> > tiles;
+ for (int priority = 0; priority < 4; ++priority) {
+ for (int i = 0; i < 5; ++i) {
+ scoped_refptr<Tile> tile = CreateTile();
+ tile->SetPriority(ACTIVE_TREE, priorities[priority]);
+ tile->SetPriority(PENDING_TREE, priorities[priority]);
+ tiles.push_back(tile);
+ set.InsertTile(tile, NEVER_AND_ACTIVE_BIN);
+ }
+ }
+
+ // Tiles should appear in BinComparator order.
+ std::sort(tiles.begin(), tiles.end(), BinComparator());
+
+ int i = 0;
+ for (PrioritizedTileSet::Iterator it(&set, true);
+ it;
+ ++it) {
+ EXPECT_TRUE(*it == tiles[i].get());
+ ++i;
+ }
+ EXPECT_EQ(20, i);
+}
+
+TEST_F(PrioritizedTileSetTest, NeverBin) {
+ // Ensure that NEVER_BIN tiles are sorted, since they might not
+ // be NEVER_BIN on a LOW_PRIORITY tree.
+
+ PrioritizedTileSet set;
+ TilePriority priorities[4] = {
+ TilePriorityForEventualBin(),
+ TilePriorityForNowBin(),
+ TilePriority(),
+ TilePriorityForSoonBin()};
+
+ std::vector<scoped_refptr<Tile> > tiles;
+ for (int priority = 0; priority < 4; ++priority) {
+ for (int i = 0; i < 5; ++i) {
+ scoped_refptr<Tile> tile = CreateTile();
+ tile->SetPriority(ACTIVE_TREE, priorities[priority]);
+ tile->SetPriority(PENDING_TREE, priorities[priority]);
+ tiles.push_back(tile);
+ set.InsertTile(tile, NEVER_BIN);
+ }
+ }
+
+ // Tiles should appear in BinComparator order.
+ std::sort(tiles.begin(), tiles.end(), BinComparator());
+
+ int i = 0;
+ for (PrioritizedTileSet::Iterator it(&set, true);
+ it;
+ ++it) {
+ EXPECT_TRUE(*it == tiles[i].get());
+ ++i;
+ }
+ EXPECT_EQ(20, i);
+}
+
+TEST_F(PrioritizedTileSetTest, TilesForEachBin) {
+ // Aggregate test with one tile for each of the bins, which
+ // should appear in order of the bins.
+
+ scoped_refptr<Tile> now_and_ready_to_draw_bin = CreateTile();
+ scoped_refptr<Tile> now_bin = CreateTile();
+ scoped_refptr<Tile> soon_bin = CreateTile();
+ scoped_refptr<Tile> eventually_and_active_bin = CreateTile();
+ scoped_refptr<Tile> eventually_bin = CreateTile();
+ scoped_refptr<Tile> never_bin = CreateTile();
+ scoped_refptr<Tile> never_and_active_bin = CreateTile();
+
+ PrioritizedTileSet set;
+ set.InsertTile(soon_bin, SOON_BIN);
+ set.InsertTile(never_and_active_bin, NEVER_AND_ACTIVE_BIN);
+ set.InsertTile(eventually_bin, EVENTUALLY_BIN);
+ set.InsertTile(now_bin, NOW_BIN);
+ set.InsertTile(eventually_and_active_bin, EVENTUALLY_AND_ACTIVE_BIN);
+ set.InsertTile(never_bin, NEVER_BIN);
+ set.InsertTile(now_and_ready_to_draw_bin, NOW_AND_READY_TO_DRAW_BIN);
+
+ // Tiles should appear in order.
+ PrioritizedTileSet::Iterator it(&set, true);
+ EXPECT_TRUE(*it == now_and_ready_to_draw_bin.get());
+ ++it;
+ EXPECT_TRUE(*it == now_bin.get());
+ ++it;
+ EXPECT_TRUE(*it == soon_bin.get());
+ ++it;
+ EXPECT_TRUE(*it == eventually_and_active_bin.get());
+ ++it;
+ EXPECT_TRUE(*it == eventually_bin.get());
+ ++it;
+ EXPECT_TRUE(*it == never_and_active_bin.get());
+ ++it;
+ EXPECT_TRUE(*it == never_bin.get());
+ ++it;
+ EXPECT_FALSE(it);
+}
+
+TEST_F(PrioritizedTileSetTest, ManyTilesForEachBin) {
+ // Aggregate test with many tiles in each of the bins of various
+ // priorities. Ensure that they are all returned in a sorted order.
+
+ std::vector<scoped_refptr<Tile> > now_and_ready_to_draw_bins;
+ std::vector<scoped_refptr<Tile> > now_bins;
+ std::vector<scoped_refptr<Tile> > soon_bins;
+ std::vector<scoped_refptr<Tile> > eventually_and_active_bins;
+ std::vector<scoped_refptr<Tile> > eventually_bins;
+ std::vector<scoped_refptr<Tile> > never_bins;
+ std::vector<scoped_refptr<Tile> > never_and_active_bins;
+
+ TilePriority priorities[4] = {
+ TilePriorityForEventualBin(),
+ TilePriorityForNowBin(),
+ TilePriority(),
+ TilePriorityForSoonBin()};
+
+ PrioritizedTileSet set;
+ for (int priority = 0; priority < 4; ++priority) {
+ for (int i = 0; i < 5; ++i) {
+ scoped_refptr<Tile> tile = CreateTile();
+ tile->SetPriority(ACTIVE_TREE, priorities[priority]);
+ tile->SetPriority(PENDING_TREE, priorities[priority]);
+
+ now_and_ready_to_draw_bins.push_back(tile);
+ now_bins.push_back(tile);
+ soon_bins.push_back(tile);
+ eventually_and_active_bins.push_back(tile);
+ eventually_bins.push_back(tile);
+ never_bins.push_back(tile);
+ never_and_active_bins.push_back(tile);
+
+ set.InsertTile(tile, NOW_AND_READY_TO_DRAW_BIN);
+ set.InsertTile(tile, NOW_BIN);
+ set.InsertTile(tile, SOON_BIN);
+ set.InsertTile(tile, EVENTUALLY_AND_ACTIVE_BIN);
+ set.InsertTile(tile, EVENTUALLY_BIN);
+ set.InsertTile(tile, NEVER_BIN);
+ set.InsertTile(tile, NEVER_AND_ACTIVE_BIN);
+ }
+ }
+
+ PrioritizedTileSet::Iterator it(&set, true);
+ std::vector<scoped_refptr<Tile> >::iterator vector_it;
+
+ // Now and ready are not sorted.
+ for (vector_it = now_and_ready_to_draw_bins.begin();
+ vector_it != now_and_ready_to_draw_bins.end();
+ ++vector_it) {
+ EXPECT_TRUE(*vector_it == *it);
+ ++it;
+ }
+
+ // Now bins are sorted.
+ std::sort(now_bins.begin(), now_bins.end(), BinComparator());
+ for (vector_it = now_bins.begin(); vector_it != now_bins.end(); ++vector_it) {
+ EXPECT_TRUE(*vector_it == *it);
+ ++it;
+ }
+
+ // Soon bins are sorted.
+ std::sort(soon_bins.begin(), soon_bins.end(), BinComparator());
+ for (vector_it = soon_bins.begin(); vector_it != soon_bins.end();
+ ++vector_it) {
+ EXPECT_TRUE(*vector_it == *it);
+ ++it;
+ }
+
+ // Eventually and active bins are sorted.
+ std::sort(eventually_and_active_bins.begin(),
+ eventually_and_active_bins.end(),
+ BinComparator());
+ for (vector_it = eventually_and_active_bins.begin();
+ vector_it != eventually_and_active_bins.end();
+ ++vector_it) {
+ EXPECT_TRUE(*vector_it == *it);
+ ++it;
+ }
+
+ // Eventually bins are sorted.
+ std::sort(eventually_bins.begin(), eventually_bins.end(), BinComparator());
+ for (vector_it = eventually_bins.begin(); vector_it != eventually_bins.end();
+ ++vector_it) {
+ EXPECT_TRUE(*vector_it == *it);
+ ++it;
+ }
+
+ // Never and active bins are sorted.
+ std::sort(never_and_active_bins.begin(),
+ never_and_active_bins.end(),
+ BinComparator());
+ for (vector_it = never_and_active_bins.begin();
+ vector_it != never_and_active_bins.end();
+ ++vector_it) {
+ EXPECT_TRUE(*vector_it == *it);
+ ++it;
+ }
+
+ // Never bins are sorted.
+ std::sort(never_bins.begin(), never_bins.end(), BinComparator());
+ for (vector_it = never_bins.begin(); vector_it != never_bins.end();
+ ++vector_it) {
+ EXPECT_TRUE(*vector_it == *it);
+ ++it;
+ }
+
+ EXPECT_FALSE(it);
+}
+
+TEST_F(PrioritizedTileSetTest, ManyTilesForEachBinDisablePriority) {
+ // Aggregate test with many tiles for each of the bins. Tiles should
+ // appear in order, until DisablePriorityOrdering is called. After that
+ // tiles should appear in the order they were inserted.
+
+ std::vector<scoped_refptr<Tile> > now_and_ready_to_draw_bins;
+ std::vector<scoped_refptr<Tile> > now_bins;
+ std::vector<scoped_refptr<Tile> > soon_bins;
+ std::vector<scoped_refptr<Tile> > eventually_and_active_bins;
+ std::vector<scoped_refptr<Tile> > eventually_bins;
+ std::vector<scoped_refptr<Tile> > never_bins;
+ std::vector<scoped_refptr<Tile> > never_and_active_bins;
+
+ TilePriority priorities[4] = {
+ TilePriorityForEventualBin(),
+ TilePriorityForNowBin(),
+ TilePriority(),
+ TilePriorityForSoonBin()};
+
+ PrioritizedTileSet set;
+ for (int priority = 0; priority < 4; ++priority) {
+ for (int i = 0; i < 5; ++i) {
+ scoped_refptr<Tile> tile = CreateTile();
+ tile->SetPriority(ACTIVE_TREE, priorities[priority]);
+ tile->SetPriority(PENDING_TREE, priorities[priority]);
+
+ now_and_ready_to_draw_bins.push_back(tile);
+ now_bins.push_back(tile);
+ soon_bins.push_back(tile);
+ eventually_and_active_bins.push_back(tile);
+ eventually_bins.push_back(tile);
+ never_bins.push_back(tile);
+ never_and_active_bins.push_back(tile);
+
+ set.InsertTile(tile, NOW_AND_READY_TO_DRAW_BIN);
+ set.InsertTile(tile, NOW_BIN);
+ set.InsertTile(tile, SOON_BIN);
+ set.InsertTile(tile, EVENTUALLY_AND_ACTIVE_BIN);
+ set.InsertTile(tile, EVENTUALLY_BIN);
+ set.InsertTile(tile, NEVER_BIN);
+ set.InsertTile(tile, NEVER_AND_ACTIVE_BIN);
+ }
+ }
+
+ PrioritizedTileSet::Iterator it(&set, true);
+ std::vector<scoped_refptr<Tile> >::iterator vector_it;
+
+ // Now and ready are not sorted.
+ for (vector_it = now_and_ready_to_draw_bins.begin();
+ vector_it != now_and_ready_to_draw_bins.end();
+ ++vector_it) {
+ EXPECT_TRUE(*vector_it == *it);
+ ++it;
+ }
+
+ // Now bins are sorted.
+ std::sort(now_bins.begin(), now_bins.end(), BinComparator());
+ for (vector_it = now_bins.begin(); vector_it != now_bins.end(); ++vector_it) {
+ EXPECT_TRUE(*vector_it == *it);
+ ++it;
+ }
+
+ // Soon bins are sorted.
+ std::sort(soon_bins.begin(), soon_bins.end(), BinComparator());
+ for (vector_it = soon_bins.begin(); vector_it != soon_bins.end();
+ ++vector_it) {
+ EXPECT_TRUE(*vector_it == *it);
+ ++it;
+ }
+
+ // After we disable priority ordering, we already have sorted the next vector.
+ it.DisablePriorityOrdering();
+
+ // Eventually and active bins are sorted.
+ std::sort(eventually_and_active_bins.begin(),
+ eventually_and_active_bins.end(),
+ BinComparator());
+ for (vector_it = eventually_and_active_bins.begin();
+ vector_it != eventually_and_active_bins.end();
+ ++vector_it) {
+ EXPECT_TRUE(*vector_it == *it);
+ ++it;
+ }
+
+ // Eventually bins are not sorted.
+ for (vector_it = eventually_bins.begin(); vector_it != eventually_bins.end();
+ ++vector_it) {
+ EXPECT_TRUE(*vector_it == *it);
+ ++it;
+ }
+
+ // Never and active bins are not sorted.
+ for (vector_it = never_and_active_bins.begin();
+ vector_it != never_and_active_bins.end();
+ ++vector_it) {
+ EXPECT_TRUE(*vector_it == *it);
+ ++it;
+ }
+
+ // Never bins are not sorted.
+ for (vector_it = never_bins.begin(); vector_it != never_bins.end();
+ ++vector_it) {
+ EXPECT_TRUE(*vector_it == *it);
+ ++it;
+ }
+
+ EXPECT_FALSE(it);
+}
+
+TEST_F(PrioritizedTileSetTest, TilesForFirstAndLastBins) {
+ // Make sure that if we have empty lists between two non-empty lists,
+ // we just get two tiles from the iterator.
+
+ scoped_refptr<Tile> now_and_ready_to_draw_bin = CreateTile();
+ scoped_refptr<Tile> never_bin = CreateTile();
+
+ PrioritizedTileSet set;
+ set.InsertTile(never_bin, NEVER_BIN);
+ set.InsertTile(now_and_ready_to_draw_bin, NOW_AND_READY_TO_DRAW_BIN);
+
+ // Only two tiles should appear and they should appear in order.
+ PrioritizedTileSet::Iterator it(&set, true);
+ EXPECT_TRUE(*it == now_and_ready_to_draw_bin.get());
+ ++it;
+ EXPECT_TRUE(*it == never_bin.get());
+ ++it;
+ EXPECT_FALSE(it);
+}
+
+TEST_F(PrioritizedTileSetTest, MultipleIterators) {
+ // Ensure that multiple iterators don't interfere with each other.
+
+ scoped_refptr<Tile> now_and_ready_to_draw_bin = CreateTile();
+ scoped_refptr<Tile> now_bin = CreateTile();
+ scoped_refptr<Tile> soon_bin = CreateTile();
+ scoped_refptr<Tile> eventually_bin = CreateTile();
+ scoped_refptr<Tile> never_bin = CreateTile();
+
+ PrioritizedTileSet set;
+ set.InsertTile(soon_bin, SOON_BIN);
+ set.InsertTile(eventually_bin, EVENTUALLY_BIN);
+ set.InsertTile(now_bin, NOW_BIN);
+ set.InsertTile(never_bin, NEVER_BIN);
+ set.InsertTile(now_and_ready_to_draw_bin, NOW_AND_READY_TO_DRAW_BIN);
+
+ // Tiles should appear in order.
+ PrioritizedTileSet::Iterator it(&set, true);
+ EXPECT_TRUE(*it == now_and_ready_to_draw_bin.get());
+ ++it;
+ EXPECT_TRUE(*it == now_bin.get());
+ ++it;
+ EXPECT_TRUE(*it == soon_bin.get());
+ ++it;
+ EXPECT_TRUE(*it == eventually_bin.get());
+ ++it;
+ EXPECT_TRUE(*it == never_bin.get());
+ ++it;
+ EXPECT_FALSE(it);
+
+ // Creating multiple iterators shouldn't affect old iterators.
+ PrioritizedTileSet::Iterator second_it(&set, true);
+ EXPECT_TRUE(second_it);
+ EXPECT_FALSE(it);
+
+ ++second_it;
+ EXPECT_TRUE(second_it);
+ ++second_it;
+ EXPECT_TRUE(second_it);
+ EXPECT_FALSE(it);
+
+ PrioritizedTileSet::Iterator third_it(&set, true);
+ EXPECT_TRUE(third_it);
+ ++second_it;
+ ++second_it;
+ EXPECT_TRUE(second_it);
+ EXPECT_TRUE(third_it);
+ EXPECT_FALSE(it);
+
+ ++third_it;
+ ++third_it;
+ EXPECT_TRUE(third_it);
+ EXPECT_TRUE(*third_it == soon_bin.get());
+ EXPECT_TRUE(second_it);
+ EXPECT_TRUE(*second_it == never_bin.get());
+ EXPECT_FALSE(it);
+
+ ++second_it;
+ EXPECT_TRUE(third_it);
+ EXPECT_FALSE(second_it);
+ EXPECT_FALSE(it);
+
+ set.Clear();
+
+ PrioritizedTileSet::Iterator empty_it(&set, true);
+ EXPECT_FALSE(empty_it);
+}
+
+} // namespace
+} // namespace cc
+
diff --git a/chromium/cc/resources/priority_calculator.cc b/chromium/cc/resources/priority_calculator.cc
new file mode 100644
index 00000000000..4e828207ff1
--- /dev/null
+++ b/chromium/cc/resources/priority_calculator.cc
@@ -0,0 +1,125 @@
+// 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 "cc/resources/priority_calculator.h"
+
+#include <algorithm>
+
+#include "ui/gfx/rect.h"
+
+namespace cc {
+
+static const int kNothingPriorityCutoff = -3;
+
+static const int kMostHighPriority = -2;
+
+static const int kUIDrawsToRootSurfacePriority = -1;
+static const int kVisibleDrawsToRootSurfacePriority = 0;
+static const int kRenderSurfacesPriority = 1;
+static const int kUIDoesNotDrawToRootSurfacePriority = 2;
+static const int kVisibleDoesNotDrawToRootSurfacePriority = 3;
+
+static const int kVisibleOnlyPriorityCutoff = 4;
+
+// The lower digits are how far from being visible the texture is,
+// in pixels.
+static const int kNotVisibleBasePriority = 1000000;
+static const int kNotVisibleLimitPriority = 1900000;
+
+// Arbitrarily define "nearby" to be 2000 pixels. A better estimate
+// would be percent-of-viewport or percent-of-screen.
+static const int kVisibleAndNearbyPriorityCutoff =
+ kNotVisibleBasePriority + 2000;
+
+// Small animated layers are treated as though they are 512 pixels
+// from being visible.
+static const int kSmallAnimatedLayerPriority = kNotVisibleBasePriority + 512;
+
+static const int kLingeringBasePriority = 2000000;
+static const int kLingeringLimitPriority = 2900000;
+
+static const int kMostLowPriority = 3000000;
+
+static const int kEverythingPriorityCutoff = 3000001;
+
+// static
+int PriorityCalculator::UIPriority(bool draws_to_root_surface) {
+ return draws_to_root_surface ? kUIDrawsToRootSurfacePriority
+ : kUIDoesNotDrawToRootSurfacePriority;
+}
+
+// static
+int PriorityCalculator::VisiblePriority(bool draws_to_root_surface) {
+ return draws_to_root_surface ? kVisibleDrawsToRootSurfacePriority
+ : kVisibleDoesNotDrawToRootSurfacePriority;
+}
+
+// static
+int PriorityCalculator::RenderSurfacePriority() {
+ return kRenderSurfacesPriority;
+}
+
+// static
+int PriorityCalculator::LingeringPriority(int previous_priority) {
+ // TODO(reveman): We should remove this once we have priorities for all
+ // textures (we can't currently calculate distances for off-screen textures).
+ return std::min(kLingeringLimitPriority,
+ std::max(kLingeringBasePriority, previous_priority + 1));
+}
+
+namespace {
+int ManhattanDistance(gfx::Rect a, gfx::Rect b) {
+ gfx::Rect c = gfx::UnionRects(a, b);
+ int x = std::max(0, c.width() - a.width() - b.width() + 1);
+ int y = std::max(0, c.height() - a.height() - b.height() + 1);
+ return (x + y);
+}
+}
+
+// static
+int PriorityCalculator::PriorityFromDistance(gfx::Rect visible_rect,
+ gfx::Rect texture_rect,
+ bool draws_to_root_surface) {
+ int distance = ManhattanDistance(visible_rect, texture_rect);
+ if (!distance)
+ return VisiblePriority(draws_to_root_surface);
+ return std::min(kNotVisibleLimitPriority, kNotVisibleBasePriority + distance);
+}
+
+// static
+int PriorityCalculator::SmallAnimatedLayerMinPriority() {
+ return kSmallAnimatedLayerPriority;
+}
+
+// static
+int PriorityCalculator::HighestPriority() {
+ return kMostHighPriority;
+}
+
+// static
+int PriorityCalculator::LowestPriority() {
+ return kMostLowPriority;
+}
+
+// static
+int PriorityCalculator::AllowNothingCutoff() {
+ return kNothingPriorityCutoff;
+}
+
+// static
+int PriorityCalculator::AllowVisibleOnlyCutoff() {
+ return kVisibleOnlyPriorityCutoff;
+}
+
+// static
+int PriorityCalculator::AllowVisibleAndNearbyCutoff() {
+ return kVisibleAndNearbyPriorityCutoff;
+}
+
+// static
+int PriorityCalculator::AllowEverythingCutoff() {
+ return kEverythingPriorityCutoff;
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/priority_calculator.h b/chromium/cc/resources/priority_calculator.h
new file mode 100644
index 00000000000..877896176b8
--- /dev/null
+++ b/chromium/cc/resources/priority_calculator.h
@@ -0,0 +1,47 @@
+// Copyright 2010 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.
+
+#ifndef CC_RESOURCES_PRIORITY_CALCULATOR_H_
+#define CC_RESOURCES_PRIORITY_CALCULATOR_H_
+
+#include "base/basictypes.h"
+#include "cc/base/cc_export.h"
+
+namespace gfx { class Rect; }
+
+namespace cc {
+
+class CC_EXPORT PriorityCalculator {
+ public:
+ PriorityCalculator() {}
+
+ static int UIPriority(bool draws_to_root_surface);
+ static int VisiblePriority(bool draws_to_root_surface);
+ static int RenderSurfacePriority();
+ static int LingeringPriority(int previous_priority);
+ static int PriorityFromDistance(gfx::Rect visible_rect,
+ gfx::Rect texture_rect,
+ bool draws_to_root_surface);
+ static int SmallAnimatedLayerMinPriority();
+
+ static int HighestPriority();
+ static int LowestPriority();
+ static inline bool priority_is_lower(int a, int b) { return a > b; }
+ static inline bool priority_is_higher(int a, int b) { return a < b; }
+ static inline int max_priority(int a, int b) {
+ return priority_is_higher(a, b) ? a : b;
+ }
+
+ static int AllowNothingCutoff();
+ static int AllowVisibleOnlyCutoff();
+ static int AllowVisibleAndNearbyCutoff();
+ static int AllowEverythingCutoff();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PriorityCalculator);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_PRIORITY_CALCULATOR_H_
diff --git a/chromium/cc/resources/raster_mode.cc b/chromium/cc/resources/raster_mode.cc
new file mode 100644
index 00000000000..b35bc0be42d
--- /dev/null
+++ b/chromium/cc/resources/raster_mode.cc
@@ -0,0 +1,32 @@
+// Copyright 2013 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 "cc/resources/raster_mode.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+
+namespace cc {
+
+scoped_ptr<base::Value> RasterModeAsValue(RasterMode raster_mode) {
+ switch (raster_mode) {
+ case HIGH_QUALITY_NO_LCD_RASTER_MODE:
+ return scoped_ptr<base::Value>(
+ base::Value::CreateStringValue("HIGH_QUALITY_NO_LCD_RASTER_MODE"));
+ case HIGH_QUALITY_RASTER_MODE:
+ return scoped_ptr<base::Value>(
+ base::Value::CreateStringValue("HIGH_QUALITY_RASTER_MODE"));
+ case LOW_QUALITY_RASTER_MODE:
+ return scoped_ptr<base::Value>(
+ base::Value::CreateStringValue("LOW_QUALITY_RASTER_MODE"));
+ case NUM_RASTER_MODES:
+ default:
+ NOTREACHED() << "Unrecognized RasterMode value " << raster_mode;
+ return scoped_ptr<base::Value>(
+ base::Value::CreateStringValue("<unknown RasterMode value>"));
+ }
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/raster_mode.h b/chromium/cc/resources/raster_mode.h
new file mode 100644
index 00000000000..9868059e0b7
--- /dev/null
+++ b/chromium/cc/resources/raster_mode.h
@@ -0,0 +1,31 @@
+// Copyright 2013 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.
+
+#ifndef CC_RESOURCES_RASTER_MODE_H_
+#define CC_RESOURCES_RASTER_MODE_H_
+
+#include "base/memory/scoped_ptr.h"
+
+namespace base {
+class Value;
+}
+
+namespace cc {
+
+// Low quality implies no lcd test;
+// high quality implies lcd text.
+// Note that the order of these matters, from "better" to "worse" in terms of
+// quality.
+enum RasterMode {
+ HIGH_QUALITY_NO_LCD_RASTER_MODE = 0,
+ HIGH_QUALITY_RASTER_MODE = 1,
+ LOW_QUALITY_RASTER_MODE = 2,
+ NUM_RASTER_MODES = 3
+};
+
+scoped_ptr<base::Value> RasterModeAsValue(RasterMode mode);
+
+} // namespace cc
+
+#endif // CC_RESOURCES_RASTER_MODE_H_
diff --git a/chromium/cc/resources/raster_worker_pool.cc b/chromium/cc/resources/raster_worker_pool.cc
new file mode 100644
index 00000000000..aced03fcec6
--- /dev/null
+++ b/chromium/cc/resources/raster_worker_pool.cc
@@ -0,0 +1,543 @@
+// Copyright 2013 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 "cc/resources/raster_worker_pool.h"
+
+#include "base/json/json_writer.h"
+#include "base/metrics/histogram.h"
+#include "base/values.h"
+#include "cc/debug/benchmark_instrumentation.h"
+#include "cc/debug/devtools_instrumentation.h"
+#include "cc/debug/traced_value.h"
+#include "cc/resources/picture_pile_impl.h"
+#include "skia/ext/lazy_pixel_ref.h"
+#include "skia/ext/paint_simplifier.h"
+
+namespace cc {
+
+namespace {
+
+// Flag to indicate whether we should try and detect that
+// a tile is of solid color.
+const bool kUseColorEstimator = true;
+
+class DisableLCDTextFilter : public SkDrawFilter {
+ public:
+ // SkDrawFilter interface.
+ virtual bool filter(SkPaint* paint, SkDrawFilter::Type type) OVERRIDE {
+ if (type != SkDrawFilter::kText_Type)
+ return true;
+
+ paint->setLCDRenderText(false);
+ return true;
+ }
+};
+
+class RasterWorkerPoolTaskImpl : public internal::RasterWorkerPoolTask {
+ public:
+ RasterWorkerPoolTaskImpl(const Resource* resource,
+ PicturePileImpl* picture_pile,
+ gfx::Rect content_rect,
+ float contents_scale,
+ RasterMode raster_mode,
+ bool is_tile_in_pending_tree_now_bin,
+ TileResolution tile_resolution,
+ int layer_id,
+ const void* tile_id,
+ int source_frame_number,
+ RenderingStatsInstrumentation* rendering_stats,
+ const RasterWorkerPool::RasterTask::Reply& reply,
+ TaskVector* dependencies)
+ : internal::RasterWorkerPoolTask(resource, dependencies),
+ picture_pile_(picture_pile),
+ content_rect_(content_rect),
+ contents_scale_(contents_scale),
+ raster_mode_(raster_mode),
+ is_tile_in_pending_tree_now_bin_(is_tile_in_pending_tree_now_bin),
+ tile_resolution_(tile_resolution),
+ layer_id_(layer_id),
+ tile_id_(tile_id),
+ source_frame_number_(source_frame_number),
+ rendering_stats_(rendering_stats),
+ reply_(reply) {}
+
+ void RunAnalysisOnThread(unsigned thread_index) {
+ TRACE_EVENT1("cc",
+ "RasterWorkerPoolTaskImpl::RunAnalysisOnThread",
+ "data",
+ TracedValue::FromValue(DataAsValue().release()));
+
+ DCHECK(picture_pile_.get());
+ DCHECK(rendering_stats_);
+
+ PicturePileImpl* picture_clone =
+ picture_pile_->GetCloneForDrawingOnThread(thread_index);
+
+ DCHECK(picture_clone);
+
+ base::TimeTicks start_time = rendering_stats_->StartRecording();
+ picture_clone->AnalyzeInRect(content_rect_, contents_scale_, &analysis_);
+ base::TimeDelta duration = rendering_stats_->EndRecording(start_time);
+
+ // Record the solid color prediction.
+ UMA_HISTOGRAM_BOOLEAN("Renderer4.SolidColorTilesAnalyzed",
+ analysis_.is_solid_color);
+ rendering_stats_->AddAnalysisResult(duration, analysis_.is_solid_color);
+
+ // Clear the flag if we're not using the estimator.
+ analysis_.is_solid_color &= kUseColorEstimator;
+ }
+
+ bool RunRasterOnThread(SkDevice* device, unsigned thread_index) {
+ TRACE_EVENT2(
+ benchmark_instrumentation::kCategory,
+ benchmark_instrumentation::kRunRasterOnThread,
+ benchmark_instrumentation::kData,
+ TracedValue::FromValue(DataAsValue().release()),
+ "raster_mode",
+ TracedValue::FromValue(RasterModeAsValue(raster_mode_).release()));
+
+ devtools_instrumentation::ScopedLayerTask raster_task(
+ devtools_instrumentation::kRasterTask, layer_id_);
+
+ DCHECK(picture_pile_.get());
+ DCHECK(device);
+
+ if (analysis_.is_solid_color)
+ return false;
+
+ PicturePileImpl* picture_clone =
+ picture_pile_->GetCloneForDrawingOnThread(thread_index);
+
+ SkCanvas canvas(device);
+
+ skia::RefPtr<SkDrawFilter> draw_filter;
+ switch (raster_mode_) {
+ case LOW_QUALITY_RASTER_MODE:
+ draw_filter = skia::AdoptRef(new skia::PaintSimplifier);
+ break;
+ case HIGH_QUALITY_NO_LCD_RASTER_MODE:
+ draw_filter = skia::AdoptRef(new DisableLCDTextFilter);
+ break;
+ case HIGH_QUALITY_RASTER_MODE:
+ break;
+ case NUM_RASTER_MODES:
+ default:
+ NOTREACHED();
+ }
+
+ canvas.setDrawFilter(draw_filter.get());
+
+ if (rendering_stats_->record_rendering_stats()) {
+ PicturePileImpl::RasterStats raster_stats;
+ picture_clone->RasterToBitmap(
+ &canvas, content_rect_, contents_scale_, &raster_stats);
+ rendering_stats_->AddRaster(
+ raster_stats.total_rasterize_time,
+ raster_stats.best_rasterize_time,
+ raster_stats.total_pixels_rasterized,
+ is_tile_in_pending_tree_now_bin_);
+
+ HISTOGRAM_CUSTOM_COUNTS(
+ "Renderer4.PictureRasterTimeUS",
+ raster_stats.total_rasterize_time.InMicroseconds(),
+ 0,
+ 100000,
+ 100);
+ } else {
+ picture_clone->RasterToBitmap(
+ &canvas, content_rect_, contents_scale_, NULL);
+ }
+ return true;
+ }
+
+ // Overridden from internal::RasterWorkerPoolTask:
+ virtual bool RunOnWorkerThread(SkDevice* device, unsigned thread_index)
+ OVERRIDE {
+ RunAnalysisOnThread(thread_index);
+ return RunRasterOnThread(device, thread_index);
+ }
+ virtual void CompleteOnOriginThread() OVERRIDE {
+ reply_.Run(analysis_, !HasFinishedRunning() || WasCanceled());
+ }
+
+ protected:
+ virtual ~RasterWorkerPoolTaskImpl() {}
+
+ private:
+ scoped_ptr<base::Value> DataAsValue() const {
+ scoped_ptr<base::DictionaryValue> res(new base::DictionaryValue());
+ res->Set("tile_id", TracedValue::CreateIDRef(tile_id_).release());
+ res->SetBoolean("is_tile_in_pending_tree_now_bin",
+ is_tile_in_pending_tree_now_bin_);
+ res->Set("resolution", TileResolutionAsValue(tile_resolution_).release());
+ res->SetInteger("source_frame_number", source_frame_number_);
+ res->SetInteger("layer_id", layer_id_);
+ return res.PassAs<base::Value>();
+ }
+
+ PicturePileImpl::Analysis analysis_;
+ scoped_refptr<PicturePileImpl> picture_pile_;
+ gfx::Rect content_rect_;
+ float contents_scale_;
+ RasterMode raster_mode_;
+ bool is_tile_in_pending_tree_now_bin_;
+ TileResolution tile_resolution_;
+ int layer_id_;
+ const void* tile_id_;
+ int source_frame_number_;
+ RenderingStatsInstrumentation* rendering_stats_;
+ const RasterWorkerPool::RasterTask::Reply reply_;
+
+ DISALLOW_COPY_AND_ASSIGN(RasterWorkerPoolTaskImpl);
+};
+
+class ImageDecodeWorkerPoolTaskImpl : public internal::WorkerPoolTask {
+ public:
+ ImageDecodeWorkerPoolTaskImpl(skia::LazyPixelRef* pixel_ref,
+ int layer_id,
+ RenderingStatsInstrumentation* rendering_stats,
+ const RasterWorkerPool::Task::Reply& reply)
+ : pixel_ref_(pixel_ref),
+ layer_id_(layer_id),
+ rendering_stats_(rendering_stats),
+ reply_(reply) {}
+
+ // Overridden from internal::WorkerPoolTask:
+ virtual void RunOnWorkerThread(unsigned thread_index) OVERRIDE {
+ TRACE_EVENT0("cc", "ImageDecodeWorkerPoolTaskImpl::RunOnWorkerThread");
+ devtools_instrumentation::ScopedLayerTask image_decode_task(
+ devtools_instrumentation::kImageDecodeTask, layer_id_);
+ base::TimeTicks start_time = rendering_stats_->StartRecording();
+ pixel_ref_->Decode();
+ base::TimeDelta duration = rendering_stats_->EndRecording(start_time);
+ rendering_stats_->AddDeferredImageDecode(duration);
+ }
+ virtual void CompleteOnOriginThread() OVERRIDE {
+ reply_.Run(!HasFinishedRunning());
+ }
+
+ protected:
+ virtual ~ImageDecodeWorkerPoolTaskImpl() {}
+
+ private:
+ skia::LazyPixelRef* pixel_ref_;
+ int layer_id_;
+ RenderingStatsInstrumentation* rendering_stats_;
+ const RasterWorkerPool::Task::Reply reply_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImageDecodeWorkerPoolTaskImpl);
+};
+
+class RasterFinishedWorkerPoolTaskImpl : public internal::WorkerPoolTask {
+ public:
+ typedef base::Callback<void(const internal::WorkerPoolTask* source)>
+ Callback;
+
+ RasterFinishedWorkerPoolTaskImpl(
+ const Callback& on_raster_finished_callback)
+ : origin_loop_(base::MessageLoopProxy::current().get()),
+ on_raster_finished_callback_(on_raster_finished_callback) {
+ }
+
+ // Overridden from internal::WorkerPoolTask:
+ virtual void RunOnWorkerThread(unsigned thread_index) OVERRIDE {
+ TRACE_EVENT0("cc", "RasterFinishedWorkerPoolTaskImpl::RunOnWorkerThread");
+ origin_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&RasterFinishedWorkerPoolTaskImpl::RunOnOriginThread,
+ this));
+ }
+ virtual void CompleteOnOriginThread() OVERRIDE {}
+
+ private:
+ virtual ~RasterFinishedWorkerPoolTaskImpl() {}
+
+ void RunOnOriginThread() const {
+ on_raster_finished_callback_.Run(this);
+ }
+
+ scoped_refptr<base::MessageLoopProxy> origin_loop_;
+ const Callback on_raster_finished_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(RasterFinishedWorkerPoolTaskImpl);
+};
+
+const char* kWorkerThreadNamePrefix = "CompositorRaster";
+
+} // namespace
+
+namespace internal {
+
+RasterWorkerPoolTask::RasterWorkerPoolTask(
+ const Resource* resource, TaskVector* dependencies)
+ : did_run_(false),
+ did_complete_(false),
+ was_canceled_(false),
+ resource_(resource) {
+ dependencies_.swap(*dependencies);
+}
+
+RasterWorkerPoolTask::~RasterWorkerPoolTask() {
+}
+
+void RasterWorkerPoolTask::DidRun(bool was_canceled) {
+ DCHECK(!did_run_);
+ did_run_ = true;
+ was_canceled_ = was_canceled;
+}
+
+bool RasterWorkerPoolTask::HasFinishedRunning() const {
+ return did_run_;
+}
+
+bool RasterWorkerPoolTask::WasCanceled() const {
+ return was_canceled_;
+}
+
+void RasterWorkerPoolTask::WillComplete() {
+ DCHECK(!did_complete_);
+}
+
+void RasterWorkerPoolTask::DidComplete() {
+ DCHECK(!did_complete_);
+ did_complete_ = true;
+}
+
+bool RasterWorkerPoolTask::HasCompleted() const {
+ return did_complete_;
+}
+
+} // namespace internal
+
+RasterWorkerPool::Task::Set::Set() {
+}
+
+RasterWorkerPool::Task::Set::~Set() {
+}
+
+void RasterWorkerPool::Task::Set::Insert(const Task& task) {
+ DCHECK(!task.is_null());
+ tasks_.push_back(task.internal_);
+}
+
+RasterWorkerPool::Task::Task() {
+}
+
+RasterWorkerPool::Task::Task(internal::WorkerPoolTask* internal)
+ : internal_(internal) {
+}
+
+RasterWorkerPool::Task::~Task() {
+}
+
+void RasterWorkerPool::Task::Reset() {
+ internal_ = NULL;
+}
+
+RasterWorkerPool::RasterTask::Queue::Queue() {
+}
+
+RasterWorkerPool::RasterTask::Queue::~Queue() {
+}
+
+void RasterWorkerPool::RasterTask::Queue::Append(
+ const RasterTask& task, bool required_for_activation) {
+ DCHECK(!task.is_null());
+ tasks_.push_back(task.internal_);
+ if (required_for_activation)
+ tasks_required_for_activation_.insert(task.internal_.get());
+}
+
+RasterWorkerPool::RasterTask::RasterTask() {
+}
+
+RasterWorkerPool::RasterTask::RasterTask(
+ internal::RasterWorkerPoolTask* internal)
+ : internal_(internal) {
+}
+
+void RasterWorkerPool::RasterTask::Reset() {
+ internal_ = NULL;
+}
+
+RasterWorkerPool::RasterTask::~RasterTask() {
+}
+
+// static
+RasterWorkerPool::RasterTask RasterWorkerPool::CreateRasterTask(
+ const Resource* resource,
+ PicturePileImpl* picture_pile,
+ gfx::Rect content_rect,
+ float contents_scale,
+ RasterMode raster_mode,
+ bool is_tile_in_pending_tree_now_bin,
+ TileResolution tile_resolution,
+ int layer_id,
+ const void* tile_id,
+ int source_frame_number,
+ RenderingStatsInstrumentation* rendering_stats,
+ const RasterTask::Reply& reply,
+ Task::Set* dependencies) {
+ return RasterTask(
+ new RasterWorkerPoolTaskImpl(resource,
+ picture_pile,
+ content_rect,
+ contents_scale,
+ raster_mode,
+ is_tile_in_pending_tree_now_bin,
+ tile_resolution,
+ layer_id,
+ tile_id,
+ source_frame_number,
+ rendering_stats,
+ reply,
+ &dependencies->tasks_));
+}
+
+// static
+RasterWorkerPool::Task RasterWorkerPool::CreateImageDecodeTask(
+ skia::LazyPixelRef* pixel_ref,
+ int layer_id,
+ RenderingStatsInstrumentation* stats_instrumentation,
+ const Task::Reply& reply) {
+ return Task(new ImageDecodeWorkerPoolTaskImpl(pixel_ref,
+ layer_id,
+ stats_instrumentation,
+ reply));
+}
+
+RasterWorkerPool::RasterWorkerPool(ResourceProvider* resource_provider,
+ size_t num_threads)
+ : WorkerPool(num_threads, kWorkerThreadNamePrefix),
+ client_(NULL),
+ resource_provider_(resource_provider),
+ weak_ptr_factory_(this) {
+}
+
+RasterWorkerPool::~RasterWorkerPool() {
+}
+
+void RasterWorkerPool::SetClient(RasterWorkerPoolClient* client) {
+ client_ = client;
+}
+
+void RasterWorkerPool::Shutdown() {
+ raster_tasks_.clear();
+ TaskGraph empty;
+ SetTaskGraph(&empty);
+ WorkerPool::Shutdown();
+ weak_ptr_factory_.InvalidateWeakPtrs();
+}
+
+void RasterWorkerPool::SetRasterTasks(RasterTask::Queue* queue) {
+ raster_tasks_.swap(queue->tasks_);
+ raster_tasks_required_for_activation_.swap(
+ queue->tasks_required_for_activation_);
+}
+
+bool RasterWorkerPool::IsRasterTaskRequiredForActivation(
+ internal::RasterWorkerPoolTask* task) const {
+ return
+ raster_tasks_required_for_activation_.find(task) !=
+ raster_tasks_required_for_activation_.end();
+}
+
+scoped_refptr<internal::WorkerPoolTask>
+ RasterWorkerPool::CreateRasterFinishedTask() {
+ return make_scoped_refptr(
+ new RasterFinishedWorkerPoolTaskImpl(
+ base::Bind(&RasterWorkerPool::OnRasterFinished,
+ weak_ptr_factory_.GetWeakPtr())));
+}
+
+scoped_refptr<internal::WorkerPoolTask>
+ RasterWorkerPool::CreateRasterRequiredForActivationFinishedTask() {
+ return make_scoped_refptr(
+ new RasterFinishedWorkerPoolTaskImpl(
+ base::Bind(&RasterWorkerPool::OnRasterRequiredForActivationFinished,
+ weak_ptr_factory_.GetWeakPtr())));
+}
+
+void RasterWorkerPool::OnRasterFinished(
+ const internal::WorkerPoolTask* source) {
+ TRACE_EVENT0("cc", "RasterWorkerPool::OnRasterFinished");
+
+ // Early out if current |raster_finished_task_| is not the source.
+ if (source != raster_finished_task_.get())
+ return;
+
+ OnRasterTasksFinished();
+}
+
+void RasterWorkerPool::OnRasterRequiredForActivationFinished(
+ const internal::WorkerPoolTask* source) {
+ TRACE_EVENT0("cc", "RasterWorkerPool::OnRasterRequiredForActivationFinished");
+
+ // Early out if current |raster_required_for_activation_finished_task_|
+ // is not the source.
+ if (source != raster_required_for_activation_finished_task_.get())
+ return;
+
+ OnRasterTasksRequiredForActivationFinished();
+}
+
+scoped_ptr<base::Value> RasterWorkerPool::ScheduledStateAsValue() const {
+ scoped_ptr<base::DictionaryValue> scheduled_state(new base::DictionaryValue);
+ scheduled_state->SetInteger("task_count", raster_tasks_.size());
+ scheduled_state->SetInteger("task_required_for_activation_count",
+ raster_tasks_required_for_activation_.size());
+ return scheduled_state.PassAs<base::Value>();
+}
+
+// static
+internal::GraphNode* RasterWorkerPool::CreateGraphNodeForTask(
+ internal::WorkerPoolTask* task,
+ unsigned priority,
+ TaskGraph* graph) {
+ internal::GraphNode* node = new internal::GraphNode(task, priority);
+ DCHECK(graph->find(task) == graph->end());
+ graph->set(task, make_scoped_ptr(node));
+ return node;
+}
+
+// static
+internal::GraphNode* RasterWorkerPool::CreateGraphNodeForRasterTask(
+ internal::WorkerPoolTask* raster_task,
+ const TaskVector& decode_tasks,
+ unsigned priority,
+ TaskGraph* graph) {
+ DCHECK(!raster_task->HasCompleted());
+
+ internal::GraphNode* raster_node = CreateGraphNodeForTask(
+ raster_task, priority, graph);
+
+ // Insert image decode tasks.
+ for (TaskVector::const_iterator it = decode_tasks.begin();
+ it != decode_tasks.end(); ++it) {
+ internal::WorkerPoolTask* decode_task = it->get();
+
+ // Skip if already decoded.
+ if (decode_task->HasCompleted())
+ continue;
+
+ raster_node->add_dependency();
+
+ // Check if decode task already exists in graph.
+ GraphNodeMap::iterator decode_it = graph->find(decode_task);
+ if (decode_it != graph->end()) {
+ internal::GraphNode* decode_node = decode_it->second;
+ decode_node->add_dependent(raster_node);
+ continue;
+ }
+
+ internal::GraphNode* decode_node = CreateGraphNodeForTask(
+ decode_task, priority, graph);
+ decode_node->add_dependent(raster_node);
+ }
+
+ return raster_node;
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/raster_worker_pool.h b/chromium/cc/resources/raster_worker_pool.h
new file mode 100644
index 00000000000..d0e1e7c0de0
--- /dev/null
+++ b/chromium/cc/resources/raster_worker_pool.h
@@ -0,0 +1,278 @@
+// Copyright 2013 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.
+
+#ifndef CC_RESOURCES_RASTER_WORKER_POOL_H_
+#define CC_RESOURCES_RASTER_WORKER_POOL_H_
+
+#include <vector>
+
+#include "base/containers/hash_tables.h"
+#include "cc/debug/rendering_stats_instrumentation.h"
+#include "cc/resources/picture_pile_impl.h"
+#include "cc/resources/raster_mode.h"
+#include "cc/resources/tile_priority.h"
+#include "cc/resources/worker_pool.h"
+
+class SkDevice;
+
+namespace skia {
+class LazyPixelRef;
+}
+
+namespace cc {
+class PicturePileImpl;
+class PixelBufferRasterWorkerPool;
+class Resource;
+class ResourceProvider;
+
+namespace internal {
+
+class CC_EXPORT RasterWorkerPoolTask
+ : public base::RefCounted<RasterWorkerPoolTask> {
+ public:
+ typedef std::vector<scoped_refptr<WorkerPoolTask> > TaskVector;
+
+ // Returns true if |device| was written to. False indicate that
+ // the content of |device| is undefined and the resource doesn't
+ // need to be initialized.
+ virtual bool RunOnWorkerThread(SkDevice* device, unsigned thread_index) = 0;
+ virtual void CompleteOnOriginThread() = 0;
+
+ void DidRun(bool was_canceled);
+ bool HasFinishedRunning() const;
+ bool WasCanceled() const;
+ void WillComplete();
+ void DidComplete();
+ bool HasCompleted() const;
+
+ const Resource* resource() const { return resource_; }
+ const TaskVector& dependencies() const { return dependencies_; }
+
+ protected:
+ friend class base::RefCounted<RasterWorkerPoolTask>;
+
+ RasterWorkerPoolTask(const Resource* resource, TaskVector* dependencies);
+ virtual ~RasterWorkerPoolTask();
+
+ private:
+ bool did_run_;
+ bool did_complete_;
+ bool was_canceled_;
+ const Resource* resource_;
+ TaskVector dependencies_;
+};
+
+} // namespace internal
+} // namespace cc
+
+#if defined(COMPILER_GCC)
+namespace BASE_HASH_NAMESPACE {
+template <> struct hash<cc::internal::RasterWorkerPoolTask*> {
+ size_t operator()(cc::internal::RasterWorkerPoolTask* ptr) const {
+ return hash<size_t>()(reinterpret_cast<size_t>(ptr));
+ }
+};
+} // namespace BASE_HASH_NAMESPACE
+#endif // COMPILER
+
+namespace cc {
+
+class CC_EXPORT RasterWorkerPoolClient {
+ public:
+ virtual bool ShouldForceTasksRequiredForActivationToComplete() const = 0;
+ virtual void DidFinishRunningTasks() = 0;
+ virtual void DidFinishRunningTasksRequiredForActivation() = 0;
+
+ protected:
+ virtual ~RasterWorkerPoolClient() {}
+};
+
+// A worker thread pool that runs raster tasks.
+class CC_EXPORT RasterWorkerPool : public WorkerPool {
+ public:
+ class CC_EXPORT Task {
+ public:
+ typedef base::Callback<void(bool was_canceled)> Reply;
+
+ class CC_EXPORT Set {
+ public:
+ Set();
+ ~Set();
+
+ void Insert(const Task& task);
+
+ private:
+ friend class RasterWorkerPool;
+ friend class RasterWorkerPoolTest;
+
+ typedef internal::RasterWorkerPoolTask::TaskVector TaskVector;
+ TaskVector tasks_;
+ };
+
+ Task();
+ ~Task();
+
+ // Returns true if Task is null (doesn't refer to anything).
+ bool is_null() const { return !internal_.get(); }
+
+ // Returns the Task into an uninitialized state.
+ void Reset();
+
+ protected:
+ friend class RasterWorkerPool;
+ friend class RasterWorkerPoolTest;
+
+ explicit Task(internal::WorkerPoolTask* internal);
+
+ scoped_refptr<internal::WorkerPoolTask> internal_;
+ };
+
+ class CC_EXPORT RasterTask {
+ public:
+ typedef base::Callback<void(const PicturePileImpl::Analysis& analysis,
+ bool was_canceled)> Reply;
+
+ class CC_EXPORT Queue {
+ public:
+ Queue();
+ ~Queue();
+
+ void Append(const RasterTask& task, bool required_for_activation);
+
+ private:
+ friend class RasterWorkerPool;
+
+ typedef std::vector<scoped_refptr<internal::RasterWorkerPoolTask> >
+ TaskVector;
+ TaskVector tasks_;
+ typedef base::hash_set<internal::RasterWorkerPoolTask*> TaskSet;
+ TaskSet tasks_required_for_activation_;
+ };
+
+ RasterTask();
+ ~RasterTask();
+
+ // Returns true if Task is null (doesn't refer to anything).
+ bool is_null() const { return !internal_.get(); }
+
+ // Returns the Task into an uninitialized state.
+ void Reset();
+
+ protected:
+ friend class RasterWorkerPool;
+ friend class RasterWorkerPoolTest;
+
+ explicit RasterTask(internal::RasterWorkerPoolTask* internal);
+
+ scoped_refptr<internal::RasterWorkerPoolTask> internal_;
+ };
+
+ virtual ~RasterWorkerPool();
+
+ void SetClient(RasterWorkerPoolClient* client);
+
+ // Tells the worker pool to shutdown after canceling all previously
+ // scheduled tasks. Reply callbacks are still guaranteed to run.
+ virtual void Shutdown() OVERRIDE;
+
+ // Schedule running of raster tasks in |queue| and all dependencies.
+ // Previously scheduled tasks that are no longer needed to run
+ // raster tasks in |queue| will be canceled unless already running.
+ // Once scheduled, reply callbacks are guaranteed to run for all tasks
+ // even if they later get canceled by another call to ScheduleTasks().
+ virtual void ScheduleTasks(RasterTask::Queue* queue) = 0;
+
+ // TODO(vmpstr): Figure out an elegant way to not pass this many parameters.
+ static RasterTask CreateRasterTask(
+ const Resource* resource,
+ PicturePileImpl* picture_pile,
+ gfx::Rect content_rect,
+ float contents_scale,
+ RasterMode raster_mode,
+ bool is_tile_in_pending_tree_now_bin,
+ TileResolution tile_resolution,
+ int layer_id,
+ const void* tile_id,
+ int source_frame_number,
+ RenderingStatsInstrumentation* rendering_stats,
+ const RasterTask::Reply& reply,
+ Task::Set* dependencies);
+
+ static Task CreateImageDecodeTask(
+ skia::LazyPixelRef* pixel_ref,
+ int layer_id,
+ RenderingStatsInstrumentation* stats_instrumentation,
+ const Task::Reply& reply);
+
+ protected:
+ typedef std::vector<scoped_refptr<internal::WorkerPoolTask> > TaskVector;
+ typedef std::vector<scoped_refptr<internal::RasterWorkerPoolTask> >
+ RasterTaskVector;
+ typedef base::hash_set<internal::RasterWorkerPoolTask*> RasterTaskSet;
+ typedef internal::RasterWorkerPoolTask* TaskMapKey;
+ typedef base::hash_map<TaskMapKey,
+ scoped_refptr<internal::WorkerPoolTask> > TaskMap;
+
+ RasterWorkerPool(ResourceProvider* resource_provider, size_t num_threads);
+
+ virtual void OnRasterTasksFinished() = 0;
+ virtual void OnRasterTasksRequiredForActivationFinished() = 0;
+
+ void SetRasterTasks(RasterTask::Queue* queue);
+ bool IsRasterTaskRequiredForActivation(
+ internal::RasterWorkerPoolTask* task) const;
+
+ RasterWorkerPoolClient* client() const { return client_; }
+ ResourceProvider* resource_provider() const { return resource_provider_; }
+ const RasterTaskVector& raster_tasks() const { return raster_tasks_; }
+ const RasterTaskSet& raster_tasks_required_for_activation() const {
+ return raster_tasks_required_for_activation_;
+ }
+ void set_raster_finished_task(
+ scoped_refptr<internal::WorkerPoolTask> raster_finished_task) {
+ raster_finished_task_ = raster_finished_task;
+ }
+ void set_raster_required_for_activation_finished_task(
+ scoped_refptr<internal::WorkerPoolTask>
+ raster_required_for_activation_finished_task) {
+ raster_required_for_activation_finished_task_ =
+ raster_required_for_activation_finished_task;
+ }
+
+ scoped_refptr<internal::WorkerPoolTask> CreateRasterFinishedTask();
+ scoped_refptr<internal::WorkerPoolTask>
+ CreateRasterRequiredForActivationFinishedTask();
+
+ scoped_ptr<base::Value> ScheduledStateAsValue() const;
+
+ static internal::GraphNode* CreateGraphNodeForTask(
+ internal::WorkerPoolTask* task,
+ unsigned priority,
+ TaskGraph* graph);
+
+ static internal::GraphNode* CreateGraphNodeForRasterTask(
+ internal::WorkerPoolTask* raster_task,
+ const TaskVector& decode_tasks,
+ unsigned priority,
+ TaskGraph* graph);
+
+ private:
+ void OnRasterFinished(const internal::WorkerPoolTask* source);
+ void OnRasterRequiredForActivationFinished(
+ const internal::WorkerPoolTask* source);
+
+ RasterWorkerPoolClient* client_;
+ ResourceProvider* resource_provider_;
+ RasterTask::Queue::TaskVector raster_tasks_;
+ RasterTask::Queue::TaskSet raster_tasks_required_for_activation_;
+
+ base::WeakPtrFactory<RasterWorkerPool> weak_ptr_factory_;
+ scoped_refptr<internal::WorkerPoolTask> raster_finished_task_;
+ scoped_refptr<internal::WorkerPoolTask>
+ raster_required_for_activation_finished_task_;
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_RASTER_WORKER_POOL_H_
diff --git a/chromium/cc/resources/raster_worker_pool_perftest.cc b/chromium/cc/resources/raster_worker_pool_perftest.cc
new file mode 100644
index 00000000000..a4a258b16d3
--- /dev/null
+++ b/chromium/cc/resources/raster_worker_pool_perftest.cc
@@ -0,0 +1,240 @@
+// Copyright 2013 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 "cc/resources/raster_worker_pool.h"
+
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+
+namespace {
+
+static const int kTimeLimitMillis = 2000;
+static const int kWarmupRuns = 5;
+static const int kTimeCheckInterval = 10;
+
+class PerfWorkerPoolTaskImpl : public internal::WorkerPoolTask {
+ public:
+ // Overridden from internal::WorkerPoolTask:
+ virtual void RunOnWorkerThread(unsigned thread_index) OVERRIDE {}
+ virtual void CompleteOnOriginThread() OVERRIDE {}
+
+ private:
+ virtual ~PerfWorkerPoolTaskImpl() {}
+};
+
+class PerfRasterWorkerPool : public RasterWorkerPool {
+ public:
+ PerfRasterWorkerPool() : RasterWorkerPool(NULL, 1) {}
+ virtual ~PerfRasterWorkerPool() {}
+
+ static scoped_ptr<PerfRasterWorkerPool> Create() {
+ return make_scoped_ptr(new PerfRasterWorkerPool);
+ }
+
+ // Overridden from RasterWorkerPool:
+ virtual void ScheduleTasks(RasterTask::Queue* queue) OVERRIDE {
+ NOTREACHED();
+ }
+ virtual void OnRasterTasksFinished() OVERRIDE {
+ NOTREACHED();
+ }
+ virtual void OnRasterTasksRequiredForActivationFinished() OVERRIDE {
+ NOTREACHED();
+ }
+
+ void SetRasterTasks(RasterTask::Queue* queue) {
+ RasterWorkerPool::SetRasterTasks(queue);
+
+ TaskMap perf_tasks;
+ for (RasterTaskVector::const_iterator it = raster_tasks().begin();
+ it != raster_tasks().end(); ++it) {
+ internal::RasterWorkerPoolTask* task = it->get();
+
+ scoped_refptr<internal::WorkerPoolTask> new_perf_task(
+ new PerfWorkerPoolTaskImpl);
+ perf_tasks[task] = new_perf_task;
+ }
+
+ perf_tasks_.swap(perf_tasks);
+ }
+
+ void BuildTaskGraph() {
+ unsigned priority = 0;
+ TaskGraph graph;
+
+ scoped_refptr<internal::WorkerPoolTask>
+ raster_required_for_activation_finished_task(
+ CreateRasterRequiredForActivationFinishedTask());
+ internal::GraphNode* raster_required_for_activation_finished_node =
+ CreateGraphNodeForTask(
+ raster_required_for_activation_finished_task.get(),
+ priority++,
+ &graph);
+
+ scoped_refptr<internal::WorkerPoolTask> raster_finished_task(
+ CreateRasterFinishedTask());
+ internal::GraphNode* raster_finished_node =
+ CreateGraphNodeForTask(raster_finished_task.get(),
+ priority++,
+ &graph);
+
+ for (RasterTaskVector::const_iterator it = raster_tasks().begin();
+ it != raster_tasks().end(); ++it) {
+ internal::RasterWorkerPoolTask* task = it->get();
+
+ TaskMap::iterator perf_it = perf_tasks_.find(task);
+ DCHECK(perf_it != perf_tasks_.end());
+ if (perf_it != perf_tasks_.end()) {
+ internal::WorkerPoolTask* perf_task = perf_it->second.get();
+
+ internal::GraphNode* perf_node =
+ CreateGraphNodeForRasterTask(perf_task,
+ task->dependencies(),
+ priority++,
+ &graph);
+
+ if (IsRasterTaskRequiredForActivation(task)) {
+ raster_required_for_activation_finished_node->add_dependency();
+ perf_node->add_dependent(
+ raster_required_for_activation_finished_node);
+ }
+
+ raster_finished_node->add_dependency();
+ perf_node->add_dependent(raster_finished_node);
+ }
+ }
+ }
+
+ private:
+ TaskMap perf_tasks_;
+
+ DISALLOW_COPY_AND_ASSIGN(PerfRasterWorkerPool);
+};
+
+class RasterWorkerPoolPerfTest : public testing::Test {
+ public:
+ RasterWorkerPoolPerfTest() : num_runs_(0) {}
+
+ // Overridden from testing::Test:
+ virtual void SetUp() OVERRIDE {
+ raster_worker_pool_ = PerfRasterWorkerPool::Create();
+ }
+ virtual void TearDown() OVERRIDE {
+ raster_worker_pool_->Shutdown();
+ }
+
+ void EndTest() {
+ elapsed_ = base::TimeTicks::HighResNow() - start_time_;
+ }
+
+ void AfterTest(const std::string test_name) {
+ // Format matches chrome/test/perf/perf_test.h:PrintResult
+ printf("*RESULT %s: %.2f runs/s\n",
+ test_name.c_str(),
+ num_runs_ / elapsed_.InSecondsF());
+ }
+
+ bool DidRun() {
+ ++num_runs_;
+ if (num_runs_ == kWarmupRuns)
+ start_time_ = base::TimeTicks::HighResNow();
+
+ if (!start_time_.is_null() && (num_runs_ % kTimeCheckInterval) == 0) {
+ base::TimeDelta elapsed = base::TimeTicks::HighResNow() - start_time_;
+ if (elapsed >= base::TimeDelta::FromMilliseconds(kTimeLimitMillis)) {
+ elapsed_ = elapsed;
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ void CreateTasks(RasterWorkerPool::RasterTask::Queue* tasks,
+ unsigned num_raster_tasks,
+ unsigned num_image_decode_tasks) {
+ typedef std::vector<RasterWorkerPool::Task> TaskVector;
+ TaskVector image_decode_tasks;
+
+ for (unsigned i = 0; i < num_image_decode_tasks; ++i) {
+ image_decode_tasks.push_back(
+ RasterWorkerPool::CreateImageDecodeTask(
+ NULL,
+ 0,
+ NULL,
+ base::Bind(
+ &RasterWorkerPoolPerfTest::OnImageDecodeTaskCompleted)));
+ }
+
+ for (unsigned i = 0; i < num_raster_tasks; ++i) {
+ RasterWorkerPool::Task::Set decode_tasks;
+ for (TaskVector::iterator it = image_decode_tasks.begin();
+ it != image_decode_tasks.end(); ++it)
+ decode_tasks.Insert(*it);
+
+ tasks->Append(
+ RasterWorkerPool::CreateRasterTask(
+ NULL,
+ NULL,
+ gfx::Rect(),
+ 1.0,
+ HIGH_QUALITY_RASTER_MODE,
+ false,
+ TileResolution(),
+ 1,
+ NULL,
+ 1,
+ NULL,
+ base::Bind(&RasterWorkerPoolPerfTest::OnRasterTaskCompleted),
+ &decode_tasks),
+ false);
+ }
+ }
+
+ void RunBuildTaskGraphTest(const std::string test_name,
+ unsigned num_raster_tasks,
+ unsigned num_image_decode_tasks) {
+ start_time_ = base::TimeTicks();
+ num_runs_ = 0;
+ RasterWorkerPool::RasterTask::Queue tasks;
+ CreateTasks(&tasks, num_raster_tasks, num_image_decode_tasks);
+ raster_worker_pool_->SetRasterTasks(&tasks);
+ do {
+ raster_worker_pool_->BuildTaskGraph();
+ } while (DidRun());
+
+ AfterTest(test_name);
+ }
+
+ protected:
+ static void OnRasterTaskCompleted(const PicturePileImpl::Analysis& analysis,
+ bool was_canceled) {}
+ static void OnImageDecodeTaskCompleted(bool was_canceled) {}
+
+ scoped_ptr<PerfRasterWorkerPool> raster_worker_pool_;
+ base::TimeTicks start_time_;
+ base::TimeDelta elapsed_;
+ int num_runs_;
+};
+
+TEST_F(RasterWorkerPoolPerfTest, BuildTaskGraph) {
+ RunBuildTaskGraphTest("build_task_graph_10_0", 10, 0);
+ RunBuildTaskGraphTest("build_task_graph_100_0", 100, 0);
+ RunBuildTaskGraphTest("build_task_graph_1000_0", 1000, 0);
+ RunBuildTaskGraphTest("build_task_graph_10_1", 10, 1);
+ RunBuildTaskGraphTest("build_task_graph_100_1", 100, 1);
+ RunBuildTaskGraphTest("build_task_graph_1000_1", 1000, 1);
+ RunBuildTaskGraphTest("build_task_graph_10_4", 10, 4);
+ RunBuildTaskGraphTest("build_task_graph_100_4", 100, 4);
+ RunBuildTaskGraphTest("build_task_graph_1000_4", 1000, 4);
+ RunBuildTaskGraphTest("build_task_graph_10_16", 10, 16);
+ RunBuildTaskGraphTest("build_task_graph_100_16", 100, 16);
+ RunBuildTaskGraphTest("build_task_graph_1000_16", 1000, 16);
+}
+
+} // namespace
+
+} // namespace cc
diff --git a/chromium/cc/resources/raster_worker_pool_unittest.cc b/chromium/cc/resources/raster_worker_pool_unittest.cc
new file mode 100644
index 00000000000..3d27c41b353
--- /dev/null
+++ b/chromium/cc/resources/raster_worker_pool_unittest.cc
@@ -0,0 +1,280 @@
+// Copyright 2013 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 "cc/resources/raster_worker_pool.h"
+
+#include <vector>
+
+#include "cc/resources/image_raster_worker_pool.h"
+#include "cc/resources/picture_pile.h"
+#include "cc/resources/picture_pile_impl.h"
+#include "cc/resources/pixel_buffer_raster_worker_pool.h"
+#include "cc/resources/resource_provider.h"
+#include "cc/resources/scoped_resource.h"
+#include "cc/test/fake_output_surface.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+
+class TestRasterWorkerPoolTaskImpl : public internal::RasterWorkerPoolTask {
+ public:
+ typedef base::Callback<void(const PicturePileImpl::Analysis& analysis,
+ bool was_canceled,
+ bool did_raster)> Reply;
+
+ TestRasterWorkerPoolTaskImpl(
+ const Resource* resource,
+ const Reply& reply,
+ TaskVector* dependencies)
+ : internal::RasterWorkerPoolTask(resource, dependencies),
+ reply_(reply),
+ did_raster_(false) {}
+
+ // Overridden from internal::WorkerPoolTask:
+ virtual bool RunOnWorkerThread(SkDevice* device, unsigned thread_index)
+ OVERRIDE {
+ did_raster_ = true;
+ return true;
+ }
+ virtual void CompleteOnOriginThread() OVERRIDE {
+ reply_.Run(PicturePileImpl::Analysis(), !HasFinishedRunning(), did_raster_);
+ }
+
+ protected:
+ virtual ~TestRasterWorkerPoolTaskImpl() {}
+
+ private:
+ const Reply reply_;
+ bool did_raster_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestRasterWorkerPoolTaskImpl);
+};
+
+class RasterWorkerPoolTest : public testing::Test,
+ public RasterWorkerPoolClient {
+ public:
+ RasterWorkerPoolTest()
+ : output_surface_(FakeOutputSurface::Create3d()),
+ resource_provider_(
+ ResourceProvider::Create(output_surface_.get(), 0)),
+ check_interval_milliseconds_(1),
+ timeout_seconds_(5),
+ timed_out_(false) {
+ }
+ virtual ~RasterWorkerPoolTest() {
+ resource_provider_.reset();
+ }
+
+ // Overridden from testing::Test:
+ virtual void TearDown() OVERRIDE {
+ if (!raster_worker_pool_)
+ return;
+ raster_worker_pool_->Shutdown();
+ raster_worker_pool_->CheckForCompletedTasks();
+ }
+
+ // Overriden from RasterWorkerPoolClient:
+ virtual bool ShouldForceTasksRequiredForActivationToComplete() const
+ OVERRIDE {
+ return false;
+ }
+ virtual void DidFinishRunningTasks() OVERRIDE {}
+ virtual void DidFinishRunningTasksRequiredForActivation() OVERRIDE {}
+
+ virtual void BeginTest() = 0;
+ virtual void AfterTest() = 0;
+
+ ResourceProvider* resource_provider() const {
+ return resource_provider_.get();
+ }
+
+ RasterWorkerPool* worker_pool() {
+ return raster_worker_pool_.get();
+ }
+
+ void RunTest(bool use_map_image) {
+ if (use_map_image) {
+ raster_worker_pool_ = ImageRasterWorkerPool::Create(
+ resource_provider(), 1);
+ } else {
+ raster_worker_pool_ = PixelBufferRasterWorkerPool::Create(
+ resource_provider(), 1);
+ }
+
+ raster_worker_pool_->SetClient(this);
+
+ BeginTest();
+
+ ScheduleCheckForCompletedTasks();
+
+ if (timeout_seconds_) {
+ timeout_.Reset(base::Bind(&RasterWorkerPoolTest::OnTimeout,
+ base::Unretained(this)));
+ base::MessageLoopProxy::current()->PostDelayedTask(
+ FROM_HERE,
+ timeout_.callback(),
+ base::TimeDelta::FromSeconds(timeout_seconds_));
+ }
+
+ base::MessageLoop::current()->Run();
+
+ check_.Cancel();
+ timeout_.Cancel();
+
+ if (timed_out_) {
+ FAIL() << "Test timed out";
+ return;
+ }
+ AfterTest();
+ }
+
+ void EndTest() {
+ base::MessageLoop::current()->Quit();
+ }
+
+ void ScheduleTasks() {
+ RasterWorkerPool::RasterTask::Queue tasks;
+
+ for (std::vector<RasterWorkerPool::RasterTask>::iterator it =
+ tasks_.begin();
+ it != tasks_.end(); ++it)
+ tasks.Append(*it, false);
+
+ worker_pool()->ScheduleTasks(&tasks);
+ }
+
+ void AppendTask(unsigned id) {
+ const gfx::Size size(1, 1);
+
+ scoped_ptr<ScopedResource> resource(
+ ScopedResource::create(resource_provider()));
+ resource->Allocate(size, GL_RGBA, ResourceProvider::TextureUsageAny);
+ const Resource* const_resource = resource.get();
+
+ RasterWorkerPool::Task::Set empty;
+ tasks_.push_back(
+ RasterWorkerPool::RasterTask(new TestRasterWorkerPoolTaskImpl(
+ const_resource,
+ base::Bind(&RasterWorkerPoolTest::OnTaskCompleted,
+ base::Unretained(this),
+ base::Passed(&resource),
+ id),
+ &empty.tasks_)));
+ }
+
+ virtual void OnTaskCompleted(scoped_ptr<ScopedResource> resource,
+ unsigned id,
+ const PicturePileImpl::Analysis& analysis,
+ bool was_canceled,
+ bool did_raster) {}
+
+ private:
+ void ScheduleCheckForCompletedTasks() {
+ check_.Reset(base::Bind(&RasterWorkerPoolTest::OnCheckForCompletedTasks,
+ base::Unretained(this)));
+ base::MessageLoopProxy::current()->PostDelayedTask(
+ FROM_HERE,
+ check_.callback(),
+ base::TimeDelta::FromMilliseconds(check_interval_milliseconds_));
+ }
+
+ void OnCheckForCompletedTasks() {
+ raster_worker_pool_->CheckForCompletedTasks();
+ ScheduleCheckForCompletedTasks();
+ }
+
+ void OnTimeout() {
+ timed_out_ = true;
+ base::MessageLoop::current()->Quit();
+ }
+
+ protected:
+ scoped_ptr<FakeOutputSurface> output_surface_;
+ scoped_ptr<ResourceProvider> resource_provider_;
+ scoped_ptr<RasterWorkerPool> raster_worker_pool_;
+ base::CancelableClosure check_;
+ int check_interval_milliseconds_;
+ base::CancelableClosure timeout_;
+ int timeout_seconds_;
+ bool timed_out_;
+ std::vector<RasterWorkerPool::RasterTask> tasks_;
+};
+
+namespace {
+
+#define PIXEL_BUFFER_TEST_F(TEST_FIXTURE_NAME) \
+ TEST_F(TEST_FIXTURE_NAME, RunPixelBuffer) { \
+ RunTest(false); \
+ }
+
+#define IMAGE_TEST_F(TEST_FIXTURE_NAME) \
+ TEST_F(TEST_FIXTURE_NAME, RunImage) { \
+ RunTest(true); \
+ }
+
+#define PIXEL_BUFFER_AND_IMAGE_TEST_F(TEST_FIXTURE_NAME) \
+ PIXEL_BUFFER_TEST_F(TEST_FIXTURE_NAME); \
+ IMAGE_TEST_F(TEST_FIXTURE_NAME)
+
+class BasicRasterWorkerPoolTest : public RasterWorkerPoolTest {
+ public:
+ virtual void OnTaskCompleted(scoped_ptr<ScopedResource> resource,
+ unsigned id,
+ const PicturePileImpl::Analysis& analysis,
+ bool was_canceled,
+ bool did_raster) OVERRIDE {
+ EXPECT_TRUE(did_raster);
+ on_task_completed_ids_.push_back(id);
+ if (on_task_completed_ids_.size() == 2)
+ EndTest();
+ }
+
+ // Overridden from RasterWorkerPoolTest:
+ virtual void BeginTest() OVERRIDE {
+ AppendTask(0u);
+ AppendTask(1u);
+ ScheduleTasks();
+ }
+ virtual void AfterTest() OVERRIDE {
+ EXPECT_EQ(2u, on_task_completed_ids_.size());
+ tasks_.clear();
+ }
+
+ std::vector<unsigned> on_task_completed_ids_;
+};
+
+PIXEL_BUFFER_AND_IMAGE_TEST_F(BasicRasterWorkerPoolTest);
+
+class RasterWorkerPoolTestFailedMapResource : public RasterWorkerPoolTest {
+ public:
+ virtual void OnTaskCompleted(scoped_ptr<ScopedResource> resource,
+ unsigned id,
+ const PicturePileImpl::Analysis& analysis,
+ bool was_canceled,
+ bool did_raster) OVERRIDE {
+ EXPECT_FALSE(did_raster);
+ EndTest();
+ }
+
+ // Overridden from RasterWorkerPoolTest:
+ virtual void BeginTest() OVERRIDE {
+ TestWebGraphicsContext3D* context3d =
+ static_cast<TestWebGraphicsContext3D*>(output_surface_->context3d());
+ context3d->set_times_map_image_chromium_succeeds(0);
+ context3d->set_times_map_buffer_chromium_succeeds(0);
+ AppendTask(0u);
+ ScheduleTasks();
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ ASSERT_EQ(1u, tasks_.size());
+ tasks_.clear();
+ }
+};
+
+PIXEL_BUFFER_AND_IMAGE_TEST_F(RasterWorkerPoolTestFailedMapResource);
+
+} // namespace
+
+} // namespace cc
diff --git a/chromium/cc/resources/resource.cc b/chromium/cc/resources/resource.cc
new file mode 100644
index 00000000000..192eaebddc6
--- /dev/null
+++ b/chromium/cc/resources/resource.cc
@@ -0,0 +1,39 @@
+// 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 "cc/resources/resource.h"
+#include "third_party/khronos/GLES2/gl2ext.h"
+
+namespace cc {
+
+size_t Resource::bytes() const {
+ if (size_.IsEmpty())
+ return 0;
+
+ return MemorySizeBytes(size_, format_);
+}
+
+size_t Resource::BytesPerPixel(GLenum format) {
+ size_t components_per_pixel = 0;
+ size_t bytes_per_component = 1;
+ switch (format) {
+ case GL_RGBA:
+ case GL_BGRA_EXT:
+ components_per_pixel = 4;
+ break;
+ case GL_LUMINANCE:
+ components_per_pixel = 1;
+ break;
+ default:
+ NOTREACHED();
+ }
+ return components_per_pixel * bytes_per_component;
+}
+
+size_t Resource::MemorySizeBytes(gfx::Size size, GLenum format) {
+ return BytesPerPixel(format) * size.width() * size.height();
+}
+
+
+} // namespace cc
diff --git a/chromium/cc/resources/resource.h b/chromium/cc/resources/resource.h
new file mode 100644
index 00000000000..1c658223649
--- /dev/null
+++ b/chromium/cc/resources/resource.h
@@ -0,0 +1,49 @@
+// 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.
+
+#ifndef CC_RESOURCES_RESOURCE_H_
+#define CC_RESOURCES_RESOURCE_H_
+
+#include "cc/base/cc_export.h"
+#include "cc/resources/resource_provider.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "ui/gfx/size.h"
+
+namespace cc {
+
+class CC_EXPORT Resource {
+ public:
+ Resource() : id_(0) {}
+ Resource(unsigned id, gfx::Size size, GLenum format)
+ : id_(id),
+ size_(size),
+ format_(format) {}
+
+ ResourceProvider::ResourceId id() const { return id_; }
+ gfx::Size size() const { return size_; }
+ GLenum format() const { return format_; }
+
+ size_t bytes() const;
+
+ static size_t BytesPerPixel(GLenum format);
+ static size_t MemorySizeBytes(gfx::Size size, GLenum format);
+
+ protected:
+ void set_id(ResourceProvider::ResourceId id) { id_ = id; }
+ void set_dimensions(gfx::Size size, GLenum format) {
+ size_ = size;
+ format_ = format;
+ }
+
+ private:
+ ResourceProvider::ResourceId id_;
+ gfx::Size size_;
+ GLenum format_;
+
+ DISALLOW_COPY_AND_ASSIGN(Resource);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_RESOURCE_H_
diff --git a/chromium/cc/resources/resource_pool.cc b/chromium/cc/resources/resource_pool.cc
new file mode 100644
index 00000000000..5835db565d3
--- /dev/null
+++ b/chromium/cc/resources/resource_pool.cc
@@ -0,0 +1,121 @@
+// 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 "cc/resources/resource_pool.h"
+
+#include "cc/resources/resource_provider.h"
+
+namespace cc {
+
+ResourcePool::Resource::Resource(cc::ResourceProvider* resource_provider,
+ gfx::Size size,
+ GLenum format)
+ : cc::Resource(resource_provider->CreateManagedResource(
+ size,
+ format,
+ ResourceProvider::TextureUsageAny),
+ size,
+ format),
+ resource_provider_(resource_provider) {
+ DCHECK(id());
+}
+
+ResourcePool::Resource::~Resource() {
+ DCHECK(id());
+ DCHECK(resource_provider_);
+ resource_provider_->DeleteResource(id());
+}
+
+ResourcePool::ResourcePool(ResourceProvider* resource_provider)
+ : resource_provider_(resource_provider),
+ max_memory_usage_bytes_(0),
+ max_unused_memory_usage_bytes_(0),
+ memory_usage_bytes_(0),
+ unused_memory_usage_bytes_(0),
+ num_resources_limit_(0) {
+}
+
+ResourcePool::~ResourcePool() {
+ SetMemoryUsageLimits(0, 0, 0);
+}
+
+scoped_ptr<ResourcePool::Resource> ResourcePool::AcquireResource(
+ gfx::Size size, GLenum format) {
+ for (ResourceList::iterator it = resources_.begin();
+ it != resources_.end(); ++it) {
+ Resource* resource = *it;
+
+ // TODO(epenner): It would be nice to DCHECK that this
+ // doesn't happen two frames in a row for any resource
+ // in this pool.
+ if (!resource_provider_->CanLockForWrite(resource->id()))
+ continue;
+
+ if (resource->size() != size)
+ continue;
+ if (resource->format() != format)
+ continue;
+
+ resources_.erase(it);
+ unused_memory_usage_bytes_ -= resource->bytes();
+ return make_scoped_ptr(resource);
+ }
+
+ // Create new resource.
+ Resource* resource = new Resource(
+ resource_provider_, size, format);
+
+ // Extend all read locks on all resources until the resource is
+ // finished being used, such that we know when resources are
+ // truly safe to recycle.
+ resource_provider_->EnableReadLockFences(resource->id(), true);
+
+ memory_usage_bytes_ += resource->bytes();
+ return make_scoped_ptr(resource);
+}
+
+void ResourcePool::ReleaseResource(
+ scoped_ptr<ResourcePool::Resource> resource) {
+ if (MemoryUsageTooHigh()) {
+ memory_usage_bytes_ -= resource->bytes();
+ return;
+ }
+
+ unused_memory_usage_bytes_ += resource->bytes();
+ resources_.push_back(resource.release());
+}
+
+void ResourcePool::SetMemoryUsageLimits(
+ size_t max_memory_usage_bytes,
+ size_t max_unused_memory_usage_bytes,
+ size_t num_resources_limit) {
+ max_memory_usage_bytes_ = max_memory_usage_bytes;
+ max_unused_memory_usage_bytes_ = max_unused_memory_usage_bytes;
+ num_resources_limit_ = num_resources_limit;
+
+ while (!resources_.empty()) {
+ if (!MemoryUsageTooHigh())
+ break;
+
+ // MRU eviction pattern as least recently used is less likely to
+ // be blocked by read lock fence.
+ Resource* resource = resources_.back();
+ resources_.pop_back();
+ memory_usage_bytes_ -= resource->bytes();
+ unused_memory_usage_bytes_ -= resource->bytes();
+ delete resource;
+ }
+}
+
+bool ResourcePool::MemoryUsageTooHigh() {
+ if (resources_.size() > num_resources_limit_)
+ return true;
+ if (memory_usage_bytes_ > max_memory_usage_bytes_)
+ return true;
+ if (unused_memory_usage_bytes_ > max_unused_memory_usage_bytes_)
+ return true;
+ return false;
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/resource_pool.h b/chromium/cc/resources/resource_pool.h
new file mode 100644
index 00000000000..309bf33f30b
--- /dev/null
+++ b/chromium/cc/resources/resource_pool.h
@@ -0,0 +1,76 @@
+// 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.
+
+#ifndef CC_RESOURCES_RESOURCE_POOL_H_
+#define CC_RESOURCES_RESOURCE_POOL_H_
+
+#include <list>
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/output/renderer.h"
+#include "cc/resources/resource.h"
+
+namespace cc {
+class ResourceProvider;
+
+class CC_EXPORT ResourcePool {
+ public:
+ class CC_EXPORT Resource : public cc::Resource {
+ public:
+ Resource(ResourceProvider* resource_provider,
+ gfx::Size size,
+ GLenum format);
+ ~Resource();
+
+ private:
+ ResourceProvider* resource_provider_;
+
+ DISALLOW_COPY_AND_ASSIGN(Resource);
+ };
+
+ static scoped_ptr<ResourcePool> Create(ResourceProvider* resource_provider) {
+ return make_scoped_ptr(new ResourcePool(resource_provider));
+ }
+
+ virtual ~ResourcePool();
+
+ scoped_ptr<ResourcePool::Resource> AcquireResource(gfx::Size size,
+ GLenum format);
+ void ReleaseResource(scoped_ptr<ResourcePool::Resource>);
+
+ void SetMemoryUsageLimits(size_t max_memory_usage_bytes,
+ size_t max_unused_memory_usage_bytes,
+ size_t num_resources_limit);
+
+ size_t total_memory_usage_bytes() const {
+ return memory_usage_bytes_;
+ }
+ size_t acquired_memory_usage_bytes() const {
+ return memory_usage_bytes_ - unused_memory_usage_bytes_;
+ }
+ size_t NumResources() const { return resources_.size(); }
+
+ protected:
+ explicit ResourcePool(ResourceProvider* resource_provider);
+
+ bool MemoryUsageTooHigh();
+
+ private:
+ ResourceProvider* resource_provider_;
+ size_t max_memory_usage_bytes_;
+ size_t max_unused_memory_usage_bytes_;
+ size_t memory_usage_bytes_;
+ size_t unused_memory_usage_bytes_;
+ size_t num_resources_limit_;
+
+ typedef std::list<Resource*> ResourceList;
+ ResourceList resources_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResourcePool);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_RESOURCE_POOL_H_
diff --git a/chromium/cc/resources/resource_provider.cc b/chromium/cc/resources/resource_provider.cc
new file mode 100644
index 00000000000..11eaab07a28
--- /dev/null
+++ b/chromium/cc/resources/resource_provider.cc
@@ -0,0 +1,1446 @@
+// 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 "cc/resources/resource_provider.h"
+
+#include <algorithm>
+#include <limits>
+
+#include "base/containers/hash_tables.h"
+#include "base/debug/alias.h"
+#include "base/stl_util.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "cc/output/gl_renderer.h" // For the GLC() macro.
+#include "cc/resources/platform_color.h"
+#include "cc/resources/transferable_resource.h"
+#include "cc/scheduler/texture_uploader.h"
+#include "gpu/GLES2/gl2extchromium.h"
+#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "third_party/khronos/GLES2/gl2ext.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/vector2d.h"
+
+using WebKit::WebGraphicsContext3D;
+
+namespace cc {
+
+namespace {
+
+GLenum TextureToStorageFormat(GLenum texture_format) {
+ GLenum storage_format = GL_RGBA8_OES;
+ switch (texture_format) {
+ case GL_RGBA:
+ break;
+ case GL_BGRA_EXT:
+ storage_format = GL_BGRA8_EXT;
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+
+ return storage_format;
+}
+
+bool IsTextureFormatSupportedForStorage(GLenum format) {
+ return (format == GL_RGBA || format == GL_BGRA_EXT);
+}
+
+unsigned CreateTextureId(WebGraphicsContext3D* context3d) {
+ unsigned texture_id = 0;
+ GLC(context3d, texture_id = context3d->createTexture());
+ GLC(context3d, context3d->bindTexture(GL_TEXTURE_2D, texture_id));
+ GLC(context3d, context3d->texParameteri(
+ GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
+ GLC(context3d, context3d->texParameteri(
+ GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
+ GLC(context3d, context3d->texParameteri(
+ GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
+ GLC(context3d, context3d->texParameteri(
+ GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
+ return texture_id;
+}
+
+} // namespace
+
+ResourceProvider::Resource::Resource()
+ : gl_id(0),
+ gl_pixel_buffer_id(0),
+ gl_upload_query_id(0),
+ pixels(NULL),
+ pixel_buffer(NULL),
+ lock_for_read_count(0),
+ locked_for_write(false),
+ external(false),
+ exported(false),
+ marked_for_deletion(false),
+ pending_set_pixels(false),
+ set_pixels_completion_forced(false),
+ allocated(false),
+ enable_read_lock_fences(false),
+ read_lock_fence(NULL),
+ size(),
+ format(0),
+ filter(0),
+ image_id(0),
+ texture_pool(0),
+ hint(TextureUsageAny),
+ type(static_cast<ResourceType>(0)) {}
+
+ResourceProvider::Resource::~Resource() {}
+
+ResourceProvider::Resource::Resource(
+ unsigned texture_id,
+ gfx::Size size,
+ GLenum format,
+ GLenum filter,
+ GLenum texture_pool,
+ TextureUsageHint hint)
+ : gl_id(texture_id),
+ gl_pixel_buffer_id(0),
+ gl_upload_query_id(0),
+ pixels(NULL),
+ pixel_buffer(NULL),
+ lock_for_read_count(0),
+ locked_for_write(false),
+ external(false),
+ exported(false),
+ marked_for_deletion(false),
+ pending_set_pixels(false),
+ set_pixels_completion_forced(false),
+ allocated(false),
+ enable_read_lock_fences(false),
+ read_lock_fence(NULL),
+ size(size),
+ format(format),
+ filter(filter),
+ image_id(0),
+ texture_pool(texture_pool),
+ hint(hint),
+ type(GLTexture) {}
+
+ResourceProvider::Resource::Resource(
+ uint8_t* pixels, gfx::Size size, GLenum format, GLenum filter)
+ : gl_id(0),
+ gl_pixel_buffer_id(0),
+ gl_upload_query_id(0),
+ pixels(pixels),
+ pixel_buffer(NULL),
+ lock_for_read_count(0),
+ locked_for_write(false),
+ external(false),
+ exported(false),
+ marked_for_deletion(false),
+ pending_set_pixels(false),
+ set_pixels_completion_forced(false),
+ allocated(false),
+ enable_read_lock_fences(false),
+ read_lock_fence(NULL),
+ size(size),
+ format(format),
+ filter(filter),
+ image_id(0),
+ texture_pool(0),
+ hint(TextureUsageAny),
+ type(Bitmap) {}
+
+ResourceProvider::Child::Child() {}
+
+ResourceProvider::Child::~Child() {}
+
+scoped_ptr<ResourceProvider> ResourceProvider::Create(
+ OutputSurface* output_surface,
+ int highp_threshold_min) {
+ scoped_ptr<ResourceProvider> resource_provider(
+ new ResourceProvider(output_surface, highp_threshold_min));
+
+ bool success = false;
+ if (output_surface->context3d()) {
+ success = resource_provider->InitializeGL();
+ } else {
+ resource_provider->InitializeSoftware();
+ success = true;
+ }
+
+ if (!success)
+ return scoped_ptr<ResourceProvider>();
+
+ DCHECK_NE(InvalidType, resource_provider->default_resource_type());
+ return resource_provider.Pass();
+}
+
+ResourceProvider::~ResourceProvider() {
+ while (!resources_.empty())
+ DeleteResourceInternal(resources_.begin(), ForShutdown);
+
+ CleanUpGLIfNeeded();
+}
+
+WebGraphicsContext3D* ResourceProvider::GraphicsContext3D() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return output_surface_->context3d();
+}
+
+bool ResourceProvider::InUseByConsumer(ResourceId id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ResourceMap::iterator it = resources_.find(id);
+ CHECK(it != resources_.end());
+ Resource* resource = &it->second;
+ return !!resource->lock_for_read_count || resource->exported;
+}
+
+ResourceProvider::ResourceId ResourceProvider::CreateResource(
+ gfx::Size size, GLenum format, TextureUsageHint hint) {
+ DCHECK(!size.IsEmpty());
+ switch (default_resource_type_) {
+ case GLTexture:
+ return CreateGLTexture(
+ size, format, GL_TEXTURE_POOL_UNMANAGED_CHROMIUM, hint);
+ case Bitmap:
+ DCHECK(format == GL_RGBA);
+ return CreateBitmap(size);
+ case InvalidType:
+ break;
+ }
+
+ LOG(FATAL) << "Invalid default resource type.";
+ return 0;
+}
+
+ResourceProvider::ResourceId ResourceProvider::CreateManagedResource(
+ gfx::Size size, GLenum format, TextureUsageHint hint) {
+ DCHECK(!size.IsEmpty());
+ switch (default_resource_type_) {
+ case GLTexture:
+ return CreateGLTexture(
+ size, format, GL_TEXTURE_POOL_MANAGED_CHROMIUM, hint);
+ case Bitmap:
+ DCHECK(format == GL_RGBA);
+ return CreateBitmap(size);
+ case InvalidType:
+ break;
+ }
+
+ LOG(FATAL) << "Invalid default resource type.";
+ return 0;
+}
+
+ResourceProvider::ResourceId ResourceProvider::CreateGLTexture(
+ gfx::Size size, GLenum format, GLenum texture_pool, TextureUsageHint hint) {
+ DCHECK_LE(size.width(), max_texture_size_);
+ DCHECK_LE(size.height(), max_texture_size_);
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ ResourceId id = next_id_++;
+ Resource resource(0, size, format, GL_LINEAR, texture_pool, hint);
+ resource.allocated = false;
+ resources_[id] = resource;
+ return id;
+}
+
+ResourceProvider::ResourceId ResourceProvider::CreateBitmap(gfx::Size size) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ uint8_t* pixels = new uint8_t[4 * size.GetArea()];
+
+ ResourceId id = next_id_++;
+ Resource resource(pixels, size, GL_RGBA, GL_LINEAR);
+ resource.allocated = true;
+ resources_[id] = resource;
+ return id;
+}
+
+ResourceProvider::ResourceId
+ResourceProvider::CreateResourceFromExternalTexture(
+ unsigned texture_target,
+ unsigned texture_id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ DCHECK(context3d);
+ GLC(context3d, context3d->bindTexture(texture_target, texture_id));
+ GLC(context3d, context3d->texParameteri(
+ texture_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
+ GLC(context3d, context3d->texParameteri(
+ texture_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
+ GLC(context3d, context3d->texParameteri(
+ texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
+ GLC(context3d, context3d->texParameteri(
+ texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
+
+ ResourceId id = next_id_++;
+ Resource resource(texture_id, gfx::Size(), 0, GL_LINEAR, 0, TextureUsageAny);
+ resource.external = true;
+ resource.allocated = true;
+ resources_[id] = resource;
+ return id;
+}
+
+ResourceProvider::ResourceId ResourceProvider::CreateResourceFromTextureMailbox(
+ const TextureMailbox& mailbox) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ // Just store the information. Mailbox will be consumed in LockForRead().
+ ResourceId id = next_id_++;
+ DCHECK(mailbox.IsValid());
+ Resource& resource = resources_[id];
+ if (mailbox.IsTexture()) {
+ resource = Resource(0, gfx::Size(), 0, GL_LINEAR, 0, TextureUsageAny);
+ } else {
+ DCHECK(mailbox.IsSharedMemory());
+ base::SharedMemory* shared_memory = mailbox.shared_memory();
+ DCHECK(shared_memory->memory());
+ uint8_t* pixels = reinterpret_cast<uint8_t*>(shared_memory->memory());
+ resource = Resource(pixels, mailbox.shared_memory_size(),
+ GL_RGBA, GL_LINEAR);
+ }
+ resource.external = true;
+ resource.allocated = true;
+ resource.mailbox = mailbox;
+ return id;
+}
+
+void ResourceProvider::DeleteResource(ResourceId id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ResourceMap::iterator it = resources_.find(id);
+ CHECK(it != resources_.end());
+ Resource* resource = &it->second;
+ DCHECK(!resource->lock_for_read_count);
+ DCHECK(!resource->marked_for_deletion);
+ DCHECK(resource->pending_set_pixels || !resource->locked_for_write);
+
+ if (resource->exported) {
+ resource->marked_for_deletion = true;
+ return;
+ } else {
+ DeleteResourceInternal(it, Normal);
+ }
+}
+
+void ResourceProvider::DeleteResourceInternal(ResourceMap::iterator it,
+ DeleteStyle style) {
+ Resource* resource = &it->second;
+ bool lost_resource = lost_output_surface_;
+
+ DCHECK(!resource->exported || style != Normal);
+ if (style == ForShutdown && resource->exported)
+ lost_resource = true;
+
+ if (resource->image_id) {
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ DCHECK(context3d);
+ GLC(context3d, context3d->destroyImageCHROMIUM(resource->image_id));
+ }
+
+ if (resource->gl_id && !resource->external) {
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ DCHECK(context3d);
+ GLC(context3d, context3d->deleteTexture(resource->gl_id));
+ }
+ if (resource->gl_upload_query_id) {
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ DCHECK(context3d);
+ GLC(context3d, context3d->deleteQueryEXT(resource->gl_upload_query_id));
+ }
+ if (resource->gl_pixel_buffer_id) {
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ DCHECK(context3d);
+ GLC(context3d, context3d->deleteBuffer(resource->gl_pixel_buffer_id));
+ }
+ if (resource->mailbox.IsValid() && resource->external) {
+ unsigned sync_point = resource->mailbox.sync_point();
+ if (resource->mailbox.IsTexture()) {
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ DCHECK(context3d);
+ if (resource->gl_id)
+ GLC(context3d, context3d->deleteTexture(resource->gl_id));
+ if (!lost_resource && resource->gl_id)
+ sync_point = context3d->insertSyncPoint();
+ } else {
+ DCHECK(resource->mailbox.IsSharedMemory());
+ base::SharedMemory* shared_memory = resource->mailbox.shared_memory();
+ if (resource->pixels && shared_memory) {
+ DCHECK(shared_memory->memory() == resource->pixels);
+ resource->pixels = NULL;
+ }
+ }
+ resource->mailbox.RunReleaseCallback(sync_point, lost_resource);
+ }
+ if (resource->pixels)
+ delete[] resource->pixels;
+ if (resource->pixel_buffer)
+ delete[] resource->pixel_buffer;
+
+ resources_.erase(it);
+}
+
+ResourceProvider::ResourceType ResourceProvider::GetResourceType(
+ ResourceId id) {
+ ResourceMap::iterator it = resources_.find(id);
+ CHECK(it != resources_.end());
+ Resource* resource = &it->second;
+ return resource->type;
+}
+
+void ResourceProvider::SetPixels(ResourceId id,
+ const uint8_t* image,
+ gfx::Rect image_rect,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ResourceMap::iterator it = resources_.find(id);
+ CHECK(it != resources_.end());
+ Resource* resource = &it->second;
+ DCHECK(!resource->locked_for_write);
+ DCHECK(!resource->lock_for_read_count);
+ DCHECK(!resource->external);
+ DCHECK(!resource->exported);
+ DCHECK(ReadLockFenceHasPassed(resource));
+ LazyAllocate(resource);
+
+ if (resource->gl_id) {
+ DCHECK(!resource->pending_set_pixels);
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ DCHECK(context3d);
+ DCHECK(texture_uploader_.get());
+ context3d->bindTexture(GL_TEXTURE_2D, resource->gl_id);
+ texture_uploader_->Upload(image,
+ image_rect,
+ source_rect,
+ dest_offset,
+ resource->format,
+ resource->size);
+ }
+
+ if (resource->pixels) {
+ DCHECK(resource->allocated);
+ DCHECK(resource->format == GL_RGBA);
+ SkBitmap src_full;
+ src_full.setConfig(
+ SkBitmap::kARGB_8888_Config, image_rect.width(), image_rect.height());
+ src_full.setPixels(const_cast<uint8_t*>(image));
+ SkBitmap src_subset;
+ SkIRect sk_source_rect = SkIRect::MakeXYWH(source_rect.x(),
+ source_rect.y(),
+ source_rect.width(),
+ source_rect.height());
+ sk_source_rect.offset(-image_rect.x(), -image_rect.y());
+ src_full.extractSubset(&src_subset, sk_source_rect);
+
+ ScopedWriteLockSoftware lock(this, id);
+ SkCanvas* dest = lock.sk_canvas();
+ dest->writePixels(src_subset, dest_offset.x(), dest_offset.y());
+ }
+}
+
+size_t ResourceProvider::NumBlockingUploads() {
+ if (!texture_uploader_)
+ return 0;
+
+ return texture_uploader_->NumBlockingUploads();
+}
+
+void ResourceProvider::MarkPendingUploadsAsNonBlocking() {
+ if (!texture_uploader_)
+ return;
+
+ texture_uploader_->MarkPendingUploadsAsNonBlocking();
+}
+
+double ResourceProvider::EstimatedUploadsPerSecond() {
+ if (!texture_uploader_)
+ return 0.0;
+
+ return texture_uploader_->EstimatedTexturesPerSecond();
+}
+
+void ResourceProvider::FlushUploads() {
+ if (!texture_uploader_)
+ return;
+
+ texture_uploader_->Flush();
+}
+
+void ResourceProvider::ReleaseCachedData() {
+ if (!texture_uploader_)
+ return;
+
+ texture_uploader_->ReleaseCachedQueries();
+}
+
+void ResourceProvider::Flush() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ if (context3d)
+ context3d->flush();
+}
+
+void ResourceProvider::Finish() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ if (context3d)
+ context3d->finish();
+}
+
+bool ResourceProvider::ShallowFlushIfSupported() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ if (!context3d || !use_shallow_flush_)
+ return false;
+
+ context3d->shallowFlushCHROMIUM();
+ return true;
+}
+
+const ResourceProvider::Resource* ResourceProvider::LockForRead(ResourceId id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ResourceMap::iterator it = resources_.find(id);
+ CHECK(it != resources_.end());
+ Resource* resource = &it->second;
+ DCHECK(!resource->locked_for_write ||
+ resource->set_pixels_completion_forced) <<
+ "locked for write: " << resource->locked_for_write <<
+ " pixels completion forced: " << resource->set_pixels_completion_forced;
+ DCHECK(!resource->exported);
+ // Uninitialized! Call SetPixels or LockForWrite first.
+ DCHECK(resource->allocated);
+
+ LazyCreate(resource);
+
+ if (resource->external) {
+ if (!resource->gl_id && resource->mailbox.IsTexture()) {
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ DCHECK(context3d);
+ if (resource->mailbox.sync_point()) {
+ GLC(context3d,
+ context3d->waitSyncPoint(resource->mailbox.sync_point()));
+ resource->mailbox.ResetSyncPoint();
+ }
+ resource->gl_id = context3d->createTexture();
+ GLC(context3d, context3d->bindTexture(
+ resource->mailbox.target(), resource->gl_id));
+ GLC(context3d, context3d->consumeTextureCHROMIUM(
+ resource->mailbox.target(), resource->mailbox.data()));
+ }
+ }
+
+ resource->lock_for_read_count++;
+ if (resource->enable_read_lock_fences)
+ resource->read_lock_fence = current_read_lock_fence_;
+
+ return resource;
+}
+
+void ResourceProvider::UnlockForRead(ResourceId id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ResourceMap::iterator it = resources_.find(id);
+ CHECK(it != resources_.end());
+ Resource* resource = &it->second;
+ DCHECK_GT(resource->lock_for_read_count, 0);
+ DCHECK(!resource->exported);
+ resource->lock_for_read_count--;
+}
+
+const ResourceProvider::Resource* ResourceProvider::LockForWrite(
+ ResourceId id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ResourceMap::iterator it = resources_.find(id);
+ CHECK(it != resources_.end());
+ Resource* resource = &it->second;
+ DCHECK(!resource->locked_for_write);
+ DCHECK(!resource->lock_for_read_count);
+ DCHECK(!resource->exported);
+ DCHECK(!resource->external);
+ DCHECK(ReadLockFenceHasPassed(resource));
+ LazyAllocate(resource);
+
+ resource->locked_for_write = true;
+ return resource;
+}
+
+bool ResourceProvider::CanLockForWrite(ResourceId id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ResourceMap::iterator it = resources_.find(id);
+ CHECK(it != resources_.end());
+ Resource* resource = &it->second;
+ return !resource->locked_for_write &&
+ !resource->lock_for_read_count &&
+ !resource->exported &&
+ !resource->external &&
+ ReadLockFenceHasPassed(resource);
+}
+
+void ResourceProvider::UnlockForWrite(ResourceId id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ResourceMap::iterator it = resources_.find(id);
+ CHECK(it != resources_.end());
+ Resource* resource = &it->second;
+ DCHECK(resource->locked_for_write);
+ DCHECK(!resource->exported);
+ DCHECK(!resource->external);
+ resource->locked_for_write = false;
+}
+
+ResourceProvider::ScopedReadLockGL::ScopedReadLockGL(
+ ResourceProvider* resource_provider,
+ ResourceProvider::ResourceId resource_id)
+ : resource_provider_(resource_provider),
+ resource_id_(resource_id),
+ texture_id_(resource_provider->LockForRead(resource_id)->gl_id) {
+ DCHECK(texture_id_);
+}
+
+ResourceProvider::ScopedReadLockGL::~ScopedReadLockGL() {
+ resource_provider_->UnlockForRead(resource_id_);
+}
+
+ResourceProvider::ScopedSamplerGL::ScopedSamplerGL(
+ ResourceProvider* resource_provider,
+ ResourceProvider::ResourceId resource_id,
+ GLenum target,
+ GLenum filter)
+ : ScopedReadLockGL(resource_provider, resource_id),
+ target_(target),
+ unit_(GL_TEXTURE0) {
+ resource_provider->BindForSampling(resource_id, target, unit_, filter);
+}
+
+ResourceProvider::ScopedSamplerGL::ScopedSamplerGL(
+ ResourceProvider* resource_provider,
+ ResourceProvider::ResourceId resource_id,
+ GLenum target,
+ GLenum unit,
+ GLenum filter)
+ : ScopedReadLockGL(resource_provider, resource_id),
+ target_(target),
+ unit_(unit) {
+ resource_provider->BindForSampling(resource_id, target, unit, filter);
+}
+
+ResourceProvider::ScopedSamplerGL::~ScopedSamplerGL() {
+ resource_provider_->UnbindForSampling(resource_id_, target_, unit_);
+}
+
+ResourceProvider::ScopedWriteLockGL::ScopedWriteLockGL(
+ ResourceProvider* resource_provider,
+ ResourceProvider::ResourceId resource_id)
+ : resource_provider_(resource_provider),
+ resource_id_(resource_id),
+ texture_id_(resource_provider->LockForWrite(resource_id)->gl_id) {
+ DCHECK(texture_id_);
+}
+
+ResourceProvider::ScopedWriteLockGL::~ScopedWriteLockGL() {
+ resource_provider_->UnlockForWrite(resource_id_);
+}
+
+void ResourceProvider::PopulateSkBitmapWithResource(
+ SkBitmap* sk_bitmap, const Resource* resource) {
+ DCHECK(resource->pixels);
+ DCHECK(resource->format == GL_RGBA);
+ sk_bitmap->setConfig(SkBitmap::kARGB_8888_Config,
+ resource->size.width(),
+ resource->size.height());
+ sk_bitmap->setPixels(resource->pixels);
+}
+
+ResourceProvider::ScopedReadLockSoftware::ScopedReadLockSoftware(
+ ResourceProvider* resource_provider,
+ ResourceProvider::ResourceId resource_id)
+ : resource_provider_(resource_provider),
+ resource_id_(resource_id) {
+ ResourceProvider::PopulateSkBitmapWithResource(
+ &sk_bitmap_, resource_provider->LockForRead(resource_id));
+}
+
+ResourceProvider::ScopedReadLockSoftware::~ScopedReadLockSoftware() {
+ resource_provider_->UnlockForRead(resource_id_);
+}
+
+ResourceProvider::ScopedWriteLockSoftware::ScopedWriteLockSoftware(
+ ResourceProvider* resource_provider,
+ ResourceProvider::ResourceId resource_id)
+ : resource_provider_(resource_provider),
+ resource_id_(resource_id) {
+ ResourceProvider::PopulateSkBitmapWithResource(
+ &sk_bitmap_, resource_provider->LockForWrite(resource_id));
+ sk_canvas_.reset(new SkCanvas(sk_bitmap_));
+}
+
+ResourceProvider::ScopedWriteLockSoftware::~ScopedWriteLockSoftware() {
+ resource_provider_->UnlockForWrite(resource_id_);
+}
+
+ResourceProvider::ResourceProvider(OutputSurface* output_surface,
+ int highp_threshold_min)
+ : output_surface_(output_surface),
+ lost_output_surface_(false),
+ highp_threshold_min_(highp_threshold_min),
+ next_id_(1),
+ next_child_(1),
+ default_resource_type_(InvalidType),
+ use_texture_storage_ext_(false),
+ use_texture_usage_hint_(false),
+ use_shallow_flush_(false),
+ max_texture_size_(0),
+ best_texture_format_(0) {}
+
+void ResourceProvider::InitializeSoftware() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_NE(Bitmap, default_resource_type_);
+
+ CleanUpGLIfNeeded();
+
+ default_resource_type_ = Bitmap;
+ max_texture_size_ = INT_MAX / 2;
+ best_texture_format_ = GL_RGBA;
+}
+
+bool ResourceProvider::InitializeGL() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!texture_uploader_);
+ DCHECK_NE(GLTexture, default_resource_type_);
+
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ DCHECK(context3d);
+
+ if (!context3d->makeContextCurrent())
+ return false;
+
+ default_resource_type_ = GLTexture;
+
+ std::string extensions_string =
+ UTF16ToASCII(context3d->getString(GL_EXTENSIONS));
+ std::vector<std::string> extensions;
+ base::SplitString(extensions_string, ' ', &extensions);
+ bool use_map_sub = false;
+ bool use_bgra = false;
+ for (size_t i = 0; i < extensions.size(); ++i) {
+ if (extensions[i] == "GL_EXT_texture_storage")
+ use_texture_storage_ext_ = true;
+ else if (extensions[i] == "GL_ANGLE_texture_usage")
+ use_texture_usage_hint_ = true;
+ else if (extensions[i] == "GL_CHROMIUM_map_sub")
+ use_map_sub = true;
+ else if (extensions[i] == "GL_CHROMIUM_shallow_flush")
+ use_shallow_flush_ = true;
+ else if (extensions[i] == "GL_EXT_texture_format_BGRA8888")
+ use_bgra = true;
+ }
+
+ texture_uploader_ =
+ TextureUploader::Create(context3d, use_map_sub, use_shallow_flush_);
+ GLC(context3d, context3d->getIntegerv(GL_MAX_TEXTURE_SIZE,
+ &max_texture_size_));
+ best_texture_format_ = PlatformColor::BestTextureFormat(use_bgra);
+
+ return true;
+}
+
+void ResourceProvider::CleanUpGLIfNeeded() {
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ if (default_resource_type_ != GLTexture) {
+ // We are not in GL mode, but double check before returning.
+ DCHECK(!context3d);
+ DCHECK(!texture_uploader_);
+ return;
+ }
+
+ DCHECK(context3d);
+ context3d->makeContextCurrent();
+ texture_uploader_.reset();
+ Finish();
+}
+
+int ResourceProvider::CreateChild() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ Child child_info;
+ int child = next_child_++;
+ children_[child] = child_info;
+ return child;
+}
+
+void ResourceProvider::DestroyChild(int child_id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ChildMap::iterator it = children_.find(child_id);
+ DCHECK(it != children_.end());
+ Child& child = it->second;
+ for (ResourceIdMap::iterator child_it = child.child_to_parent_map.begin();
+ child_it != child.child_to_parent_map.end();
+ ++child_it)
+ DeleteResource(child_it->second);
+ children_.erase(it);
+}
+
+const ResourceProvider::ResourceIdMap& ResourceProvider::GetChildToParentMap(
+ int child) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ChildMap::const_iterator it = children_.find(child);
+ DCHECK(it != children_.end());
+ return it->second.child_to_parent_map;
+}
+
+void ResourceProvider::PrepareSendToParent(const ResourceIdArray& resources,
+ TransferableResourceArray* list) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ if (!context3d || !context3d->makeContextCurrent()) {
+ // TODO(skaslev): Implement this path for software compositing.
+ return;
+ }
+ bool need_sync_point = false;
+ for (ResourceIdArray::const_iterator it = resources.begin();
+ it != resources.end();
+ ++it) {
+ TransferableResource resource;
+ if (TransferResource(context3d, *it, &resource)) {
+ if (!resource.sync_point)
+ need_sync_point = true;
+ resources_.find(*it)->second.exported = true;
+ list->push_back(resource);
+ }
+ }
+ if (need_sync_point) {
+ unsigned int sync_point = context3d->insertSyncPoint();
+ for (TransferableResourceArray::iterator it = list->begin();
+ it != list->end();
+ ++it) {
+ if (!it->sync_point)
+ it->sync_point = sync_point;
+ }
+ }
+}
+
+void ResourceProvider::PrepareSendToChild(int child,
+ const ResourceIdArray& resources,
+ TransferableResourceArray* list) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ if (!context3d || !context3d->makeContextCurrent()) {
+ // TODO(skaslev): Implement this path for software compositing.
+ return;
+ }
+ Child& child_info = children_.find(child)->second;
+ bool need_sync_point = false;
+ for (ResourceIdArray::const_iterator it = resources.begin();
+ it != resources.end();
+ ++it) {
+ TransferableResource resource;
+ if (!TransferResource(context3d, *it, &resource))
+ NOTREACHED();
+ if (!resource.sync_point)
+ need_sync_point = true;
+ DCHECK(child_info.parent_to_child_map.find(*it) !=
+ child_info.parent_to_child_map.end());
+ resource.id = child_info.parent_to_child_map[*it];
+ child_info.parent_to_child_map.erase(*it);
+ child_info.child_to_parent_map.erase(resource.id);
+ list->push_back(resource);
+ DeleteResource(*it);
+ }
+ if (need_sync_point) {
+ unsigned int sync_point = context3d->insertSyncPoint();
+ for (TransferableResourceArray::iterator it = list->begin();
+ it != list->end();
+ ++it) {
+ if (!it->sync_point)
+ it->sync_point = sync_point;
+ }
+ }
+}
+
+void ResourceProvider::ReceiveFromChild(
+ int child, const TransferableResourceArray& resources) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ if (!context3d || !context3d->makeContextCurrent()) {
+ // TODO(skaslev): Implement this path for software compositing.
+ return;
+ }
+ Child& child_info = children_.find(child)->second;
+ for (TransferableResourceArray::const_iterator it = resources.begin();
+ it != resources.end();
+ ++it) {
+ unsigned texture_id;
+ // NOTE: If the parent is a browser and the child a renderer, the parent
+ // is not supposed to have its context wait, because that could induce
+ // deadlocks and/or security issues. The caller is responsible for
+ // waiting asynchronously, and resetting sync_point before calling this.
+ // However if the parent is a renderer (e.g. browser tag), it may be ok
+ // (and is simpler) to wait.
+ if (it->sync_point)
+ GLC(context3d, context3d->waitSyncPoint(it->sync_point));
+ GLC(context3d, texture_id = context3d->createTexture());
+ GLC(context3d, context3d->bindTexture(GL_TEXTURE_2D, texture_id));
+ GLC(context3d, context3d->consumeTextureCHROMIUM(GL_TEXTURE_2D,
+ it->mailbox.name));
+ ResourceId id = next_id_++;
+ Resource resource(
+ texture_id, it->size, it->format, it->filter, 0, TextureUsageAny);
+ resource.mailbox.SetName(it->mailbox);
+ // Don't allocate a texture for a child.
+ resource.allocated = true;
+ resources_[id] = resource;
+ child_info.parent_to_child_map[id] = it->id;
+ child_info.child_to_parent_map[it->id] = id;
+ }
+}
+
+void ResourceProvider::ReceiveFromParent(
+ const TransferableResourceArray& resources) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ if (!context3d || !context3d->makeContextCurrent()) {
+ // TODO(skaslev): Implement this path for software compositing.
+ return;
+ }
+ for (TransferableResourceArray::const_iterator it = resources.begin();
+ it != resources.end();
+ ++it) {
+ ResourceMap::iterator map_iterator = resources_.find(it->id);
+ DCHECK(map_iterator != resources_.end());
+ Resource* resource = &map_iterator->second;
+ DCHECK(resource->exported);
+ resource->exported = false;
+ resource->filter = it->filter;
+ DCHECK(resource->mailbox.ContainsMailbox(it->mailbox));
+ if (resource->gl_id) {
+ if (it->sync_point)
+ GLC(context3d, context3d->waitSyncPoint(it->sync_point));
+ } else {
+ resource->mailbox = TextureMailbox(resource->mailbox.name(),
+ resource->mailbox.callback(),
+ it->sync_point);
+ }
+ if (resource->marked_for_deletion)
+ DeleteResourceInternal(map_iterator, Normal);
+ }
+}
+
+bool ResourceProvider::TransferResource(WebGraphicsContext3D* context,
+ ResourceId id,
+ TransferableResource* resource) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ResourceMap::iterator it = resources_.find(id);
+ CHECK(it != resources_.end());
+ Resource* source = &it->second;
+ DCHECK(!source->locked_for_write);
+ DCHECK(!source->lock_for_read_count);
+ DCHECK(!source->external || (source->external && source->mailbox.IsValid()));
+ DCHECK(source->allocated);
+ if (source->exported)
+ return false;
+ resource->id = id;
+ resource->format = source->format;
+ resource->filter = source->filter;
+ resource->size = source->size;
+
+ // TODO(skaslev) Implement this path for shared memory resources.
+ DCHECK(!source->mailbox.IsSharedMemory());
+
+ if (!source->mailbox.IsTexture()) {
+ // This is a resource allocated by the compositor, we need to produce it.
+ // Don't set a sync point, the caller will do it.
+ DCHECK(source->gl_id);
+ GLC(context, context->bindTexture(GL_TEXTURE_2D, source->gl_id));
+ GLC(context, context->genMailboxCHROMIUM(resource->mailbox.name));
+ GLC(context, context->produceTextureCHROMIUM(GL_TEXTURE_2D,
+ resource->mailbox.name));
+ source->mailbox.SetName(resource->mailbox);
+ } else {
+ // This is either an external resource, or a compositor resource that we
+ // already exported. Make sure to forward the sync point that we were given.
+ resource->mailbox = source->mailbox.name();
+ resource->sync_point = source->mailbox.sync_point();
+ source->mailbox.ResetSyncPoint();
+ }
+
+ return true;
+}
+
+void ResourceProvider::AcquirePixelBuffer(ResourceId id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ResourceMap::iterator it = resources_.find(id);
+ CHECK(it != resources_.end());
+ Resource* resource = &it->second;
+ DCHECK(!resource->external);
+ DCHECK(!resource->exported);
+ DCHECK(!resource->image_id);
+
+ if (resource->type == GLTexture) {
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ DCHECK(context3d);
+ if (!resource->gl_pixel_buffer_id)
+ resource->gl_pixel_buffer_id = context3d->createBuffer();
+ context3d->bindBuffer(
+ GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM,
+ resource->gl_pixel_buffer_id);
+ context3d->bufferData(
+ GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM,
+ 4 * resource->size.GetArea(),
+ NULL,
+ GL_DYNAMIC_DRAW);
+ context3d->bindBuffer(GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, 0);
+ }
+
+ if (resource->pixels) {
+ if (resource->pixel_buffer)
+ return;
+
+ resource->pixel_buffer = new uint8_t[4 * resource->size.GetArea()];
+ }
+}
+
+void ResourceProvider::ReleasePixelBuffer(ResourceId id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ResourceMap::iterator it = resources_.find(id);
+ CHECK(it != resources_.end());
+ Resource* resource = &it->second;
+ DCHECK(!resource->external);
+ DCHECK(!resource->exported);
+ DCHECK(!resource->image_id);
+
+ // The pixel buffer can be released while there is a pending "set pixels"
+ // if completion has been forced. Any shared memory associated with this
+ // pixel buffer will not be freed until the waitAsyncTexImage2DCHROMIUM
+ // command has been processed on the service side. It is also safe to
+ // reuse any query id associated with this resource before they complete
+ // as each new query has a unique submit count.
+ if (resource->pending_set_pixels) {
+ DCHECK(resource->set_pixels_completion_forced);
+ resource->pending_set_pixels = false;
+ UnlockForWrite(id);
+ }
+
+ if (resource->type == GLTexture) {
+ if (!resource->gl_pixel_buffer_id)
+ return;
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ DCHECK(context3d);
+ context3d->bindBuffer(
+ GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM,
+ resource->gl_pixel_buffer_id);
+ context3d->bufferData(
+ GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM,
+ 0,
+ NULL,
+ GL_DYNAMIC_DRAW);
+ context3d->bindBuffer(GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, 0);
+ }
+
+ if (resource->pixels) {
+ if (!resource->pixel_buffer)
+ return;
+ delete[] resource->pixel_buffer;
+ resource->pixel_buffer = NULL;
+ }
+}
+
+uint8_t* ResourceProvider::MapPixelBuffer(ResourceId id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ResourceMap::iterator it = resources_.find(id);
+ CHECK(it != resources_.end());
+ Resource* resource = &it->second;
+ DCHECK(!resource->external);
+ DCHECK(!resource->exported);
+ DCHECK(!resource->image_id);
+
+ if (resource->type == GLTexture) {
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ DCHECK(context3d);
+ DCHECK(resource->gl_pixel_buffer_id);
+ context3d->bindBuffer(
+ GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM,
+ resource->gl_pixel_buffer_id);
+ uint8_t* image = static_cast<uint8_t*>(
+ context3d->mapBufferCHROMIUM(
+ GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, GL_WRITE_ONLY));
+ context3d->bindBuffer(GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, 0);
+ // Buffer is required to be 4-byte aligned.
+ CHECK(!(reinterpret_cast<intptr_t>(image) & 3));
+ return image;
+ }
+
+ if (resource->pixels)
+ return resource->pixel_buffer;
+
+ return NULL;
+}
+
+void ResourceProvider::UnmapPixelBuffer(ResourceId id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ResourceMap::iterator it = resources_.find(id);
+ CHECK(it != resources_.end());
+ Resource* resource = &it->second;
+ DCHECK(!resource->external);
+ DCHECK(!resource->exported);
+ DCHECK(!resource->image_id);
+
+ if (resource->type == GLTexture) {
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ DCHECK(context3d);
+ DCHECK(resource->gl_pixel_buffer_id);
+ context3d->bindBuffer(
+ GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM,
+ resource->gl_pixel_buffer_id);
+ context3d->unmapBufferCHROMIUM(GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM);
+ context3d->bindBuffer(GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, 0);
+ }
+}
+
+void ResourceProvider::BindForSampling(ResourceProvider::ResourceId resource_id,
+ GLenum target,
+ GLenum unit,
+ GLenum filter) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ ResourceMap::iterator it = resources_.find(resource_id);
+ DCHECK(it != resources_.end());
+ Resource* resource = &it->second;
+ DCHECK(resource->lock_for_read_count);
+ DCHECK(!resource->locked_for_write || resource->set_pixels_completion_forced);
+ DCHECK_EQ(GL_TEXTURE0, GetActiveTextureUnit(context3d));
+
+ if (unit != GL_TEXTURE0)
+ GLC(context3d, context3d->activeTexture(unit));
+ GLC(context3d, context3d->bindTexture(target, resource->gl_id));
+ if (filter != resource->filter) {
+ GLC(context3d, context3d->texParameteri(target,
+ GL_TEXTURE_MIN_FILTER,
+ filter));
+ GLC(context3d, context3d->texParameteri(target,
+ GL_TEXTURE_MAG_FILTER,
+ filter));
+ resource->filter = filter;
+ }
+
+ if (resource->image_id)
+ context3d->bindTexImage2DCHROMIUM(target, resource->image_id);
+
+ // Active unit being GL_TEXTURE0 is effectively the ground state.
+ if (unit != GL_TEXTURE0)
+ GLC(context3d, context3d->activeTexture(GL_TEXTURE0));
+}
+
+void ResourceProvider::UnbindForSampling(
+ ResourceProvider::ResourceId resource_id, GLenum target, GLenum unit) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ResourceMap::iterator it = resources_.find(resource_id);
+ DCHECK(it != resources_.end());
+ Resource* resource = &it->second;
+
+ if (!resource->image_id)
+ return;
+
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ DCHECK_EQ(GL_TEXTURE0, GetActiveTextureUnit(context3d));
+ if (unit != GL_TEXTURE0)
+ GLC(context3d, context3d->activeTexture(unit));
+ context3d->releaseTexImage2DCHROMIUM(target, resource->image_id);
+ // Active unit being GL_TEXTURE0 is effectively the ground state.
+ if (unit != GL_TEXTURE0)
+ GLC(context3d, context3d->activeTexture(GL_TEXTURE0));
+}
+
+void ResourceProvider::BeginSetPixels(ResourceId id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ResourceMap::iterator it = resources_.find(id);
+ CHECK(it != resources_.end());
+ Resource* resource = &it->second;
+ DCHECK(!resource->pending_set_pixels);
+
+ LazyCreate(resource);
+ DCHECK(resource->gl_id || resource->allocated);
+ DCHECK(ReadLockFenceHasPassed(resource));
+ DCHECK(!resource->image_id);
+
+ bool allocate = !resource->allocated;
+ resource->allocated = true;
+ LockForWrite(id);
+
+ if (resource->gl_id) {
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ DCHECK(context3d);
+ DCHECK(resource->gl_pixel_buffer_id);
+ context3d->bindTexture(GL_TEXTURE_2D, resource->gl_id);
+ context3d->bindBuffer(
+ GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM,
+ resource->gl_pixel_buffer_id);
+ if (!resource->gl_upload_query_id)
+ resource->gl_upload_query_id = context3d->createQueryEXT();
+ context3d->beginQueryEXT(
+ GL_ASYNC_PIXEL_TRANSFERS_COMPLETED_CHROMIUM,
+ resource->gl_upload_query_id);
+ if (allocate) {
+ context3d->asyncTexImage2DCHROMIUM(GL_TEXTURE_2D,
+ 0, /* level */
+ resource->format,
+ resource->size.width(),
+ resource->size.height(),
+ 0, /* border */
+ resource->format,
+ GL_UNSIGNED_BYTE,
+ NULL);
+ } else {
+ context3d->asyncTexSubImage2DCHROMIUM(GL_TEXTURE_2D,
+ 0, /* level */
+ 0, /* x */
+ 0, /* y */
+ resource->size.width(),
+ resource->size.height(),
+ resource->format,
+ GL_UNSIGNED_BYTE,
+ NULL);
+ }
+ context3d->endQueryEXT(GL_ASYNC_PIXEL_TRANSFERS_COMPLETED_CHROMIUM);
+ context3d->bindBuffer(GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, 0);
+ }
+
+ if (resource->pixels) {
+ DCHECK(!resource->mailbox.IsValid());
+ DCHECK(resource->pixel_buffer);
+ DCHECK(resource->format == GL_RGBA);
+
+ std::swap(resource->pixels, resource->pixel_buffer);
+ delete[] resource->pixel_buffer;
+ resource->pixel_buffer = NULL;
+ }
+
+ resource->pending_set_pixels = true;
+ resource->set_pixels_completion_forced = false;
+}
+
+void ResourceProvider::ForceSetPixelsToComplete(ResourceId id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ResourceMap::iterator it = resources_.find(id);
+ CHECK(it != resources_.end());
+ Resource* resource = &it->second;
+ DCHECK(resource->locked_for_write);
+ DCHECK(resource->pending_set_pixels);
+ DCHECK(!resource->set_pixels_completion_forced);
+
+ if (resource->gl_id) {
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ GLC(context3d, context3d->bindTexture(GL_TEXTURE_2D, resource->gl_id));
+ GLC(context3d, context3d->waitAsyncTexImage2DCHROMIUM(GL_TEXTURE_2D));
+ GLC(context3d, context3d->bindTexture(GL_TEXTURE_2D, 0));
+ }
+
+ resource->set_pixels_completion_forced = true;
+}
+
+bool ResourceProvider::DidSetPixelsComplete(ResourceId id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ResourceMap::iterator it = resources_.find(id);
+ CHECK(it != resources_.end());
+ Resource* resource = &it->second;
+ DCHECK(resource->locked_for_write);
+ DCHECK(resource->pending_set_pixels);
+
+ if (resource->gl_id) {
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ DCHECK(context3d);
+ DCHECK(resource->gl_upload_query_id);
+ unsigned complete = 1;
+ context3d->getQueryObjectuivEXT(
+ resource->gl_upload_query_id,
+ GL_QUERY_RESULT_AVAILABLE_EXT,
+ &complete);
+ if (!complete)
+ return false;
+ }
+
+ resource->pending_set_pixels = false;
+ UnlockForWrite(id);
+
+ return true;
+}
+
+void ResourceProvider::CreateForTesting(ResourceId id) {
+ ResourceMap::iterator it = resources_.find(id);
+ CHECK(it != resources_.end());
+ Resource* resource = &it->second;
+ LazyCreate(resource);
+}
+
+void ResourceProvider::LazyCreate(Resource* resource) {
+ if (resource->type != GLTexture || resource->gl_id != 0)
+ return;
+
+ // Early out for resources that don't require texture creation.
+ if (resource->texture_pool == 0)
+ return;
+
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ DCHECK(context3d);
+ // Create and set texture properties. Allocation is delayed until needed.
+ resource->gl_id = CreateTextureId(context3d);
+ GLC(context3d, context3d->texParameteri(GL_TEXTURE_2D,
+ GL_TEXTURE_POOL_CHROMIUM,
+ resource->texture_pool));
+ if (use_texture_usage_hint_ && resource->hint == TextureUsageFramebuffer) {
+ GLC(context3d, context3d->texParameteri(GL_TEXTURE_2D,
+ GL_TEXTURE_USAGE_ANGLE,
+ GL_FRAMEBUFFER_ATTACHMENT_ANGLE));
+ }
+}
+
+void ResourceProvider::AllocateForTesting(ResourceId id) {
+ ResourceMap::iterator it = resources_.find(id);
+ CHECK(it != resources_.end());
+ Resource* resource = &it->second;
+ LazyAllocate(resource);
+}
+
+void ResourceProvider::LazyAllocate(Resource* resource) {
+ DCHECK(resource);
+ LazyCreate(resource);
+
+ DCHECK(resource->gl_id || resource->allocated);
+ if (resource->allocated || !resource->gl_id)
+ return;
+ resource->allocated = true;
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ gfx::Size& size = resource->size;
+ GLenum format = resource->format;
+ GLC(context3d, context3d->bindTexture(GL_TEXTURE_2D, resource->gl_id));
+ if (use_texture_storage_ext_ && IsTextureFormatSupportedForStorage(format)) {
+ GLenum storage_format = TextureToStorageFormat(format);
+ GLC(context3d, context3d->texStorage2DEXT(GL_TEXTURE_2D,
+ 1,
+ storage_format,
+ size.width(),
+ size.height()));
+ } else {
+ GLC(context3d, context3d->texImage2D(GL_TEXTURE_2D,
+ 0,
+ format,
+ size.width(),
+ size.height(),
+ 0,
+ format,
+ GL_UNSIGNED_BYTE,
+ NULL));
+ }
+}
+
+void ResourceProvider::EnableReadLockFences(ResourceProvider::ResourceId id,
+ bool enable) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ResourceMap::iterator it = resources_.find(id);
+ CHECK(it != resources_.end());
+ Resource* resource = &it->second;
+ resource->enable_read_lock_fences = enable;
+}
+
+void ResourceProvider::AcquireImage(ResourceId id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ResourceMap::iterator it = resources_.find(id);
+ CHECK(it != resources_.end());
+ Resource* resource = &it->second;
+
+ DCHECK(!resource->external);
+ DCHECK(!resource->exported);
+
+ if (resource->type != GLTexture)
+ return;
+
+ if (resource->image_id)
+ return;
+
+ resource->allocated = true;
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ DCHECK(context3d);
+ resource->image_id = context3d->createImageCHROMIUM(
+ resource->size.width(), resource->size.height(), GL_RGBA8_OES);
+ DCHECK(resource->image_id);
+}
+
+void ResourceProvider::ReleaseImage(ResourceId id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ResourceMap::iterator it = resources_.find(id);
+ CHECK(it != resources_.end());
+ Resource* resource = &it->second;
+
+ DCHECK(!resource->external);
+ DCHECK(!resource->exported);
+
+ if (!resource->image_id)
+ return;
+
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ DCHECK(context3d);
+ context3d->destroyImageCHROMIUM(resource->image_id);
+ resource->image_id = 0;
+ resource->allocated = false;
+}
+
+uint8_t* ResourceProvider::MapImage(ResourceId id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ResourceMap::iterator it = resources_.find(id);
+ CHECK(it != resources_.end());
+ Resource* resource = &it->second;
+
+ DCHECK(ReadLockFenceHasPassed(resource));
+ DCHECK(!resource->external);
+ DCHECK(!resource->exported);
+
+ if (resource->image_id) {
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ DCHECK(context3d);
+ return static_cast<uint8_t*>(
+ context3d->mapImageCHROMIUM(resource->image_id, GL_READ_WRITE));
+ }
+
+ if (resource->pixels)
+ return resource->pixels;
+
+ return NULL;
+}
+
+void ResourceProvider::UnmapImage(ResourceId id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ResourceMap::iterator it = resources_.find(id);
+ CHECK(it != resources_.end());
+ Resource* resource = &it->second;
+
+ DCHECK(!resource->external);
+ DCHECK(!resource->exported);
+
+ if (resource->image_id) {
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ DCHECK(context3d);
+ context3d->unmapImageCHROMIUM(resource->image_id);
+ }
+}
+
+int ResourceProvider::GetImageStride(ResourceId id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ResourceMap::iterator it = resources_.find(id);
+ CHECK(it != resources_.end());
+ Resource* resource = &it->second;
+
+ DCHECK(!resource->external);
+ DCHECK(!resource->exported);
+
+ int stride = 0;
+
+ if (resource->image_id) {
+ WebGraphicsContext3D* context3d = output_surface_->context3d();
+ DCHECK(context3d);
+ context3d->getImageParameterivCHROMIUM(
+ resource->image_id, GL_IMAGE_ROWBYTES_CHROMIUM, &stride);
+ }
+
+ return stride;
+}
+
+GLint ResourceProvider::GetActiveTextureUnit(WebGraphicsContext3D* context) {
+ GLint active_unit = 0;
+ context->getIntegerv(GL_ACTIVE_TEXTURE, &active_unit);
+ return active_unit;
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/resource_provider.h b/chromium/cc/resources/resource_provider.h
new file mode 100644
index 00000000000..b5dccc8f046
--- /dev/null
+++ b/chromium/cc/resources/resource_provider.h
@@ -0,0 +1,451 @@
+// 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.
+
+#ifndef CC_RESOURCES_RESOURCE_PROVIDER_H_
+#define CC_RESOURCES_RESOURCE_PROVIDER_H_
+
+#include <deque>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "cc/base/cc_export.h"
+#include "cc/output/context_provider.h"
+#include "cc/output/output_surface.h"
+#include "cc/resources/texture_mailbox.h"
+#include "cc/resources/transferable_resource.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "ui/gfx/size.h"
+
+namespace WebKit { class WebGraphicsContext3D; }
+
+namespace gfx {
+class Rect;
+class Vector2d;
+}
+
+namespace cc {
+class TextureUploader;
+
+// This class is not thread-safe and can only be called from the thread it was
+// created on (in practice, the impl thread).
+class CC_EXPORT ResourceProvider {
+ public:
+ typedef unsigned ResourceId;
+ typedef std::vector<ResourceId> ResourceIdArray;
+ typedef std::set<ResourceId> ResourceIdSet;
+ typedef base::hash_map<ResourceId, ResourceId> ResourceIdMap;
+ enum TextureUsageHint {
+ TextureUsageAny,
+ TextureUsageFramebuffer,
+ };
+ enum ResourceType {
+ InvalidType = 0,
+ GLTexture = 1,
+ Bitmap,
+ };
+
+ static scoped_ptr<ResourceProvider> Create(OutputSurface* output_surface,
+ int highp_threshold_min);
+
+ virtual ~ResourceProvider();
+
+ void InitializeSoftware();
+ bool InitializeGL();
+
+ void DidLoseOutputSurface() { lost_output_surface_ = true; }
+
+ WebKit::WebGraphicsContext3D* GraphicsContext3D();
+ int max_texture_size() const { return max_texture_size_; }
+ GLenum best_texture_format() const { return best_texture_format_; }
+ size_t num_resources() const { return resources_.size(); }
+
+ // Checks whether a resource is in use by a consumer.
+ bool InUseByConsumer(ResourceId id);
+
+
+ // Producer interface.
+
+ ResourceType default_resource_type() const { return default_resource_type_; }
+ ResourceType GetResourceType(ResourceId id);
+
+ // Creates a resource of the default resource type.
+ ResourceId CreateResource(gfx::Size size,
+ GLenum format,
+ TextureUsageHint hint);
+
+ // Creates a resource which is tagged as being managed for GPU memory
+ // accounting purposes.
+ ResourceId CreateManagedResource(gfx::Size size,
+ GLenum format,
+ TextureUsageHint hint);
+
+ // You can also explicitly create a specific resource type.
+ ResourceId CreateGLTexture(gfx::Size size,
+ GLenum format,
+ GLenum texture_pool,
+ TextureUsageHint hint);
+
+ ResourceId CreateBitmap(gfx::Size size);
+ // Wraps an external texture into a GL resource.
+ ResourceId CreateResourceFromExternalTexture(
+ unsigned texture_target,
+ unsigned texture_id);
+
+ // Wraps an external texture mailbox into a GL resource.
+ ResourceId CreateResourceFromTextureMailbox(const TextureMailbox& mailbox);
+
+ void DeleteResource(ResourceId id);
+
+ // Update pixels from image, copying source_rect (in image) to dest_offset (in
+ // the resource).
+ void SetPixels(ResourceId id,
+ const uint8_t* image,
+ gfx::Rect image_rect,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset);
+
+ // Check upload status.
+ size_t NumBlockingUploads();
+ void MarkPendingUploadsAsNonBlocking();
+ double EstimatedUploadsPerSecond();
+ void FlushUploads();
+ void ReleaseCachedData();
+
+ // Flush all context operations, kicking uploads and ensuring ordering with
+ // respect to other contexts.
+ void Flush();
+
+ // Finish all context operations, causing any pending callbacks to be
+ // scheduled.
+ void Finish();
+
+ // Only flush the command buffer if supported.
+ // Returns true if the shallow flush occurred, false otherwise.
+ bool ShallowFlushIfSupported();
+
+ // Creates accounting for a child. Returns a child ID.
+ int CreateChild();
+
+ // Destroys accounting for the child, deleting all accounted resources.
+ void DestroyChild(int child);
+
+ // Gets the child->parent resource ID map.
+ const ResourceIdMap& GetChildToParentMap(int child) const;
+
+ // Prepares resources to be transfered to the parent, moving them to
+ // mailboxes and serializing meta-data into TransferableResources.
+ // Resources are not removed from the ResourceProvider, but are marked as
+ // "in use".
+ void PrepareSendToParent(const ResourceIdArray& resources,
+ TransferableResourceArray* transferable_resources);
+
+ // Prepares resources to be transfered back to the child, moving them to
+ // mailboxes and serializing meta-data into TransferableResources.
+ // Resources are removed from the ResourceProvider. Note: the resource IDs
+ // passed are in the parent namespace and will be translated to the child
+ // namespace when returned.
+ void PrepareSendToChild(int child,
+ const ResourceIdArray& resources,
+ TransferableResourceArray* transferable_resources);
+
+ // Receives resources from a child, moving them from mailboxes. Resource IDs
+ // passed are in the child namespace, and will be translated to the parent
+ // namespace, added to the child->parent map.
+ // NOTE: if the sync_point is set on any TransferableResource, this will
+ // wait on it.
+ void ReceiveFromChild(
+ int child, const TransferableResourceArray& transferable_resources);
+
+ // Receives resources from the parent, moving them from mailboxes. Resource
+ // IDs passed are in the child namespace.
+ // NOTE: if the sync_point is set on any TransferableResource, this will
+ // wait on it.
+ void ReceiveFromParent(
+ const TransferableResourceArray& transferable_resources);
+
+ // The following lock classes are part of the ResourceProvider API and are
+ // needed to read and write the resource contents. The user must ensure
+ // that they only use GL locks on GL resources, etc, and this is enforced
+ // by assertions.
+ class CC_EXPORT ScopedReadLockGL {
+ public:
+ ScopedReadLockGL(ResourceProvider* resource_provider,
+ ResourceProvider::ResourceId resource_id);
+ virtual ~ScopedReadLockGL();
+
+ unsigned texture_id() const { return texture_id_; }
+
+ protected:
+ ResourceProvider* resource_provider_;
+ ResourceProvider::ResourceId resource_id_;
+
+ private:
+ unsigned texture_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedReadLockGL);
+ };
+
+ class CC_EXPORT ScopedSamplerGL : public ScopedReadLockGL {
+ public:
+ ScopedSamplerGL(ResourceProvider* resource_provider,
+ ResourceProvider::ResourceId resource_id,
+ GLenum target,
+ GLenum filter);
+ ScopedSamplerGL(ResourceProvider* resource_provider,
+ ResourceProvider::ResourceId resource_id,
+ GLenum target,
+ GLenum unit,
+ GLenum filter);
+ virtual ~ScopedSamplerGL();
+
+ private:
+ GLenum target_;
+ GLenum unit_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedSamplerGL);
+ };
+
+ class CC_EXPORT ScopedWriteLockGL {
+ public:
+ ScopedWriteLockGL(ResourceProvider* resource_provider,
+ ResourceProvider::ResourceId resource_id);
+ ~ScopedWriteLockGL();
+
+ unsigned texture_id() const { return texture_id_; }
+
+ private:
+ ResourceProvider* resource_provider_;
+ ResourceProvider::ResourceId resource_id_;
+ unsigned texture_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedWriteLockGL);
+ };
+
+ class CC_EXPORT ScopedReadLockSoftware {
+ public:
+ ScopedReadLockSoftware(ResourceProvider* resource_provider,
+ ResourceProvider::ResourceId resource_id);
+ ~ScopedReadLockSoftware();
+
+ const SkBitmap* sk_bitmap() const { return &sk_bitmap_; }
+
+ private:
+ ResourceProvider* resource_provider_;
+ ResourceProvider::ResourceId resource_id_;
+ SkBitmap sk_bitmap_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedReadLockSoftware);
+ };
+
+ class CC_EXPORT ScopedWriteLockSoftware {
+ public:
+ ScopedWriteLockSoftware(ResourceProvider* resource_provider,
+ ResourceProvider::ResourceId resource_id);
+ ~ScopedWriteLockSoftware();
+
+ SkCanvas* sk_canvas() { return sk_canvas_.get(); }
+
+ private:
+ ResourceProvider* resource_provider_;
+ ResourceProvider::ResourceId resource_id_;
+ SkBitmap sk_bitmap_;
+ scoped_ptr<SkCanvas> sk_canvas_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedWriteLockSoftware);
+ };
+
+ class Fence : public base::RefCounted<Fence> {
+ public:
+ Fence() {}
+ virtual bool HasPassed() = 0;
+
+ protected:
+ friend class base::RefCounted<Fence>;
+ virtual ~Fence() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Fence);
+ };
+
+ // Acquire pixel buffer for resource. The pixel buffer can be used to
+ // set resource pixels without performing unnecessary copying.
+ void AcquirePixelBuffer(ResourceId id);
+ void ReleasePixelBuffer(ResourceId id);
+
+ // Map/unmap the acquired pixel buffer.
+ uint8_t* MapPixelBuffer(ResourceId id);
+ void UnmapPixelBuffer(ResourceId id);
+
+ // Asynchronously update pixels from acquired pixel buffer.
+ void BeginSetPixels(ResourceId id);
+ void ForceSetPixelsToComplete(ResourceId id);
+ bool DidSetPixelsComplete(ResourceId id);
+
+ // Acquire and release an image. The image allows direct
+ // manipulation of texture memory.
+ void AcquireImage(ResourceId id);
+ void ReleaseImage(ResourceId id);
+
+ // Maps the acquired image so that its pixels could be modified.
+ // Unmap is called when all pixels are set.
+ uint8_t* MapImage(ResourceId id);
+ void UnmapImage(ResourceId id);
+
+ // Returns the stride for the image.
+ int GetImageStride(ResourceId id);
+
+ // For tests only! This prevents detecting uninitialized reads.
+ // Use SetPixels or LockForWrite to allocate implicitly.
+ void AllocateForTesting(ResourceId id);
+
+ // For tests only!
+ void CreateForTesting(ResourceId id);
+
+ // Sets the current read fence. If a resource is locked for read
+ // and has read fences enabled, the resource will not allow writes
+ // until this fence has passed.
+ void SetReadLockFence(scoped_refptr<Fence> fence) {
+ current_read_lock_fence_ = fence;
+ }
+ Fence* GetReadLockFence() { return current_read_lock_fence_.get(); }
+
+ // Enable read lock fences for a specific resource.
+ void EnableReadLockFences(ResourceProvider::ResourceId id, bool enable);
+
+ // Indicates if we can currently lock this resource for write.
+ bool CanLockForWrite(ResourceId id);
+
+ cc::ContextProvider* offscreen_context_provider() {
+ return offscreen_context_provider_.get();
+ }
+ void set_offscreen_context_provider(
+ scoped_refptr<cc::ContextProvider> offscreen_context_provider) {
+ offscreen_context_provider_ = offscreen_context_provider;
+ }
+ static GLint GetActiveTextureUnit(WebKit::WebGraphicsContext3D* context);
+
+ private:
+ struct Resource {
+ Resource();
+ ~Resource();
+ Resource(unsigned texture_id,
+ gfx::Size size,
+ GLenum format,
+ GLenum filter,
+ GLenum texture_pool,
+ TextureUsageHint hint);
+ Resource(uint8_t* pixels, gfx::Size size, GLenum format, GLenum filter);
+
+ unsigned gl_id;
+ // Pixel buffer used for set pixels without unnecessary copying.
+ unsigned gl_pixel_buffer_id;
+ // Query used to determine when asynchronous set pixels complete.
+ unsigned gl_upload_query_id;
+ TextureMailbox mailbox;
+ uint8_t* pixels;
+ uint8_t* pixel_buffer;
+ int lock_for_read_count;
+ bool locked_for_write;
+ bool external;
+ bool exported;
+ bool marked_for_deletion;
+ bool pending_set_pixels;
+ bool set_pixels_completion_forced;
+ bool allocated;
+ bool enable_read_lock_fences;
+ scoped_refptr<Fence> read_lock_fence;
+ gfx::Size size;
+ GLenum format;
+ // TODO(skyostil): Use a separate sampler object for filter state.
+ GLenum filter;
+ unsigned image_id;
+ GLenum texture_pool;
+ TextureUsageHint hint;
+ ResourceType type;
+ };
+ typedef base::hash_map<ResourceId, Resource> ResourceMap;
+ struct Child {
+ Child();
+ ~Child();
+
+ ResourceIdMap child_to_parent_map;
+ ResourceIdMap parent_to_child_map;
+ };
+ typedef base::hash_map<int, Child> ChildMap;
+
+ bool ReadLockFenceHasPassed(Resource* resource) {
+ return !resource->read_lock_fence.get() ||
+ resource->read_lock_fence->HasPassed();
+ }
+
+ explicit ResourceProvider(OutputSurface* output_surface,
+ int highp_threshold_min);
+
+ void CleanUpGLIfNeeded();
+
+ const Resource* LockForRead(ResourceId id);
+ void UnlockForRead(ResourceId id);
+ const Resource* LockForWrite(ResourceId id);
+ void UnlockForWrite(ResourceId id);
+ static void PopulateSkBitmapWithResource(SkBitmap* sk_bitmap,
+ const Resource* resource);
+
+ bool TransferResource(WebKit::WebGraphicsContext3D* context,
+ ResourceId id,
+ TransferableResource* resource);
+ enum DeleteStyle {
+ Normal,
+ ForShutdown,
+ };
+ void DeleteResourceInternal(ResourceMap::iterator it, DeleteStyle style);
+ void LazyCreate(Resource* resource);
+ void LazyAllocate(Resource* resource);
+
+ // Binds the given GL resource to a texture target for sampling using the
+ // specified filter for both minification and magnification. The resource
+ // must be locked for reading.
+ void BindForSampling(ResourceProvider::ResourceId resource_id,
+ GLenum target,
+ GLenum unit,
+ GLenum filter);
+ void UnbindForSampling(ResourceProvider::ResourceId resource_id,
+ GLenum target,
+ GLenum unit);
+
+ OutputSurface* output_surface_;
+ bool lost_output_surface_;
+ int highp_threshold_min_;
+ ResourceId next_id_;
+ ResourceMap resources_;
+ int next_child_;
+ ChildMap children_;
+
+ ResourceType default_resource_type_;
+ bool use_texture_storage_ext_;
+ bool use_texture_usage_hint_;
+ bool use_shallow_flush_;
+ scoped_ptr<TextureUploader> texture_uploader_;
+ int max_texture_size_;
+ GLenum best_texture_format_;
+
+ scoped_refptr<cc::ContextProvider> offscreen_context_provider_;
+
+ base::ThreadChecker thread_checker_;
+
+ scoped_refptr<Fence> current_read_lock_fence_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResourceProvider);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_RESOURCE_PROVIDER_H_
diff --git a/chromium/cc/resources/resource_provider_unittest.cc b/chromium/cc/resources/resource_provider_unittest.cc
new file mode 100644
index 00000000000..ad6b3d36f2a
--- /dev/null
+++ b/chromium/cc/resources/resource_provider_unittest.cc
@@ -0,0 +1,1743 @@
+// 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 "cc/resources/resource_provider.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/containers/hash_tables.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "cc/base/scoped_ptr_deque.h"
+#include "cc/debug/test_web_graphics_context_3d.h"
+#include "cc/output/output_surface.h"
+#include "cc/test/fake_output_surface.h"
+#include "cc/test/fake_output_surface_client.h"
+#include "gpu/GLES2/gl2extchromium.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "third_party/khronos/GLES2/gl2ext.h"
+#include "ui/gfx/rect.h"
+
+using testing::Mock;
+using testing::NiceMock;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::StrictMock;
+using testing::_;
+using WebKit::WGC3Dbyte;
+using WebKit::WGC3Denum;
+using WebKit::WGC3Dint;
+using WebKit::WGC3Dsizei;
+using WebKit::WGC3Duint;
+using WebKit::WebGLId;
+
+namespace cc {
+namespace {
+
+size_t TextureSize(gfx::Size size, WGC3Denum format) {
+ unsigned int components_per_pixel = 4;
+ unsigned int bytes_per_component = 1;
+ return size.width() * size.height() * components_per_pixel *
+ bytes_per_component;
+}
+
+struct Texture : public base::RefCounted<Texture> {
+ Texture() : format(0), filter(GL_NEAREST_MIPMAP_LINEAR) {}
+
+ void Reallocate(gfx::Size size, WGC3Denum format) {
+ this->size = size;
+ this->format = format;
+ this->data.reset(new uint8_t[TextureSize(size, format)]);
+ }
+
+ gfx::Size size;
+ WGC3Denum format;
+ WGC3Denum filter;
+ scoped_ptr<uint8_t[]> data;
+
+ private:
+ friend class base::RefCounted<Texture>;
+ ~Texture() {}
+};
+
+// Shared data between multiple ResourceProviderContext. This contains mailbox
+// contents as well as information about sync points.
+class ContextSharedData {
+ public:
+ static scoped_ptr<ContextSharedData> Create() {
+ return make_scoped_ptr(new ContextSharedData());
+ }
+
+ unsigned InsertSyncPoint() { return next_sync_point_++; }
+
+ void GenMailbox(WGC3Dbyte* mailbox) {
+ memset(mailbox, 0, sizeof(WGC3Dbyte[64]));
+ memcpy(mailbox, &next_mailbox_, sizeof(next_mailbox_));
+ ++next_mailbox_;
+ }
+
+ void ProduceTexture(const WGC3Dbyte* mailbox_name,
+ unsigned sync_point,
+ scoped_refptr<Texture> texture) {
+ unsigned mailbox = 0;
+ memcpy(&mailbox, mailbox_name, sizeof(mailbox));
+ ASSERT_TRUE(mailbox && mailbox < next_mailbox_);
+ textures_[mailbox] = texture;
+ ASSERT_LT(sync_point_for_mailbox_[mailbox], sync_point);
+ sync_point_for_mailbox_[mailbox] = sync_point;
+ }
+
+ scoped_refptr<Texture> ConsumeTexture(const WGC3Dbyte* mailbox_name,
+ unsigned sync_point) {
+ unsigned mailbox = 0;
+ memcpy(&mailbox, mailbox_name, sizeof(mailbox));
+ DCHECK(mailbox && mailbox < next_mailbox_);
+
+ // If the latest sync point the context has waited on is before the sync
+ // point for when the mailbox was set, pretend we never saw that
+ // ProduceTexture.
+ if (sync_point_for_mailbox_[mailbox] > sync_point) {
+ NOTREACHED();
+ return scoped_refptr<Texture>();
+ }
+ return textures_[mailbox];
+ }
+
+ private:
+ ContextSharedData() : next_sync_point_(1), next_mailbox_(1) {}
+
+ unsigned next_sync_point_;
+ unsigned next_mailbox_;
+ typedef base::hash_map<unsigned, scoped_refptr<Texture> > TextureMap;
+ TextureMap textures_;
+ base::hash_map<unsigned, unsigned> sync_point_for_mailbox_;
+};
+
+class ResourceProviderContext : public TestWebGraphicsContext3D {
+ public:
+ static scoped_ptr<ResourceProviderContext> Create(
+ ContextSharedData* shared_data) {
+ return make_scoped_ptr(
+ new ResourceProviderContext(Attributes(), shared_data));
+ }
+
+ virtual unsigned insertSyncPoint() OVERRIDE {
+ unsigned sync_point = shared_data_->InsertSyncPoint();
+ // Commit the produceTextureCHROMIUM calls at this point, so that
+ // they're associated with the sync point.
+ for (PendingProduceTextureList::iterator it =
+ pending_produce_textures_.begin();
+ it != pending_produce_textures_.end();
+ ++it) {
+ shared_data_->ProduceTexture(
+ (*it)->mailbox, sync_point, (*it)->texture);
+ }
+ pending_produce_textures_.clear();
+ return sync_point;
+ }
+
+ virtual void waitSyncPoint(unsigned sync_point) OVERRIDE {
+ last_waited_sync_point_ = std::max(sync_point, last_waited_sync_point_);
+ }
+
+ virtual void bindTexture(WGC3Denum target, WebGLId texture) OVERRIDE {
+ ASSERT_EQ(static_cast<unsigned>(GL_TEXTURE_2D), target);
+ ASSERT_TRUE(!texture || textures_.find(texture) != textures_.end());
+ current_texture_ = texture;
+ }
+
+ virtual WebGLId createTexture() OVERRIDE {
+ WebGLId id = TestWebGraphicsContext3D::createTexture();
+ textures_[id] = new Texture;
+ return id;
+ }
+
+ virtual void deleteTexture(WebGLId id) OVERRIDE {
+ TextureMap::iterator it = textures_.find(id);
+ ASSERT_FALSE(it == textures_.end());
+ textures_.erase(it);
+ if (current_texture_ == id)
+ current_texture_ = 0;
+ }
+
+ virtual void texStorage2DEXT(WGC3Denum target,
+ WGC3Dint levels,
+ WGC3Duint internalformat,
+ WGC3Dint width,
+ WGC3Dint height) OVERRIDE {
+ ASSERT_TRUE(current_texture_);
+ ASSERT_EQ(static_cast<unsigned>(GL_TEXTURE_2D), target);
+ ASSERT_EQ(1, levels);
+ WGC3Denum format = GL_RGBA;
+ switch (internalformat) {
+ case GL_RGBA8_OES:
+ break;
+ case GL_BGRA8_EXT:
+ format = GL_BGRA_EXT;
+ break;
+ default:
+ NOTREACHED();
+ }
+ AllocateTexture(gfx::Size(width, height), format);
+ }
+
+ virtual void texImage2D(WGC3Denum target,
+ WGC3Dint level,
+ WGC3Denum internalformat,
+ WGC3Dsizei width,
+ WGC3Dsizei height,
+ WGC3Dint border,
+ WGC3Denum format,
+ WGC3Denum type,
+ const void* pixels) OVERRIDE {
+ ASSERT_TRUE(current_texture_);
+ ASSERT_EQ(static_cast<unsigned>(GL_TEXTURE_2D), target);
+ ASSERT_FALSE(level);
+ ASSERT_EQ(internalformat, format);
+ ASSERT_FALSE(border);
+ ASSERT_EQ(static_cast<unsigned>(GL_UNSIGNED_BYTE), type);
+ AllocateTexture(gfx::Size(width, height), format);
+ if (pixels)
+ SetPixels(0, 0, width, height, pixels);
+ }
+
+ virtual void texSubImage2D(WGC3Denum target,
+ WGC3Dint level,
+ WGC3Dint xoffset,
+ WGC3Dint yoffset,
+ WGC3Dsizei width,
+ WGC3Dsizei height,
+ WGC3Denum format,
+ WGC3Denum type,
+ const void* pixels) OVERRIDE {
+ ASSERT_TRUE(current_texture_);
+ ASSERT_EQ(static_cast<unsigned>(GL_TEXTURE_2D), target);
+ ASSERT_FALSE(level);
+ ASSERT_TRUE(textures_[current_texture_].get());
+ ASSERT_EQ(textures_[current_texture_]->format, format);
+ ASSERT_EQ(static_cast<unsigned>(GL_UNSIGNED_BYTE), type);
+ ASSERT_TRUE(pixels);
+ SetPixels(xoffset, yoffset, width, height, pixels);
+ }
+
+ virtual void texParameteri(WGC3Denum target, WGC3Denum param, WGC3Dint value)
+ OVERRIDE {
+ ASSERT_TRUE(current_texture_);
+ ASSERT_EQ(static_cast<unsigned>(GL_TEXTURE_2D), target);
+ scoped_refptr<Texture> texture = textures_[current_texture_];
+ ASSERT_TRUE(texture.get());
+ if (param != GL_TEXTURE_MIN_FILTER)
+ return;
+ texture->filter = value;
+ }
+
+ virtual void genMailboxCHROMIUM(WGC3Dbyte* mailbox) OVERRIDE {
+ return shared_data_->GenMailbox(mailbox);
+ }
+
+ virtual void produceTextureCHROMIUM(WGC3Denum target,
+ const WGC3Dbyte* mailbox) OVERRIDE {
+ ASSERT_TRUE(current_texture_);
+ ASSERT_EQ(static_cast<unsigned>(GL_TEXTURE_2D), target);
+
+ // Delay moving the texture into the mailbox until the next
+ // InsertSyncPoint, so that it is not visible to other contexts that
+ // haven't waited on that sync point.
+ scoped_ptr<PendingProduceTexture> pending(new PendingProduceTexture);
+ memcpy(pending->mailbox, mailbox, sizeof(pending->mailbox));
+ pending->texture = textures_[current_texture_];
+ pending_produce_textures_.push_back(pending.Pass());
+ }
+
+ virtual void consumeTextureCHROMIUM(WGC3Denum target,
+ const WGC3Dbyte* mailbox) OVERRIDE {
+ ASSERT_TRUE(current_texture_);
+ ASSERT_EQ(static_cast<unsigned>(GL_TEXTURE_2D), target);
+ textures_[current_texture_] = shared_data_->ConsumeTexture(
+ mailbox, last_waited_sync_point_);
+ }
+
+ void GetPixels(gfx::Size size, WGC3Denum format, uint8_t* pixels) {
+ ASSERT_TRUE(current_texture_);
+ scoped_refptr<Texture> texture = textures_[current_texture_];
+ ASSERT_TRUE(texture.get());
+ ASSERT_EQ(texture->size, size);
+ ASSERT_EQ(texture->format, format);
+ memcpy(pixels, texture->data.get(), TextureSize(size, format));
+ }
+
+ WGC3Denum GetTextureFilter() {
+ DCHECK(current_texture_);
+ scoped_refptr<Texture> texture = textures_[current_texture_];
+ DCHECK(texture.get());
+ return texture->filter;
+ }
+
+ int texture_count() { return textures_.size(); }
+
+ protected:
+ ResourceProviderContext(const Attributes& attrs,
+ ContextSharedData* shared_data)
+ : TestWebGraphicsContext3D(attrs),
+ shared_data_(shared_data),
+ current_texture_(0),
+ last_waited_sync_point_(0) {}
+
+ private:
+ void AllocateTexture(gfx::Size size, WGC3Denum format) {
+ ASSERT_TRUE(current_texture_);
+ scoped_refptr<Texture> texture = textures_[current_texture_];
+ ASSERT_TRUE(texture.get());
+ texture->Reallocate(size, format);
+ }
+
+ void SetPixels(int xoffset,
+ int yoffset,
+ int width,
+ int height,
+ const void* pixels) {
+ ASSERT_TRUE(current_texture_);
+ scoped_refptr<Texture> texture = textures_[current_texture_];
+ ASSERT_TRUE(texture.get());
+ ASSERT_TRUE(texture->data.get());
+ ASSERT_TRUE(xoffset >= 0 && xoffset + width <= texture->size.width());
+ ASSERT_TRUE(yoffset >= 0 && yoffset + height <= texture->size.height());
+ ASSERT_TRUE(pixels);
+ size_t in_pitch = TextureSize(gfx::Size(width, 1), texture->format);
+ size_t out_pitch =
+ TextureSize(gfx::Size(texture->size.width(), 1), texture->format);
+ uint8_t* dest = texture->data.get() + yoffset * out_pitch +
+ TextureSize(gfx::Size(xoffset, 1), texture->format);
+ const uint8_t* src = static_cast<const uint8_t*>(pixels);
+ for (int i = 0; i < height; ++i) {
+ memcpy(dest, src, in_pitch);
+ dest += out_pitch;
+ src += in_pitch;
+ }
+ }
+
+ typedef base::hash_map<WebGLId, scoped_refptr<Texture> > TextureMap;
+ struct PendingProduceTexture {
+ WGC3Dbyte mailbox[64];
+ scoped_refptr<Texture> texture;
+ };
+ typedef ScopedPtrDeque<PendingProduceTexture> PendingProduceTextureList;
+ ContextSharedData* shared_data_;
+ WebGLId current_texture_;
+ TextureMap textures_;
+ unsigned last_waited_sync_point_;
+ PendingProduceTextureList pending_produce_textures_;
+};
+
+void GetResourcePixels(ResourceProvider* resource_provider,
+ ResourceProviderContext* context,
+ ResourceProvider::ResourceId id,
+ gfx::Size size,
+ WGC3Denum format,
+ uint8_t* pixels) {
+ switch (resource_provider->default_resource_type()) {
+ case ResourceProvider::GLTexture: {
+ ResourceProvider::ScopedReadLockGL lock_gl(resource_provider, id);
+ ASSERT_NE(0U, lock_gl.texture_id());
+ context->bindTexture(GL_TEXTURE_2D, lock_gl.texture_id());
+ context->GetPixels(size, format, pixels);
+ break;
+ }
+ case ResourceProvider::Bitmap: {
+ ResourceProvider::ScopedReadLockSoftware lock_software(resource_provider,
+ id);
+ memcpy(pixels,
+ lock_software.sk_bitmap()->getPixels(),
+ lock_software.sk_bitmap()->getSize());
+ break;
+ }
+ case ResourceProvider::InvalidType:
+ NOTREACHED();
+ break;
+ }
+}
+
+class ResourceProviderTest
+ : public testing::TestWithParam<ResourceProvider::ResourceType> {
+ public:
+ ResourceProviderTest()
+ : shared_data_(ContextSharedData::Create()) {
+ switch (GetParam()) {
+ case ResourceProvider::GLTexture:
+ output_surface_ =
+ FakeOutputSurface::Create3d(ResourceProviderContext::Create(
+ shared_data_.get()).PassAs<WebKit::WebGraphicsContext3D>());
+ break;
+ case ResourceProvider::Bitmap:
+ output_surface_ = FakeOutputSurface::CreateSoftware(
+ make_scoped_ptr(new SoftwareOutputDevice));
+ break;
+ case ResourceProvider::InvalidType:
+ NOTREACHED();
+ break;
+ }
+ resource_provider_ = ResourceProvider::Create(output_surface_.get(), 0);
+ }
+
+ ResourceProviderContext* context() {
+ return static_cast<ResourceProviderContext*>(output_surface_->context3d());
+ }
+
+ void SetResourceFilter(ResourceProvider* resource_provider,
+ ResourceProvider::ResourceId id,
+ WGC3Denum filter) {
+ ResourceProvider::ScopedSamplerGL sampler(
+ resource_provider, id, GL_TEXTURE_2D, filter);
+ }
+
+ WGC3Denum GetResourceFilter(ResourceProvider* resource_provider,
+ ResourceProvider::ResourceId id) {
+ DCHECK_EQ(GetParam(), ResourceProvider::GLTexture);
+ ResourceProvider::ScopedReadLockGL lock_gl(resource_provider, id);
+ EXPECT_NE(0u, lock_gl.texture_id());
+ ResourceProviderContext* context = static_cast<ResourceProviderContext*>(
+ resource_provider->GraphicsContext3D());
+ context->bindTexture(GL_TEXTURE_2D, lock_gl.texture_id());
+ return context->GetTextureFilter();
+ }
+
+ protected:
+ scoped_ptr<ContextSharedData> shared_data_;
+ scoped_ptr<OutputSurface> output_surface_;
+ scoped_ptr<ResourceProvider> resource_provider_;
+};
+
+void CheckCreateResource(ResourceProvider::ResourceType expected_default_type,
+ ResourceProvider* resource_provider,
+ ResourceProviderContext* context) {
+ DCHECK_EQ(expected_default_type, resource_provider->default_resource_type());
+
+ gfx::Size size(1, 1);
+ WGC3Denum format = GL_RGBA;
+ size_t pixel_size = TextureSize(size, format);
+ ASSERT_EQ(4U, pixel_size);
+
+ ResourceProvider::ResourceId id = resource_provider->CreateResource(
+ size, format, ResourceProvider::TextureUsageAny);
+ EXPECT_EQ(1, static_cast<int>(resource_provider->num_resources()));
+ if (expected_default_type == ResourceProvider::GLTexture)
+ EXPECT_EQ(0, context->texture_count());
+
+ uint8_t data[4] = { 1, 2, 3, 4 };
+ gfx::Rect rect(size);
+ resource_provider->SetPixels(id, data, rect, rect, gfx::Vector2d());
+ if (expected_default_type == ResourceProvider::GLTexture)
+ EXPECT_EQ(1, context->texture_count());
+
+ uint8_t result[4] = { 0 };
+ GetResourcePixels(resource_provider, context, id, size, format, result);
+ EXPECT_EQ(0, memcmp(data, result, pixel_size));
+
+ resource_provider->DeleteResource(id);
+ EXPECT_EQ(0, static_cast<int>(resource_provider->num_resources()));
+ if (expected_default_type == ResourceProvider::GLTexture)
+ EXPECT_EQ(0, context->texture_count());
+}
+
+TEST_P(ResourceProviderTest, Basic) {
+ CheckCreateResource(GetParam(), resource_provider_.get(), context());
+}
+
+TEST_P(ResourceProviderTest, Upload) {
+ gfx::Size size(2, 2);
+ WGC3Denum format = GL_RGBA;
+ size_t pixel_size = TextureSize(size, format);
+ ASSERT_EQ(16U, pixel_size);
+
+ ResourceProvider::ResourceId id = resource_provider_->CreateResource(
+ size, format, ResourceProvider::TextureUsageAny);
+
+ uint8_t image[16] = { 0 };
+ gfx::Rect image_rect(size);
+ resource_provider_->SetPixels(
+ id, image, image_rect, image_rect, gfx::Vector2d());
+
+ for (uint8_t i = 0; i < pixel_size; ++i)
+ image[i] = i;
+
+ uint8_t result[16] = { 0 };
+ {
+ gfx::Rect source_rect(0, 0, 1, 1);
+ gfx::Vector2d dest_offset(0, 0);
+ resource_provider_->SetPixels(
+ id, image, image_rect, source_rect, dest_offset);
+
+ uint8_t expected[16] = { 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+ GetResourcePixels(
+ resource_provider_.get(), context(), id, size, format, result);
+ EXPECT_EQ(0, memcmp(expected, result, pixel_size));
+ }
+ {
+ gfx::Rect source_rect(0, 0, 1, 1);
+ gfx::Vector2d dest_offset(1, 1);
+ resource_provider_->SetPixels(
+ id, image, image_rect, source_rect, dest_offset);
+
+ uint8_t expected[16] = { 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3 };
+ GetResourcePixels(
+ resource_provider_.get(), context(), id, size, format, result);
+ EXPECT_EQ(0, memcmp(expected, result, pixel_size));
+ }
+ {
+ gfx::Rect source_rect(1, 0, 1, 1);
+ gfx::Vector2d dest_offset(0, 1);
+ resource_provider_->SetPixels(
+ id, image, image_rect, source_rect, dest_offset);
+
+ uint8_t expected[16] = { 0, 1, 2, 3, 0, 0, 0, 0, 4, 5, 6, 7, 0, 1, 2, 3 };
+ GetResourcePixels(
+ resource_provider_.get(), context(), id, size, format, result);
+ EXPECT_EQ(0, memcmp(expected, result, pixel_size));
+ }
+ {
+ gfx::Rect offset_image_rect(gfx::Point(100, 100), size);
+ gfx::Rect source_rect(100, 100, 1, 1);
+ gfx::Vector2d dest_offset(1, 0);
+ resource_provider_->SetPixels(
+ id, image, offset_image_rect, source_rect, dest_offset);
+
+ uint8_t expected[16] = { 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3 };
+ GetResourcePixels(
+ resource_provider_.get(), context(), id, size, format, result);
+ EXPECT_EQ(0, memcmp(expected, result, pixel_size));
+ }
+
+ resource_provider_->DeleteResource(id);
+}
+
+TEST_P(ResourceProviderTest, TransferResources) {
+ // Resource transfer is only supported with GL textures for now.
+ if (GetParam() != ResourceProvider::GLTexture)
+ return;
+
+ scoped_ptr<OutputSurface> child_output_surface(FakeOutputSurface::Create3d(
+ ResourceProviderContext::Create(shared_data_.get())
+ .PassAs<WebKit::WebGraphicsContext3D>()));
+ scoped_ptr<ResourceProvider> child_resource_provider(
+ ResourceProvider::Create(child_output_surface.get(), 0));
+
+ gfx::Size size(1, 1);
+ WGC3Denum format = GL_RGBA;
+ size_t pixel_size = TextureSize(size, format);
+ ASSERT_EQ(4U, pixel_size);
+
+ ResourceProvider::ResourceId id1 = child_resource_provider->CreateResource(
+ size, format, ResourceProvider::TextureUsageAny);
+ uint8_t data1[4] = { 1, 2, 3, 4 };
+ gfx::Rect rect(size);
+ child_resource_provider->SetPixels(id1, data1, rect, rect, gfx::Vector2d());
+
+ ResourceProvider::ResourceId id2 = child_resource_provider->CreateResource(
+ size, format, ResourceProvider::TextureUsageAny);
+ uint8_t data2[4] = { 5, 5, 5, 5 };
+ child_resource_provider->SetPixels(id2, data2, rect, rect, gfx::Vector2d());
+
+ int child_id = resource_provider_->CreateChild();
+ {
+ // Transfer some resources to the parent.
+ ResourceProvider::ResourceIdArray resource_ids_to_transfer;
+ resource_ids_to_transfer.push_back(id1);
+ resource_ids_to_transfer.push_back(id2);
+ TransferableResourceArray list;
+ child_resource_provider->PrepareSendToParent(resource_ids_to_transfer,
+ &list);
+ ASSERT_EQ(2u, list.size());
+ EXPECT_NE(0u, list[0].sync_point);
+ EXPECT_NE(0u, list[1].sync_point);
+ EXPECT_TRUE(child_resource_provider->InUseByConsumer(id1));
+ EXPECT_TRUE(child_resource_provider->InUseByConsumer(id2));
+ resource_provider_->ReceiveFromChild(child_id, list);
+ }
+
+ EXPECT_EQ(2u, resource_provider_->num_resources());
+ ResourceProvider::ResourceIdMap resource_map =
+ resource_provider_->GetChildToParentMap(child_id);
+ ResourceProvider::ResourceId mapped_id1 = resource_map[id1];
+ ResourceProvider::ResourceId mapped_id2 = resource_map[id2];
+ EXPECT_NE(0u, mapped_id1);
+ EXPECT_NE(0u, mapped_id2);
+ EXPECT_FALSE(resource_provider_->InUseByConsumer(id1));
+ EXPECT_FALSE(resource_provider_->InUseByConsumer(id2));
+
+ uint8_t result[4] = { 0 };
+ GetResourcePixels(
+ resource_provider_.get(), context(), mapped_id1, size, format, result);
+ EXPECT_EQ(0, memcmp(data1, result, pixel_size));
+
+ GetResourcePixels(
+ resource_provider_.get(), context(), mapped_id2, size, format, result);
+ EXPECT_EQ(0, memcmp(data2, result, pixel_size));
+ {
+ // Check that transfering again the same resource from the child to the
+ // parent is a noop.
+ ResourceProvider::ResourceIdArray resource_ids_to_transfer;
+ resource_ids_to_transfer.push_back(id1);
+ TransferableResourceArray list;
+ child_resource_provider->PrepareSendToParent(resource_ids_to_transfer,
+ &list);
+ EXPECT_EQ(0u, list.size());
+ }
+ {
+ // Transfer resources back from the parent to the child.
+ ResourceProvider::ResourceIdArray resource_ids_to_transfer;
+ resource_ids_to_transfer.push_back(mapped_id1);
+ resource_ids_to_transfer.push_back(mapped_id2);
+ TransferableResourceArray list;
+ resource_provider_->PrepareSendToChild(
+ child_id, resource_ids_to_transfer, &list);
+ ASSERT_EQ(2u, list.size());
+ EXPECT_NE(0u, list[0].sync_point);
+ EXPECT_NE(0u, list[1].sync_point);
+ child_resource_provider->ReceiveFromParent(list);
+ }
+ EXPECT_FALSE(child_resource_provider->InUseByConsumer(id1));
+ EXPECT_FALSE(child_resource_provider->InUseByConsumer(id2));
+
+ ResourceProviderContext* child_context =
+ static_cast<ResourceProviderContext*>(child_output_surface->context3d());
+ {
+ ResourceProvider::ScopedReadLockGL lock(child_resource_provider.get(), id1);
+ ASSERT_NE(0U, lock.texture_id());
+ child_context->bindTexture(GL_TEXTURE_2D, lock.texture_id());
+ child_context->GetPixels(size, format, result);
+ EXPECT_EQ(0, memcmp(data1, result, pixel_size));
+ }
+ {
+ ResourceProvider::ScopedReadLockGL lock(child_resource_provider.get(), id2);
+ ASSERT_NE(0U, lock.texture_id());
+ child_context->bindTexture(GL_TEXTURE_2D, lock.texture_id());
+ child_context->GetPixels(size, format, result);
+ EXPECT_EQ(0, memcmp(data2, result, pixel_size));
+ }
+ {
+ // Transfer resources to the parent again.
+ ResourceProvider::ResourceIdArray resource_ids_to_transfer;
+ resource_ids_to_transfer.push_back(id1);
+ resource_ids_to_transfer.push_back(id2);
+ TransferableResourceArray list;
+ child_resource_provider->PrepareSendToParent(resource_ids_to_transfer,
+ &list);
+ ASSERT_EQ(2u, list.size());
+ EXPECT_NE(0u, list[0].sync_point);
+ EXPECT_NE(0u, list[1].sync_point);
+ EXPECT_TRUE(child_resource_provider->InUseByConsumer(id1));
+ EXPECT_TRUE(child_resource_provider->InUseByConsumer(id2));
+ resource_provider_->ReceiveFromChild(child_id, list);
+ }
+
+ EXPECT_EQ(2u, resource_provider_->num_resources());
+ resource_provider_->DestroyChild(child_id);
+ EXPECT_EQ(0u, resource_provider_->num_resources());
+}
+
+TEST_P(ResourceProviderTest, DeleteTransferredResources) {
+ // Resource transfer is only supported with GL textures for now.
+ if (GetParam() != ResourceProvider::GLTexture)
+ return;
+
+ scoped_ptr<OutputSurface> child_output_surface(FakeOutputSurface::Create3d(
+ ResourceProviderContext::Create(shared_data_.get())
+ .PassAs<WebKit::WebGraphicsContext3D>()));
+ scoped_ptr<ResourceProvider> child_resource_provider(
+ ResourceProvider::Create(child_output_surface.get(), 0));
+
+ gfx::Size size(1, 1);
+ WGC3Denum format = GL_RGBA;
+ size_t pixel_size = TextureSize(size, format);
+ ASSERT_EQ(4U, pixel_size);
+
+ ResourceProvider::ResourceId id = child_resource_provider->CreateResource(
+ size, format, ResourceProvider::TextureUsageAny);
+ uint8_t data[4] = { 1, 2, 3, 4 };
+ gfx::Rect rect(size);
+ child_resource_provider->SetPixels(id, data, rect, rect, gfx::Vector2d());
+
+ int child_id = resource_provider_->CreateChild();
+ {
+ // Transfer some resource to the parent.
+ ResourceProvider::ResourceIdArray resource_ids_to_transfer;
+ resource_ids_to_transfer.push_back(id);
+ TransferableResourceArray list;
+ child_resource_provider->PrepareSendToParent(resource_ids_to_transfer,
+ &list);
+ ASSERT_EQ(1u, list.size());
+ EXPECT_NE(0u, list[0].sync_point);
+ EXPECT_TRUE(child_resource_provider->InUseByConsumer(id));
+ resource_provider_->ReceiveFromChild(child_id, list);
+ }
+
+ // Delete textures in the child, while they are transfered.
+ child_resource_provider->DeleteResource(id);
+ EXPECT_EQ(1u, child_resource_provider->num_resources());
+ {
+ // Transfer resources back from the parent to the child.
+ ResourceProvider::ResourceIdMap resource_map =
+ resource_provider_->GetChildToParentMap(child_id);
+ ResourceProvider::ResourceId mapped_id = resource_map[id];
+ EXPECT_NE(0u, mapped_id);
+ ResourceProvider::ResourceIdArray resource_ids_to_transfer;
+ resource_ids_to_transfer.push_back(mapped_id);
+ TransferableResourceArray list;
+ resource_provider_->PrepareSendToChild(
+ child_id, resource_ids_to_transfer, &list);
+ ASSERT_EQ(1u, list.size());
+ EXPECT_NE(0u, list[0].sync_point);
+ child_resource_provider->ReceiveFromParent(list);
+ }
+ EXPECT_EQ(0u, child_resource_provider->num_resources());
+}
+
+TEST_P(ResourceProviderTest, TextureFilters) {
+ // Resource transfer is only supported with GL textures for now.
+ if (GetParam() != ResourceProvider::GLTexture)
+ return;
+
+ scoped_ptr<OutputSurface> child_output_surface(FakeOutputSurface::Create3d(
+ ResourceProviderContext::Create(shared_data_.get())
+ .PassAs<WebKit::WebGraphicsContext3D>()));
+ scoped_ptr<ResourceProvider> child_resource_provider(
+ ResourceProvider::Create(child_output_surface.get(), 0));
+
+ gfx::Size size(1, 1);
+ WGC3Denum format = GL_RGBA;
+ size_t pixel_size = TextureSize(size, format);
+ ASSERT_EQ(4U, pixel_size);
+
+ ResourceProvider::ResourceId id = child_resource_provider->CreateResource(
+ size, format, ResourceProvider::TextureUsageAny);
+ uint8_t data[4] = { 1, 2, 3, 4 };
+ gfx::Rect rect(size);
+ child_resource_provider->SetPixels(id, data, rect, rect, gfx::Vector2d());
+ EXPECT_EQ(static_cast<unsigned>(GL_LINEAR),
+ GetResourceFilter(child_resource_provider.get(), id));
+ SetResourceFilter(child_resource_provider.get(), id, GL_NEAREST);
+ EXPECT_EQ(static_cast<unsigned>(GL_NEAREST),
+ GetResourceFilter(child_resource_provider.get(), id));
+
+ int child_id = resource_provider_->CreateChild();
+ {
+ // Transfer some resource to the parent.
+ ResourceProvider::ResourceIdArray resource_ids_to_transfer;
+ resource_ids_to_transfer.push_back(id);
+ TransferableResourceArray list;
+ child_resource_provider->PrepareSendToParent(resource_ids_to_transfer,
+ &list);
+ ASSERT_EQ(1u, list.size());
+ EXPECT_EQ(static_cast<unsigned>(GL_NEAREST), list[0].filter);
+ resource_provider_->ReceiveFromChild(child_id, list);
+ }
+ ResourceProvider::ResourceIdMap resource_map =
+ resource_provider_->GetChildToParentMap(child_id);
+ ResourceProvider::ResourceId mapped_id = resource_map[id];
+ EXPECT_NE(0u, mapped_id);
+ EXPECT_EQ(static_cast<unsigned>(GL_NEAREST),
+ GetResourceFilter(resource_provider_.get(), mapped_id));
+ SetResourceFilter(resource_provider_.get(), mapped_id, GL_LINEAR);
+ EXPECT_EQ(static_cast<unsigned>(GL_LINEAR),
+ GetResourceFilter(resource_provider_.get(), mapped_id));
+ {
+ // Transfer resources back from the parent to the child.
+ ResourceProvider::ResourceIdArray resource_ids_to_transfer;
+ resource_ids_to_transfer.push_back(mapped_id);
+ TransferableResourceArray list;
+ resource_provider_->PrepareSendToChild(
+ child_id, resource_ids_to_transfer, &list);
+ ASSERT_EQ(1u, list.size());
+ EXPECT_EQ(static_cast<unsigned>(GL_LINEAR), list[0].filter);
+ child_resource_provider->ReceiveFromParent(list);
+ }
+ EXPECT_EQ(static_cast<unsigned>(GL_LINEAR),
+ GetResourceFilter(child_resource_provider.get(), id));
+ SetResourceFilter(child_resource_provider.get(), id, GL_NEAREST);
+ EXPECT_EQ(static_cast<unsigned>(GL_NEAREST),
+ GetResourceFilter(child_resource_provider.get(), id));
+}
+
+void ReleaseTextureMailbox(unsigned* release_sync_point,
+ bool* release_lost_resource,
+ unsigned sync_point,
+ bool lost_resource) {
+ *release_sync_point = sync_point;
+ *release_lost_resource = lost_resource;
+}
+
+TEST_P(ResourceProviderTest, TransferMailboxResources) {
+ // Resource transfer is only supported with GL textures for now.
+ if (GetParam() != ResourceProvider::GLTexture)
+ return;
+ unsigned texture = context()->createTexture();
+ context()->bindTexture(GL_TEXTURE_2D, texture);
+ uint8_t data[4] = { 1, 2, 3, 4 };
+ context()->texImage2D(
+ GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &data);
+ gpu::Mailbox mailbox;
+ context()->genMailboxCHROMIUM(mailbox.name);
+ context()->produceTextureCHROMIUM(GL_TEXTURE_2D, mailbox.name);
+ unsigned sync_point = context()->insertSyncPoint();
+
+ // All the logic below assumes that the sync points are all positive.
+ EXPECT_LT(0u, sync_point);
+
+ unsigned release_sync_point = 0;
+ bool lost_resource = false;
+ TextureMailbox::ReleaseCallback callback =
+ base::Bind(ReleaseTextureMailbox, &release_sync_point, &lost_resource);
+ ResourceProvider::ResourceId resource =
+ resource_provider_->CreateResourceFromTextureMailbox(
+ TextureMailbox(mailbox, callback, sync_point));
+ EXPECT_EQ(1, context()->texture_count());
+ EXPECT_EQ(0u, release_sync_point);
+ {
+ // Transfer the resource, expect the sync points to be consistent.
+ ResourceProvider::ResourceIdArray resource_ids_to_transfer;
+ resource_ids_to_transfer.push_back(resource);
+ TransferableResourceArray list;
+ resource_provider_->PrepareSendToParent(resource_ids_to_transfer, &list);
+ ASSERT_EQ(1u, list.size());
+ EXPECT_LE(sync_point, list[0].sync_point);
+ EXPECT_EQ(0,
+ memcmp(mailbox.name, list[0].mailbox.name, sizeof(mailbox.name)));
+ EXPECT_EQ(0u, release_sync_point);
+
+ context()->waitSyncPoint(list[0].sync_point);
+ unsigned other_texture = context()->createTexture();
+ context()->bindTexture(GL_TEXTURE_2D, other_texture);
+ context()->consumeTextureCHROMIUM(GL_TEXTURE_2D, mailbox.name);
+ uint8_t test_data[4] = { 0 };
+ context()->GetPixels(gfx::Size(1, 1), GL_RGBA, test_data);
+ EXPECT_EQ(0, memcmp(data, test_data, sizeof(data)));
+ context()->produceTextureCHROMIUM(GL_TEXTURE_2D, mailbox.name);
+ context()->deleteTexture(other_texture);
+ list[0].sync_point = context()->insertSyncPoint();
+ EXPECT_LT(0u, list[0].sync_point);
+
+ // Receive the resource, then delete it, expect the sync points to be
+ // consistent.
+ resource_provider_->ReceiveFromParent(list);
+ EXPECT_EQ(1, context()->texture_count());
+ EXPECT_EQ(0u, release_sync_point);
+
+ resource_provider_->DeleteResource(resource);
+ EXPECT_LE(list[0].sync_point, release_sync_point);
+ EXPECT_FALSE(lost_resource);
+ }
+
+ // We're going to do the same thing as above, but testing the case where we
+ // delete the resource before we receive it back.
+ sync_point = release_sync_point;
+ EXPECT_LT(0u, sync_point);
+ release_sync_point = 0;
+ resource = resource_provider_->CreateResourceFromTextureMailbox(
+ TextureMailbox(mailbox, callback, sync_point));
+ EXPECT_EQ(1, context()->texture_count());
+ EXPECT_EQ(0u, release_sync_point);
+ {
+ // Transfer the resource, expect the sync points to be consistent.
+ ResourceProvider::ResourceIdArray resource_ids_to_transfer;
+ resource_ids_to_transfer.push_back(resource);
+ TransferableResourceArray list;
+ resource_provider_->PrepareSendToParent(resource_ids_to_transfer, &list);
+ ASSERT_EQ(1u, list.size());
+ EXPECT_LE(sync_point, list[0].sync_point);
+ EXPECT_EQ(0,
+ memcmp(mailbox.name, list[0].mailbox.name, sizeof(mailbox.name)));
+ EXPECT_EQ(0u, release_sync_point);
+
+ context()->waitSyncPoint(list[0].sync_point);
+ unsigned other_texture = context()->createTexture();
+ context()->bindTexture(GL_TEXTURE_2D, other_texture);
+ context()->consumeTextureCHROMIUM(GL_TEXTURE_2D, mailbox.name);
+ uint8_t test_data[4] = { 0 };
+ context()->GetPixels(gfx::Size(1, 1), GL_RGBA, test_data);
+ EXPECT_EQ(0, memcmp(data, test_data, sizeof(data)));
+ context()->produceTextureCHROMIUM(GL_TEXTURE_2D, mailbox.name);
+ context()->deleteTexture(other_texture);
+ list[0].sync_point = context()->insertSyncPoint();
+ EXPECT_LT(0u, list[0].sync_point);
+
+ // Delete the resource, which shouldn't do anything.
+ resource_provider_->DeleteResource(resource);
+ EXPECT_EQ(1, context()->texture_count());
+ EXPECT_EQ(0u, release_sync_point);
+
+ // Then receive the resource which should release the mailbox, expect the
+ // sync points to be consistent.
+ resource_provider_->ReceiveFromParent(list);
+ EXPECT_LE(list[0].sync_point, release_sync_point);
+ EXPECT_FALSE(lost_resource);
+ }
+
+ context()->waitSyncPoint(release_sync_point);
+ context()->bindTexture(GL_TEXTURE_2D, texture);
+ context()->consumeTextureCHROMIUM(GL_TEXTURE_2D, mailbox.name);
+ context()->deleteTexture(texture);
+}
+
+TEST_P(ResourceProviderTest, Shutdown) {
+ // TextureMailbox callbacks only exist for GL textures for now.
+ if (GetParam() != ResourceProvider::GLTexture)
+ return;
+ unsigned texture = context()->createTexture();
+ context()->bindTexture(GL_TEXTURE_2D, texture);
+ gpu::Mailbox mailbox;
+ context()->genMailboxCHROMIUM(mailbox.name);
+ context()->produceTextureCHROMIUM(GL_TEXTURE_2D, mailbox.name);
+ unsigned sync_point = context()->insertSyncPoint();
+
+ EXPECT_LT(0u, sync_point);
+
+ unsigned release_sync_point = 0;
+ bool lost_resource = false;
+ TextureMailbox::ReleaseCallback callback =
+ base::Bind(ReleaseTextureMailbox, &release_sync_point, &lost_resource);
+ resource_provider_->CreateResourceFromTextureMailbox(
+ TextureMailbox(mailbox, callback, sync_point));
+
+ EXPECT_EQ(0u, release_sync_point);
+ EXPECT_FALSE(lost_resource);
+
+ resource_provider_.reset();
+
+ EXPECT_LE(sync_point, release_sync_point);
+ EXPECT_FALSE(lost_resource);
+}
+
+static scoped_ptr<base::SharedMemory> CreateAndFillSharedMemory(
+ gfx::Size size, uint32_t value) {
+ scoped_ptr<base::SharedMemory> shared_memory(new base::SharedMemory);
+ CHECK(shared_memory->CreateAndMapAnonymous(4 * size.GetArea()));
+ uint32_t* pixels = reinterpret_cast<uint32_t*>(shared_memory->memory());
+ CHECK(pixels);
+ std::fill_n(pixels, size.GetArea(), value);
+ return shared_memory.Pass();
+}
+
+static void ReleaseSharedMemoryCallback(
+ bool* release_called,
+ unsigned sync_point, bool lost_resource) {
+ *release_called = true;
+}
+
+TEST_P(ResourceProviderTest, ShutdownSharedMemory) {
+ if (GetParam() != ResourceProvider::Bitmap)
+ return;
+
+ gfx::Size size(64, 64);
+ scoped_ptr<base::SharedMemory> shared_memory(
+ CreateAndFillSharedMemory(size, 0));
+
+ bool release_called = false;
+ TextureMailbox::ReleaseCallback callback =
+ base::Bind(ReleaseSharedMemoryCallback, &release_called);
+ resource_provider_->CreateResourceFromTextureMailbox(
+ TextureMailbox(shared_memory.get(), size, callback));
+
+ resource_provider_.reset();
+
+ EXPECT_TRUE(release_called);
+}
+
+TEST_P(ResourceProviderTest, ShutdownWithExportedResource) {
+ // TextureMailbox callbacks only exist for GL textures for now.
+ if (GetParam() != ResourceProvider::GLTexture)
+ return;
+ unsigned texture = context()->createTexture();
+ context()->bindTexture(GL_TEXTURE_2D, texture);
+ gpu::Mailbox mailbox;
+ context()->genMailboxCHROMIUM(mailbox.name);
+ context()->produceTextureCHROMIUM(GL_TEXTURE_2D, mailbox.name);
+ unsigned sync_point = context()->insertSyncPoint();
+
+ EXPECT_LT(0u, sync_point);
+
+ unsigned release_sync_point = 0;
+ bool lost_resource = false;
+ TextureMailbox::ReleaseCallback callback =
+ base::Bind(ReleaseTextureMailbox, &release_sync_point, &lost_resource);
+ ResourceProvider::ResourceId resource =
+ resource_provider_->CreateResourceFromTextureMailbox(
+ TextureMailbox(mailbox, callback, sync_point));
+
+ // Transfer the resource, so we can't release it properly on shutdown.
+ ResourceProvider::ResourceIdArray resource_ids_to_transfer;
+ resource_ids_to_transfer.push_back(resource);
+ TransferableResourceArray list;
+ resource_provider_->PrepareSendToParent(resource_ids_to_transfer, &list);
+
+ EXPECT_EQ(0u, release_sync_point);
+ EXPECT_FALSE(lost_resource);
+
+ resource_provider_.reset();
+
+ // Since the resource is in the parent, the child considers it lost.
+ EXPECT_EQ(0u, release_sync_point);
+ EXPECT_TRUE(lost_resource);
+}
+
+TEST_P(ResourceProviderTest, LostContext) {
+ // TextureMailbox callbacks only exist for GL textures for now.
+ if (GetParam() != ResourceProvider::GLTexture)
+ return;
+ unsigned texture = context()->createTexture();
+ context()->bindTexture(GL_TEXTURE_2D, texture);
+ gpu::Mailbox mailbox;
+ context()->genMailboxCHROMIUM(mailbox.name);
+ context()->produceTextureCHROMIUM(GL_TEXTURE_2D, mailbox.name);
+ unsigned sync_point = context()->insertSyncPoint();
+
+ EXPECT_LT(0u, sync_point);
+
+ unsigned release_sync_point = 0;
+ bool lost_resource = false;
+ TextureMailbox::ReleaseCallback callback =
+ base::Bind(ReleaseTextureMailbox, &release_sync_point, &lost_resource);
+ resource_provider_->CreateResourceFromTextureMailbox(
+ TextureMailbox(mailbox, callback, sync_point));
+
+ EXPECT_EQ(0u, release_sync_point);
+ EXPECT_FALSE(lost_resource);
+
+ resource_provider_->DidLoseOutputSurface();
+ resource_provider_.reset();
+
+ EXPECT_LE(sync_point, release_sync_point);
+ EXPECT_TRUE(lost_resource);
+}
+
+class TextureStateTrackingContext : public TestWebGraphicsContext3D {
+ public:
+ MOCK_METHOD2(bindTexture, void(WGC3Denum target, WebGLId texture));
+ MOCK_METHOD3(texParameteri,
+ void(WGC3Denum target, WGC3Denum pname, WGC3Dint param));
+ MOCK_METHOD1(waitSyncPoint, void(unsigned sync_point));
+ MOCK_METHOD0(insertSyncPoint, unsigned(void));
+ MOCK_METHOD2(produceTextureCHROMIUM, void(WGC3Denum target,
+ const WGC3Dbyte* mailbox));
+ MOCK_METHOD2(consumeTextureCHROMIUM, void(WGC3Denum target,
+ const WGC3Dbyte* mailbox));
+
+ // Force all textures to be "1" so we can test for them.
+ virtual WebKit::WebGLId NextTextureId() OVERRIDE { return 1; }
+};
+
+TEST_P(ResourceProviderTest, ScopedSampler) {
+ // Sampling is only supported for GL textures.
+ if (GetParam() != ResourceProvider::GLTexture)
+ return;
+
+ scoped_ptr<OutputSurface> output_surface(
+ FakeOutputSurface::Create3d(scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new TextureStateTrackingContext)));
+ TextureStateTrackingContext* context =
+ static_cast<TextureStateTrackingContext*>(output_surface->context3d());
+ scoped_ptr<ResourceProvider> resource_provider(
+ ResourceProvider::Create(output_surface.get(), 0));
+
+ gfx::Size size(1, 1);
+ WGC3Denum format = GL_RGBA;
+ int texture_id = 1;
+
+ // Check that the texture gets created with the right sampler settings.
+ EXPECT_CALL(*context, bindTexture(GL_TEXTURE_2D, texture_id))
+ .Times(2); // Once to create and once to allocate.
+ EXPECT_CALL(*context,
+ texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
+ EXPECT_CALL(*context,
+ texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
+ EXPECT_CALL(
+ *context,
+ texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
+ EXPECT_CALL(
+ *context,
+ texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
+ EXPECT_CALL(*context,
+ texParameteri(GL_TEXTURE_2D,
+ GL_TEXTURE_POOL_CHROMIUM,
+ GL_TEXTURE_POOL_UNMANAGED_CHROMIUM));
+ ResourceProvider::ResourceId id = resource_provider->CreateResource(
+ size, format, ResourceProvider::TextureUsageAny);
+ resource_provider->AllocateForTesting(id);
+
+ // Creating a sampler with the default filter should not change any texture
+ // parameters.
+ {
+ EXPECT_CALL(*context, bindTexture(GL_TEXTURE_2D, texture_id));
+ ResourceProvider::ScopedSamplerGL sampler(
+ resource_provider.get(), id, GL_TEXTURE_2D, GL_LINEAR);
+ }
+
+ // Using a different filter should be reflected in the texture parameters.
+ {
+ EXPECT_CALL(*context, bindTexture(GL_TEXTURE_2D, texture_id));
+ EXPECT_CALL(
+ *context,
+ texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
+ EXPECT_CALL(
+ *context,
+ texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
+ ResourceProvider::ScopedSamplerGL sampler(
+ resource_provider.get(), id, GL_TEXTURE_2D, GL_NEAREST);
+ }
+
+ // Test resetting to the default filter.
+ {
+ EXPECT_CALL(*context, bindTexture(GL_TEXTURE_2D, texture_id));
+ EXPECT_CALL(*context,
+ texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
+ EXPECT_CALL(*context,
+ texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
+ ResourceProvider::ScopedSamplerGL sampler(
+ resource_provider.get(), id, GL_TEXTURE_2D, GL_LINEAR);
+ }
+
+ Mock::VerifyAndClearExpectations(context);
+}
+
+TEST_P(ResourceProviderTest, ManagedResource) {
+ // Sampling is only supported for GL textures.
+ if (GetParam() != ResourceProvider::GLTexture)
+ return;
+
+ scoped_ptr<OutputSurface> output_surface(
+ FakeOutputSurface::Create3d(scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new TextureStateTrackingContext)));
+ TextureStateTrackingContext* context =
+ static_cast<TextureStateTrackingContext*>(output_surface->context3d());
+ scoped_ptr<ResourceProvider> resource_provider(
+ ResourceProvider::Create(output_surface.get(), 0));
+
+ gfx::Size size(1, 1);
+ WGC3Denum format = GL_RGBA;
+ int texture_id = 1;
+
+ // Check that the texture gets created with the right sampler settings.
+ ResourceProvider::ResourceId id = resource_provider->CreateManagedResource(
+ size, format, ResourceProvider::TextureUsageAny);
+ EXPECT_CALL(*context, bindTexture(GL_TEXTURE_2D, texture_id));
+ EXPECT_CALL(*context,
+ texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
+ EXPECT_CALL(*context,
+ texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
+ EXPECT_CALL(
+ *context,
+ texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
+ EXPECT_CALL(
+ *context,
+ texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
+ EXPECT_CALL(*context,
+ texParameteri(GL_TEXTURE_2D,
+ GL_TEXTURE_POOL_CHROMIUM,
+ GL_TEXTURE_POOL_MANAGED_CHROMIUM));
+ resource_provider->CreateForTesting(id);
+ EXPECT_NE(0u, id);
+
+ Mock::VerifyAndClearExpectations(context);
+}
+
+static void EmptyReleaseCallback(unsigned sync_point, bool lost_resource) {}
+
+TEST_P(ResourceProviderTest, TextureMailbox_SharedMemory) {
+ if (GetParam() != ResourceProvider::Bitmap)
+ return;
+
+ gfx::Size size(64, 64);
+ const uint32_t kBadBeef = 0xbadbeef;
+ scoped_ptr<base::SharedMemory> shared_memory(
+ CreateAndFillSharedMemory(size, kBadBeef));
+
+ scoped_ptr<OutputSurface> output_surface(
+ FakeOutputSurface::CreateSoftware(make_scoped_ptr(
+ new SoftwareOutputDevice)));
+ scoped_ptr<ResourceProvider> resource_provider(
+ ResourceProvider::Create(output_surface.get(), 0));
+
+ TextureMailbox::ReleaseCallback callback = base::Bind(&EmptyReleaseCallback);
+ TextureMailbox mailbox(shared_memory.get(), size, callback);
+
+ ResourceProvider::ResourceId id =
+ resource_provider->CreateResourceFromTextureMailbox(mailbox);
+ EXPECT_NE(0u, id);
+
+ {
+ ResourceProvider::ScopedReadLockSoftware lock(resource_provider.get(), id);
+ const SkBitmap* sk_bitmap = lock.sk_bitmap();
+ EXPECT_EQ(sk_bitmap->width(), size.width());
+ EXPECT_EQ(sk_bitmap->height(), size.height());
+ EXPECT_EQ(*sk_bitmap->getAddr32(16, 16), kBadBeef);
+ }
+}
+
+TEST_P(ResourceProviderTest, TextureMailbox_GLTexture2D) {
+ // Mailboxing is only supported for GL textures.
+ if (GetParam() != ResourceProvider::GLTexture)
+ return;
+
+ scoped_ptr<OutputSurface> output_surface(
+ FakeOutputSurface::Create3d(scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new TextureStateTrackingContext)));
+ TextureStateTrackingContext* context =
+ static_cast<TextureStateTrackingContext*>(output_surface->context3d());
+ scoped_ptr<ResourceProvider> resource_provider(
+ ResourceProvider::Create(output_surface.get(), 0));
+
+ unsigned texture_id = 1;
+ unsigned sync_point = 30;
+ unsigned target = GL_TEXTURE_2D;
+
+ EXPECT_CALL(*context, bindTexture(_, _)).Times(0);
+ EXPECT_CALL(*context, waitSyncPoint(_)).Times(0);
+ EXPECT_CALL(*context, insertSyncPoint()).Times(0);
+ EXPECT_CALL(*context, produceTextureCHROMIUM(_, _)).Times(0);
+ EXPECT_CALL(*context, consumeTextureCHROMIUM(_, _)).Times(0);
+
+ gpu::Mailbox gpu_mailbox;
+ memcpy(gpu_mailbox.name, "Hello world", strlen("Hello world") + 1);
+ TextureMailbox::ReleaseCallback callback = base::Bind(&EmptyReleaseCallback);
+
+ TextureMailbox mailbox(gpu_mailbox,
+ callback,
+ sync_point);
+
+ ResourceProvider::ResourceId id =
+ resource_provider->CreateResourceFromTextureMailbox(mailbox);
+ EXPECT_NE(0u, id);
+
+ Mock::VerifyAndClearExpectations(context);
+
+ {
+ // Using the texture does a consume of the mailbox.
+ EXPECT_CALL(*context, bindTexture(target, texture_id));
+ EXPECT_CALL(*context, waitSyncPoint(sync_point));
+ EXPECT_CALL(*context, consumeTextureCHROMIUM(target, _));
+
+ EXPECT_CALL(*context, insertSyncPoint()).Times(0);
+ EXPECT_CALL(*context, produceTextureCHROMIUM(_, _)).Times(0);
+
+ ResourceProvider::ScopedReadLockGL lock(resource_provider.get(), id);
+ Mock::VerifyAndClearExpectations(context);
+
+ // When done with it, a sync point should be inserted, but no produce is
+ // necessary.
+ EXPECT_CALL(*context, bindTexture(_, _)).Times(0);
+ EXPECT_CALL(*context, insertSyncPoint());
+ EXPECT_CALL(*context, produceTextureCHROMIUM(_, _)).Times(0);
+
+ EXPECT_CALL(*context, waitSyncPoint(_)).Times(0);
+ EXPECT_CALL(*context, consumeTextureCHROMIUM(_, _)).Times(0);
+ }
+}
+
+TEST_P(ResourceProviderTest, TextureMailbox_GLTextureExternalOES) {
+ // Mailboxing is only supported for GL textures.
+ if (GetParam() != ResourceProvider::GLTexture)
+ return;
+
+ scoped_ptr<OutputSurface> output_surface(
+ FakeOutputSurface::Create3d(scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new TextureStateTrackingContext)));
+ TextureStateTrackingContext* context =
+ static_cast<TextureStateTrackingContext*>(output_surface->context3d());
+ scoped_ptr<ResourceProvider> resource_provider(
+ ResourceProvider::Create(output_surface.get(), 0));
+
+ unsigned texture_id = 1;
+ unsigned sync_point = 30;
+ unsigned target = GL_TEXTURE_EXTERNAL_OES;
+
+ EXPECT_CALL(*context, bindTexture(_, _)).Times(0);
+ EXPECT_CALL(*context, waitSyncPoint(_)).Times(0);
+ EXPECT_CALL(*context, insertSyncPoint()).Times(0);
+ EXPECT_CALL(*context, produceTextureCHROMIUM(_, _)).Times(0);
+ EXPECT_CALL(*context, consumeTextureCHROMIUM(_, _)).Times(0);
+
+ gpu::Mailbox gpu_mailbox;
+ memcpy(gpu_mailbox.name, "Hello world", strlen("Hello world") + 1);
+ TextureMailbox::ReleaseCallback callback = base::Bind(&EmptyReleaseCallback);
+
+ TextureMailbox mailbox(gpu_mailbox,
+ callback,
+ target,
+ sync_point);
+
+ ResourceProvider::ResourceId id =
+ resource_provider->CreateResourceFromTextureMailbox(mailbox);
+ EXPECT_NE(0u, id);
+
+ Mock::VerifyAndClearExpectations(context);
+
+ {
+ // Using the texture does a consume of the mailbox.
+ EXPECT_CALL(*context, bindTexture(target, texture_id));
+ EXPECT_CALL(*context, waitSyncPoint(sync_point));
+ EXPECT_CALL(*context, consumeTextureCHROMIUM(target, _));
+
+ EXPECT_CALL(*context, insertSyncPoint()).Times(0);
+ EXPECT_CALL(*context, produceTextureCHROMIUM(_, _)).Times(0);
+
+ ResourceProvider::ScopedReadLockGL lock(resource_provider.get(), id);
+ Mock::VerifyAndClearExpectations(context);
+
+ // When done with it, a sync point should be inserted, but no produce is
+ // necessary.
+ EXPECT_CALL(*context, bindTexture(_, _)).Times(0);
+ EXPECT_CALL(*context, insertSyncPoint());
+ EXPECT_CALL(*context, produceTextureCHROMIUM(_, _)).Times(0);
+
+ EXPECT_CALL(*context, waitSyncPoint(_)).Times(0);
+ EXPECT_CALL(*context, consumeTextureCHROMIUM(_, _)).Times(0);
+ }
+}
+
+class AllocationTrackingContext3D : public TestWebGraphicsContext3D {
+ public:
+ MOCK_METHOD0(createTexture, WebGLId());
+ MOCK_METHOD1(deleteTexture, void(WebGLId texture_id));
+ MOCK_METHOD2(bindTexture, void(WGC3Denum target, WebGLId texture));
+ MOCK_METHOD9(texImage2D,
+ void(WGC3Denum target,
+ WGC3Dint level,
+ WGC3Denum internalformat,
+ WGC3Dsizei width,
+ WGC3Dsizei height,
+ WGC3Dint border,
+ WGC3Denum format,
+ WGC3Denum type,
+ const void* pixels));
+ MOCK_METHOD9(texSubImage2D,
+ void(WGC3Denum target,
+ WGC3Dint level,
+ WGC3Dint xoffset,
+ WGC3Dint yoffset,
+ WGC3Dsizei width,
+ WGC3Dsizei height,
+ WGC3Denum format,
+ WGC3Denum type,
+ const void* pixels));
+ MOCK_METHOD9(asyncTexImage2DCHROMIUM,
+ void(WGC3Denum target,
+ WGC3Dint level,
+ WGC3Denum internalformat,
+ WGC3Dsizei width,
+ WGC3Dsizei height,
+ WGC3Dint border,
+ WGC3Denum format,
+ WGC3Denum type,
+ const void* pixels));
+ MOCK_METHOD9(asyncTexSubImage2DCHROMIUM,
+ void(WGC3Denum target,
+ WGC3Dint level,
+ WGC3Dint xoffset,
+ WGC3Dint yoffset,
+ WGC3Dsizei width,
+ WGC3Dsizei height,
+ WGC3Denum format,
+ WGC3Denum type,
+ const void* pixels));
+ MOCK_METHOD1(waitAsyncTexImage2DCHROMIUM, void(WGC3Denum));
+ MOCK_METHOD3(createImageCHROMIUM, WGC3Duint(WGC3Dsizei, WGC3Dsizei,
+ WGC3Denum));
+ MOCK_METHOD1(destroyImageCHROMIUM, void(WGC3Duint));
+ MOCK_METHOD2(mapImageCHROMIUM, void*(WGC3Duint, WGC3Denum));
+ MOCK_METHOD3(getImageParameterivCHROMIUM, void(WGC3Duint, WGC3Denum,
+ GLint*));
+ MOCK_METHOD1(unmapImageCHROMIUM, void(WGC3Duint));
+ MOCK_METHOD2(bindTexImage2DCHROMIUM, void(WGC3Denum, WGC3Dint));
+ MOCK_METHOD2(releaseTexImage2DCHROMIUM, void(WGC3Denum, WGC3Dint));
+};
+
+TEST_P(ResourceProviderTest, TextureAllocation) {
+ // Only for GL textures.
+ if (GetParam() != ResourceProvider::GLTexture)
+ return;
+ scoped_ptr<WebKit::WebGraphicsContext3D> mock_context(
+ static_cast<WebKit::WebGraphicsContext3D*>(
+ new StrictMock<AllocationTrackingContext3D>));
+ scoped_ptr<OutputSurface> output_surface(
+ FakeOutputSurface::Create3d(mock_context.Pass()));
+
+ gfx::Size size(2, 2);
+ gfx::Vector2d offset(0, 0);
+ gfx::Rect rect(0, 0, 2, 2);
+ WGC3Denum format = GL_RGBA;
+ ResourceProvider::ResourceId id = 0;
+ uint8_t pixels[16] = { 0 };
+ int texture_id = 123;
+
+ AllocationTrackingContext3D* context =
+ static_cast<AllocationTrackingContext3D*>(output_surface->context3d());
+ scoped_ptr<ResourceProvider> resource_provider(
+ ResourceProvider::Create(output_surface.get(), 0));
+
+ // Lazy allocation. Don't allocate when creating the resource.
+ id = resource_provider->CreateResource(
+ size, format, ResourceProvider::TextureUsageAny);
+
+ EXPECT_CALL(*context, createTexture()).WillOnce(Return(texture_id));
+ EXPECT_CALL(*context, bindTexture(GL_TEXTURE_2D, texture_id)).Times(1);
+ resource_provider->CreateForTesting(id);
+
+ EXPECT_CALL(*context, deleteTexture(texture_id)).Times(1);
+ resource_provider->DeleteResource(id);
+
+ Mock::VerifyAndClearExpectations(context);
+
+ // Do allocate when we set the pixels.
+ id = resource_provider->CreateResource(
+ size, format, ResourceProvider::TextureUsageAny);
+
+ EXPECT_CALL(*context, createTexture()).WillOnce(Return(texture_id));
+ EXPECT_CALL(*context, bindTexture(GL_TEXTURE_2D, texture_id)).Times(3);
+ EXPECT_CALL(*context, texImage2D(_, _, _, 2, 2, _, _, _, _)).Times(1);
+ EXPECT_CALL(*context, texSubImage2D(_, _, _, _, 2, 2, _, _, _)).Times(1);
+ resource_provider->SetPixels(id, pixels, rect, rect, offset);
+
+ EXPECT_CALL(*context, deleteTexture(texture_id)).Times(1);
+ resource_provider->DeleteResource(id);
+
+ Mock::VerifyAndClearExpectations(context);
+
+ // Same for async version.
+ id = resource_provider->CreateResource(
+ size, format, ResourceProvider::TextureUsageAny);
+ resource_provider->AcquirePixelBuffer(id);
+
+ EXPECT_CALL(*context, createTexture()).WillOnce(Return(texture_id));
+ EXPECT_CALL(*context, bindTexture(GL_TEXTURE_2D, texture_id)).Times(2);
+ EXPECT_CALL(*context, asyncTexImage2DCHROMIUM(_, _, _, 2, 2, _, _, _, _))
+ .Times(1);
+ resource_provider->BeginSetPixels(id);
+ ASSERT_TRUE(resource_provider->DidSetPixelsComplete(id));
+
+ resource_provider->ReleasePixelBuffer(id);
+
+ EXPECT_CALL(*context, deleteTexture(texture_id)).Times(1);
+ resource_provider->DeleteResource(id);
+
+ Mock::VerifyAndClearExpectations(context);
+}
+
+TEST_P(ResourceProviderTest, PixelBuffer_GLTexture) {
+ if (GetParam() != ResourceProvider::GLTexture)
+ return;
+ scoped_ptr<WebKit::WebGraphicsContext3D> mock_context(
+ static_cast<WebKit::WebGraphicsContext3D*>(
+ new StrictMock<AllocationTrackingContext3D>));
+ scoped_ptr<OutputSurface> output_surface(
+ FakeOutputSurface::Create3d(mock_context.Pass()));
+
+ gfx::Size size(2, 2);
+ WGC3Denum format = GL_RGBA;
+ ResourceProvider::ResourceId id = 0;
+ int texture_id = 123;
+
+ AllocationTrackingContext3D* context =
+ static_cast<AllocationTrackingContext3D*>(output_surface->context3d());
+ scoped_ptr<ResourceProvider> resource_provider(
+ ResourceProvider::Create(output_surface.get(), 0));
+
+ id = resource_provider->CreateResource(
+ size, format, ResourceProvider::TextureUsageAny);
+ resource_provider->AcquirePixelBuffer(id);
+
+ EXPECT_CALL(*context, createTexture()).WillOnce(Return(texture_id));
+ EXPECT_CALL(*context, bindTexture(GL_TEXTURE_2D, texture_id)).Times(2);
+ EXPECT_CALL(*context, asyncTexImage2DCHROMIUM(_, _, _, 2, 2, _, _, _, _))
+ .Times(1);
+ resource_provider->BeginSetPixels(id);
+
+ EXPECT_TRUE(resource_provider->DidSetPixelsComplete(id));
+
+ resource_provider->ReleasePixelBuffer(id);
+
+ EXPECT_CALL(*context, deleteTexture(texture_id)).Times(1);
+ resource_provider->DeleteResource(id);
+
+ Mock::VerifyAndClearExpectations(context);
+}
+
+TEST_P(ResourceProviderTest, PixelBuffer_Bitmap) {
+ if (GetParam() != ResourceProvider::Bitmap)
+ return;
+ scoped_ptr<OutputSurface> output_surface(
+ FakeOutputSurface::CreateSoftware(make_scoped_ptr(
+ new SoftwareOutputDevice)));
+
+ gfx::Size size(1, 1);
+ WGC3Denum format = GL_RGBA;
+ ResourceProvider::ResourceId id = 0;
+ const uint32_t kBadBeef = 0xbadbeef;
+
+ scoped_ptr<ResourceProvider> resource_provider(
+ ResourceProvider::Create(output_surface.get(), 0));
+
+ id = resource_provider->CreateResource(
+ size, format, ResourceProvider::TextureUsageAny);
+ resource_provider->AcquirePixelBuffer(id);
+
+ void* data = resource_provider->MapPixelBuffer(id);
+ ASSERT_TRUE(!!data);
+ memcpy(data, &kBadBeef, sizeof(kBadBeef));
+ resource_provider->UnmapPixelBuffer(id);
+
+ resource_provider->BeginSetPixels(id);
+ EXPECT_TRUE(resource_provider->DidSetPixelsComplete(id));
+
+ resource_provider->ReleasePixelBuffer(id);
+
+ {
+ ResourceProvider::ScopedReadLockSoftware lock(resource_provider.get(), id);
+ const SkBitmap* sk_bitmap = lock.sk_bitmap();
+ EXPECT_EQ(sk_bitmap->width(), size.width());
+ EXPECT_EQ(sk_bitmap->height(), size.height());
+ EXPECT_EQ(*sk_bitmap->getAddr32(0, 0), kBadBeef);
+ }
+
+ resource_provider->DeleteResource(id);
+}
+
+TEST_P(ResourceProviderTest, ForcingAsyncUploadToComplete) {
+ // Only for GL textures.
+ if (GetParam() != ResourceProvider::GLTexture)
+ return;
+ scoped_ptr<WebKit::WebGraphicsContext3D> mock_context(
+ static_cast<WebKit::WebGraphicsContext3D*>(
+ new StrictMock<AllocationTrackingContext3D>));
+ scoped_ptr<OutputSurface> output_surface(
+ FakeOutputSurface::Create3d(mock_context.Pass()));
+
+ gfx::Size size(2, 2);
+ WGC3Denum format = GL_RGBA;
+ ResourceProvider::ResourceId id = 0;
+ int texture_id = 123;
+
+ AllocationTrackingContext3D* context =
+ static_cast<AllocationTrackingContext3D*>(output_surface->context3d());
+ scoped_ptr<ResourceProvider> resource_provider(
+ ResourceProvider::Create(output_surface.get(), 0));
+
+ id = resource_provider->CreateResource(
+ size, format, ResourceProvider::TextureUsageAny);
+ resource_provider->AcquirePixelBuffer(id);
+
+ EXPECT_CALL(*context, createTexture()).WillOnce(Return(texture_id));
+ EXPECT_CALL(*context, bindTexture(GL_TEXTURE_2D, texture_id)).Times(2);
+ EXPECT_CALL(*context, asyncTexImage2DCHROMIUM(_, _, _, 2, 2, _, _, _, _))
+ .Times(1);
+ resource_provider->BeginSetPixels(id);
+
+ EXPECT_CALL(*context, bindTexture(GL_TEXTURE_2D, texture_id)).Times(1);
+ EXPECT_CALL(*context, waitAsyncTexImage2DCHROMIUM(GL_TEXTURE_2D)).Times(1);
+ EXPECT_CALL(*context, bindTexture(GL_TEXTURE_2D, 0)).Times(1);
+ resource_provider->ForceSetPixelsToComplete(id);
+
+ resource_provider->ReleasePixelBuffer(id);
+
+ EXPECT_CALL(*context, deleteTexture(texture_id)).Times(1);
+ resource_provider->DeleteResource(id);
+
+ Mock::VerifyAndClearExpectations(context);
+}
+
+TEST_P(ResourceProviderTest, PixelBufferLostContext) {
+ scoped_ptr<WebKit::WebGraphicsContext3D> mock_context(
+ static_cast<WebKit::WebGraphicsContext3D*>(
+ new NiceMock<AllocationTrackingContext3D>));
+ scoped_ptr<OutputSurface> output_surface(
+ FakeOutputSurface::Create3d(mock_context.Pass()));
+
+ gfx::Size size(2, 2);
+ WGC3Denum format = GL_RGBA;
+ ResourceProvider::ResourceId id = 0;
+ int texture_id = 123;
+
+ AllocationTrackingContext3D* context =
+ static_cast<AllocationTrackingContext3D*>(output_surface->context3d());
+ scoped_ptr<ResourceProvider> resource_provider(
+ ResourceProvider::Create(output_surface.get(), 0));
+
+ EXPECT_CALL(*context, createTexture()).WillRepeatedly(Return(texture_id));
+
+ id = resource_provider->CreateResource(
+ size, format, ResourceProvider::TextureUsageAny);
+ context->loseContextCHROMIUM(GL_GUILTY_CONTEXT_RESET_ARB,
+ GL_INNOCENT_CONTEXT_RESET_ARB);
+ resource_provider->AcquirePixelBuffer(id);
+ uint8_t* buffer = resource_provider->MapPixelBuffer(id);
+ EXPECT_TRUE(buffer == NULL);
+ resource_provider->UnmapPixelBuffer(id);
+ resource_provider->ReleasePixelBuffer(id);
+ Mock::VerifyAndClearExpectations(context);
+}
+
+TEST_P(ResourceProviderTest, Image_GLTexture) {
+ // Only for GL textures.
+ if (GetParam() != ResourceProvider::GLTexture)
+ return;
+ scoped_ptr<WebKit::WebGraphicsContext3D> mock_context(
+ static_cast<WebKit::WebGraphicsContext3D*>(
+ new StrictMock<AllocationTrackingContext3D>));
+ scoped_ptr<OutputSurface> output_surface(
+ FakeOutputSurface::Create3d(mock_context.Pass()));
+
+ const int kWidth = 2;
+ const int kHeight = 2;
+ gfx::Size size(kWidth, kHeight);
+ WGC3Denum format = GL_RGBA;
+ ResourceProvider::ResourceId id = 0;
+ const unsigned kTextureId = 123u;
+ const unsigned kImageId = 234u;
+
+ AllocationTrackingContext3D* context =
+ static_cast<AllocationTrackingContext3D*>(output_surface->context3d());
+ scoped_ptr<ResourceProvider> resource_provider(
+ ResourceProvider::Create(output_surface.get(), 0));
+
+ id = resource_provider->CreateResource(
+ size, format, ResourceProvider::TextureUsageAny);
+ EXPECT_CALL(*context, createImageCHROMIUM(kWidth, kHeight, GL_RGBA8_OES))
+ .WillOnce(Return(kImageId))
+ .RetiresOnSaturation();
+ resource_provider->AcquireImage(id);
+
+ void* dummy_mapped_buffer_address = NULL;
+ EXPECT_CALL(*context, mapImageCHROMIUM(kImageId, GL_READ_WRITE))
+ .WillOnce(Return(dummy_mapped_buffer_address))
+ .RetiresOnSaturation();
+ resource_provider->MapImage(id);
+
+ const int kStride = 4;
+ EXPECT_CALL(*context, getImageParameterivCHROMIUM(kImageId,
+ GL_IMAGE_ROWBYTES_CHROMIUM,
+ _))
+ .WillOnce(SetArgPointee<2>(kStride))
+ .RetiresOnSaturation();
+ int stride = resource_provider->GetImageStride(id);
+ EXPECT_EQ(kStride, stride);
+
+ EXPECT_CALL(*context, unmapImageCHROMIUM(kImageId))
+ .Times(1)
+ .RetiresOnSaturation();
+ resource_provider->UnmapImage(id);
+
+ EXPECT_CALL(*context, createTexture())
+ .WillOnce(Return(kTextureId))
+ .RetiresOnSaturation();
+ // Once in CreateTextureId and once in BindForSampling
+ EXPECT_CALL(*context, bindTexture(GL_TEXTURE_2D, kTextureId)).Times(2)
+ .RetiresOnSaturation();
+ EXPECT_CALL(*context, bindTexImage2DCHROMIUM(GL_TEXTURE_2D, kImageId))
+ .Times(1)
+ .RetiresOnSaturation();
+ EXPECT_CALL(*context, releaseTexImage2DCHROMIUM(GL_TEXTURE_2D, kImageId))
+ .Times(1)
+ .RetiresOnSaturation();
+ EXPECT_CALL(*context, deleteTexture(kTextureId))
+ .Times(1)
+ .RetiresOnSaturation();
+ {
+ ResourceProvider::ScopedSamplerGL lock_gl(
+ resource_provider.get(), id, GL_TEXTURE_2D, GL_LINEAR);
+ EXPECT_EQ(kTextureId, lock_gl.texture_id());
+ }
+
+ EXPECT_CALL(*context, destroyImageCHROMIUM(kImageId))
+ .Times(1)
+ .RetiresOnSaturation();
+ resource_provider->ReleaseImage(id);
+}
+
+TEST_P(ResourceProviderTest, Image_Bitmap) {
+ if (GetParam() != ResourceProvider::Bitmap)
+ return;
+ scoped_ptr<OutputSurface> output_surface(
+ FakeOutputSurface::CreateSoftware(make_scoped_ptr(
+ new SoftwareOutputDevice)));
+
+ gfx::Size size(1, 1);
+ WGC3Denum format = GL_RGBA;
+ ResourceProvider::ResourceId id = 0;
+ const uint32_t kBadBeef = 0xbadbeef;
+
+ scoped_ptr<ResourceProvider> resource_provider(
+ ResourceProvider::Create(output_surface.get(), 0));
+
+ id = resource_provider->CreateResource(
+ size, format, ResourceProvider::TextureUsageAny);
+ resource_provider->AcquireImage(id);
+
+ const int kStride = 0;
+ int stride = resource_provider->GetImageStride(id);
+ EXPECT_EQ(kStride, stride);
+
+ void* data = resource_provider->MapImage(id);
+ ASSERT_TRUE(!!data);
+ memcpy(data, &kBadBeef, sizeof(kBadBeef));
+ resource_provider->UnmapImage(id);
+
+ {
+ ResourceProvider::ScopedReadLockSoftware lock(resource_provider.get(), id);
+ const SkBitmap* sk_bitmap = lock.sk_bitmap();
+ EXPECT_EQ(sk_bitmap->width(), size.width());
+ EXPECT_EQ(sk_bitmap->height(), size.height());
+ EXPECT_EQ(*sk_bitmap->getAddr32(0, 0), kBadBeef);
+ }
+
+ resource_provider->ReleaseImage(id);
+ resource_provider->DeleteResource(id);
+}
+
+void InitializeGLAndCheck(ContextSharedData* shared_data,
+ ResourceProvider* resource_provider,
+ FakeOutputSurface* output_surface) {
+ scoped_ptr<ResourceProviderContext> context =
+ ResourceProviderContext::Create(shared_data);
+ output_surface->SetAndInitializeContext3D(
+ context.PassAs<WebKit::WebGraphicsContext3D>());
+ EXPECT_TRUE(resource_provider->InitializeGL());
+ CheckCreateResource(
+ ResourceProvider::GLTexture,
+ resource_provider,
+ static_cast<ResourceProviderContext*>(output_surface->context3d()));
+}
+
+TEST(ResourceProviderTest, BasicInitializeGLSoftware) {
+ scoped_ptr<ContextSharedData> shared_data = ContextSharedData::Create();
+ FakeOutputSurfaceClient client;
+ scoped_ptr<FakeOutputSurface> output_surface(
+ FakeOutputSurface::CreateDeferredGL(
+ scoped_ptr<SoftwareOutputDevice>(new SoftwareOutputDevice)));
+ EXPECT_TRUE(output_surface->BindToClient(&client));
+ scoped_ptr<ResourceProvider> resource_provider(
+ ResourceProvider::Create(output_surface.get(), 0));
+
+ CheckCreateResource(ResourceProvider::Bitmap, resource_provider.get(), NULL);
+
+ InitializeGLAndCheck(shared_data.get(),
+ resource_provider.get(),
+ output_surface.get());
+
+ resource_provider->InitializeSoftware();
+ CheckCreateResource(ResourceProvider::Bitmap, resource_provider.get(), NULL);
+
+ InitializeGLAndCheck(shared_data.get(),
+ resource_provider.get(),
+ output_surface.get());
+}
+
+INSTANTIATE_TEST_CASE_P(
+ ResourceProviderTests,
+ ResourceProviderTest,
+ ::testing::Values(ResourceProvider::GLTexture, ResourceProvider::Bitmap));
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/resources/resource_update.cc b/chromium/cc/resources/resource_update.cc
new file mode 100644
index 00000000000..5760ddf4a0d
--- /dev/null
+++ b/chromium/cc/resources/resource_update.cc
@@ -0,0 +1,51 @@
+// 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 "cc/resources/resource_update.h"
+
+#include "base/logging.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkDevice.h"
+
+namespace cc {
+
+ResourceUpdate ResourceUpdate::Create(PrioritizedResource* texture,
+ const SkBitmap* bitmap,
+ gfx::Rect content_rect,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset) {
+ CHECK(content_rect.Contains(source_rect));
+ ResourceUpdate update;
+ update.texture = texture;
+ update.bitmap = bitmap;
+ update.content_rect = content_rect;
+ update.source_rect = source_rect;
+ update.dest_offset = dest_offset;
+ return update;
+}
+
+ResourceUpdate ResourceUpdate::CreateFromCanvas(
+ PrioritizedResource* resource,
+ const skia::RefPtr<SkCanvas>& canvas,
+ gfx::Rect content_rect,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset) {
+ CHECK(content_rect.Contains(source_rect));
+ ResourceUpdate update;
+ update.texture = resource;
+ update.canvas = canvas;
+ update.bitmap = &canvas->getDevice()->accessBitmap(false);
+ update.content_rect = content_rect;
+ update.source_rect = source_rect;
+ update.dest_offset = dest_offset;
+ return update;
+}
+
+ResourceUpdate::ResourceUpdate()
+ : texture(NULL),
+ bitmap(NULL) {}
+
+ResourceUpdate::~ResourceUpdate() {}
+
+} // namespace cc
diff --git a/chromium/cc/resources/resource_update.h b/chromium/cc/resources/resource_update.h
new file mode 100644
index 00000000000..47bef7da189
--- /dev/null
+++ b/chromium/cc/resources/resource_update.h
@@ -0,0 +1,46 @@
+// 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.
+
+#ifndef CC_RESOURCES_RESOURCE_UPDATE_H_
+#define CC_RESOURCES_RESOURCE_UPDATE_H_
+
+#include "base/memory/ref_counted.h"
+#include "cc/base/cc_export.h"
+#include "skia/ext/refptr.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/vector2d.h"
+
+class SkBitmap;
+class SkCanvas;
+
+namespace cc {
+
+class PrioritizedResource;
+
+struct CC_EXPORT ResourceUpdate {
+ static ResourceUpdate Create(PrioritizedResource* resource,
+ const SkBitmap* bitmap,
+ gfx::Rect content_rect,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset);
+ static ResourceUpdate CreateFromCanvas(PrioritizedResource* resource,
+ const skia::RefPtr<SkCanvas>& canvas,
+ gfx::Rect content_rect,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset);
+
+ ResourceUpdate();
+ virtual ~ResourceUpdate();
+
+ PrioritizedResource* texture;
+ const SkBitmap* bitmap;
+ skia::RefPtr<SkCanvas> canvas;
+ gfx::Rect content_rect;
+ gfx::Rect source_rect;
+ gfx::Vector2d dest_offset;
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_RESOURCE_UPDATE_H_
diff --git a/chromium/cc/resources/resource_update_controller.cc b/chromium/cc/resources/resource_update_controller.cc
new file mode 100644
index 00000000000..a46a7d1a36e
--- /dev/null
+++ b/chromium/cc/resources/resource_update_controller.cc
@@ -0,0 +1,180 @@
+// 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 "cc/resources/resource_update_controller.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/single_thread_task_runner.h"
+#include "cc/resources/prioritized_resource.h"
+#include "cc/resources/resource_provider.h"
+
+namespace {
+
+// Number of partial updates we allow.
+const size_t kPartialTextureUpdatesMax = 12;
+
+// Measured in seconds.
+const double kTextureUpdateTickRate = 0.004;
+
+// Measured in seconds.
+const double kUploaderBusyTickRate = 0.001;
+
+// Number of blocking update intervals to allow.
+const size_t kMaxBlockingUpdateIntervals = 4;
+
+} // namespace
+
+namespace cc {
+
+size_t ResourceUpdateController::MaxPartialTextureUpdates() {
+ return kPartialTextureUpdatesMax;
+}
+
+size_t ResourceUpdateController::MaxFullUpdatesPerTick(
+ ResourceProvider* resource_provider) {
+ double textures_per_second = resource_provider->EstimatedUploadsPerSecond();
+ size_t textures_per_tick =
+ floor(kTextureUpdateTickRate * textures_per_second);
+ return textures_per_tick ? textures_per_tick : 1;
+}
+
+ResourceUpdateController::ResourceUpdateController(
+ ResourceUpdateControllerClient* client,
+ base::SingleThreadTaskRunner* task_runner,
+ scoped_ptr<ResourceUpdateQueue> queue,
+ ResourceProvider* resource_provider)
+ : client_(client),
+ queue_(queue.Pass()),
+ resource_provider_(resource_provider),
+ texture_updates_per_tick_(MaxFullUpdatesPerTick(resource_provider)),
+ first_update_attempt_(true),
+ task_runner_(task_runner),
+ weak_factory_(this),
+ task_posted_(false) {}
+
+ResourceUpdateController::~ResourceUpdateController() {}
+
+void ResourceUpdateController::PerformMoreUpdates(
+ base::TimeTicks time_limit) {
+ time_limit_ = time_limit;
+
+ // Update already in progress.
+ if (task_posted_)
+ return;
+
+ // Call UpdateMoreTexturesNow() directly unless it's the first update
+ // attempt. This ensures that we empty the update queue in a finite
+ // amount of time.
+ if (!first_update_attempt_)
+ UpdateMoreTexturesNow();
+
+ // Post a 0-delay task when no updates were left. When it runs,
+ // ReadyToFinalizeTextureUpdates() will be called.
+ if (!UpdateMoreTexturesIfEnoughTimeRemaining()) {
+ task_posted_ = true;
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&ResourceUpdateController::OnTimerFired,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ first_update_attempt_ = false;
+}
+
+void ResourceUpdateController::DiscardUploadsToEvictedResources() {
+ queue_->ClearUploadsToEvictedResources();
+}
+
+void ResourceUpdateController::UpdateTexture(ResourceUpdate update) {
+ update.bitmap->lockPixels();
+ update.texture->SetPixels(
+ resource_provider_,
+ static_cast<const uint8_t*>(update.bitmap->getPixels()),
+ update.content_rect,
+ update.source_rect,
+ update.dest_offset);
+ update.bitmap->unlockPixels();
+}
+
+void ResourceUpdateController::Finalize() {
+ while (queue_->FullUploadSize())
+ UpdateTexture(queue_->TakeFirstFullUpload());
+
+ while (queue_->PartialUploadSize())
+ UpdateTexture(queue_->TakeFirstPartialUpload());
+
+ resource_provider_->FlushUploads();
+}
+
+void ResourceUpdateController::OnTimerFired() {
+ task_posted_ = false;
+ if (!UpdateMoreTexturesIfEnoughTimeRemaining())
+ client_->ReadyToFinalizeTextureUpdates();
+}
+
+base::TimeTicks ResourceUpdateController::Now() const {
+ return base::TimeTicks::Now();
+}
+
+base::TimeDelta ResourceUpdateController::UpdateMoreTexturesTime() const {
+ return base::TimeDelta::FromMilliseconds(kTextureUpdateTickRate * 1000);
+}
+
+size_t ResourceUpdateController::UpdateMoreTexturesSize() const {
+ return texture_updates_per_tick_;
+}
+
+size_t ResourceUpdateController::MaxBlockingUpdates() const {
+ return UpdateMoreTexturesSize() * kMaxBlockingUpdateIntervals;
+}
+
+base::TimeDelta ResourceUpdateController::PendingUpdateTime() const {
+ base::TimeDelta update_one_resource_time =
+ UpdateMoreTexturesTime() / UpdateMoreTexturesSize();
+ return update_one_resource_time * resource_provider_->NumBlockingUploads();
+}
+
+bool ResourceUpdateController::UpdateMoreTexturesIfEnoughTimeRemaining() {
+ while (resource_provider_->NumBlockingUploads() < MaxBlockingUpdates()) {
+ if (!queue_->FullUploadSize())
+ return false;
+
+ if (!time_limit_.is_null()) {
+ // Estimated completion time of all pending updates.
+ base::TimeTicks completion_time = Now() + PendingUpdateTime();
+
+ // Time remaining based on current completion estimate.
+ base::TimeDelta time_remaining = time_limit_ - completion_time;
+
+ if (time_remaining < UpdateMoreTexturesTime())
+ return true;
+ }
+
+ UpdateMoreTexturesNow();
+ }
+
+ task_posted_ = true;
+ task_runner_->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&ResourceUpdateController::OnTimerFired,
+ weak_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(kUploaderBusyTickRate * 1000));
+ return true;
+}
+
+void ResourceUpdateController::UpdateMoreTexturesNow() {
+ size_t uploads = std::min(
+ queue_->FullUploadSize(), UpdateMoreTexturesSize());
+
+ if (!uploads)
+ return;
+
+ while (queue_->FullUploadSize() && uploads--)
+ UpdateTexture(queue_->TakeFirstFullUpload());
+
+ resource_provider_->FlushUploads();
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/resource_update_controller.h b/chromium/cc/resources/resource_update_controller.h
new file mode 100644
index 00000000000..97138bcf5d5
--- /dev/null
+++ b/chromium/cc/resources/resource_update_controller.h
@@ -0,0 +1,90 @@
+// 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.
+
+#ifndef CC_RESOURCES_RESOURCE_UPDATE_CONTROLLER_H_
+#define CC_RESOURCES_RESOURCE_UPDATE_CONTROLLER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "cc/base/cc_export.h"
+#include "cc/resources/resource_update_queue.h"
+
+namespace base { class SingleThreadTaskRunner; }
+
+namespace cc {
+
+class ResourceProvider;
+
+class ResourceUpdateControllerClient {
+ public:
+ virtual void ReadyToFinalizeTextureUpdates() = 0;
+
+ protected:
+ virtual ~ResourceUpdateControllerClient() {}
+};
+
+class CC_EXPORT ResourceUpdateController {
+ public:
+ static scoped_ptr<ResourceUpdateController> Create(
+ ResourceUpdateControllerClient* client,
+ base::SingleThreadTaskRunner* task_runner,
+ scoped_ptr<ResourceUpdateQueue> queue,
+ ResourceProvider* resource_provider) {
+ return make_scoped_ptr(new ResourceUpdateController(
+ client, task_runner, queue.Pass(), resource_provider));
+ }
+ static size_t MaxPartialTextureUpdates();
+
+ virtual ~ResourceUpdateController();
+
+ // Discard uploads to textures that were evicted on the impl thread.
+ void DiscardUploadsToEvictedResources();
+
+ void PerformMoreUpdates(base::TimeTicks time_limit);
+ void Finalize();
+
+
+ // Virtual for testing.
+ virtual base::TimeTicks Now() const;
+ virtual base::TimeDelta UpdateMoreTexturesTime() const;
+ virtual size_t UpdateMoreTexturesSize() const;
+
+ protected:
+ ResourceUpdateController(ResourceUpdateControllerClient* client,
+ base::SingleThreadTaskRunner* task_runner,
+ scoped_ptr<ResourceUpdateQueue> queue,
+ ResourceProvider* resource_provider);
+
+ private:
+ static size_t MaxFullUpdatesPerTick(ResourceProvider* resource_provider);
+
+ size_t MaxBlockingUpdates() const;
+ base::TimeDelta PendingUpdateTime() const;
+
+ void UpdateTexture(ResourceUpdate update);
+
+ // This returns true when there were textures left to update.
+ bool UpdateMoreTexturesIfEnoughTimeRemaining();
+ void UpdateMoreTexturesNow();
+ void OnTimerFired();
+
+ ResourceUpdateControllerClient* client_;
+ scoped_ptr<ResourceUpdateQueue> queue_;
+ bool contents_textures_purged_;
+ ResourceProvider* resource_provider_;
+ base::TimeTicks time_limit_;
+ size_t texture_updates_per_tick_;
+ bool first_update_attempt_;
+ base::SingleThreadTaskRunner* task_runner_;
+ base::WeakPtrFactory<ResourceUpdateController> weak_factory_;
+ bool task_posted_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResourceUpdateController);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_RESOURCE_UPDATE_CONTROLLER_H_
diff --git a/chromium/cc/resources/resource_update_controller_unittest.cc b/chromium/cc/resources/resource_update_controller_unittest.cc
new file mode 100644
index 00000000000..9dcb23ca124
--- /dev/null
+++ b/chromium/cc/resources/resource_update_controller_unittest.cc
@@ -0,0 +1,519 @@
+// 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 "cc/resources/resource_update_controller.h"
+
+#include "base/test/test_simple_task_runner.h"
+#include "cc/debug/test_web_graphics_context_3d.h"
+#include "cc/resources/prioritized_resource_manager.h"
+#include "cc/test/fake_output_surface.h"
+#include "cc/test/fake_proxy.h"
+#include "cc/test/scheduler_test_common.h"
+#include "cc/test/tiled_layer_test_common.h"
+#include "cc/trees/single_thread_proxy.h" // For DebugScopedSetImplThread
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/khronos/GLES2/gl2ext.h"
+
+using testing::Test;
+using WebKit::WGC3Denum;
+using WebKit::WGC3Dint;
+using WebKit::WGC3Duint;
+using WebKit::WGC3Dsizei;
+using WebKit::WebGLId;
+using WebKit::WebString;
+
+namespace cc {
+namespace {
+
+const int kFlushPeriodFull = 4;
+const int kFlushPeriodPartial = kFlushPeriodFull;
+
+class ResourceUpdateControllerTest;
+
+class WebGraphicsContext3DForUploadTest : public TestWebGraphicsContext3D {
+ public:
+ explicit WebGraphicsContext3DForUploadTest(ResourceUpdateControllerTest* test)
+ : test_(test),
+ support_shallow_flush_(true) {}
+
+ virtual void flush(void) OVERRIDE;
+ virtual void shallowFlushCHROMIUM(void) OVERRIDE;
+ virtual void texSubImage2D(
+ WGC3Denum target,
+ WGC3Dint level,
+ WGC3Dint xoffset,
+ WGC3Dint yoffset,
+ WGC3Dsizei width,
+ WGC3Dsizei height,
+ WGC3Denum format,
+ WGC3Denum type,
+ const void* pixels) OVERRIDE;
+ virtual GrGLInterface* onCreateGrGLInterface() OVERRIDE { return NULL; }
+
+ virtual WebString getString(WGC3Denum name) OVERRIDE {
+ if (support_shallow_flush_)
+ return WebString("GL_CHROMIUM_shallow_flush");
+ return WebString("");
+ }
+
+ virtual void getQueryObjectuivEXT(
+ WebGLId id,
+ WGC3Denum pname,
+ WGC3Duint* value);
+
+ private:
+ ResourceUpdateControllerTest* test_;
+ bool support_shallow_flush_;
+};
+
+class ResourceUpdateControllerTest : public Test {
+ public:
+ ResourceUpdateControllerTest()
+ : proxy_(),
+ queue_(make_scoped_ptr(new ResourceUpdateQueue)),
+ resource_manager_(PrioritizedResourceManager::Create(&proxy_)),
+ query_results_available_(0),
+ full_upload_count_expected_(0),
+ partial_count_expected_(0),
+ total_upload_count_expected_(0),
+ max_upload_count_per_update_(0),
+ num_consecutive_flushes_(0),
+ num_dangling_uploads_(0),
+ num_total_uploads_(0),
+ num_total_flushes_(0) {}
+
+ virtual ~ResourceUpdateControllerTest() {
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ resource_manager_->ClearAllMemory(resource_provider_.get());
+ }
+
+ public:
+ void OnFlush() {
+ // Check for back-to-back flushes.
+ EXPECT_EQ(0, num_consecutive_flushes_) << "Back-to-back flushes detected.";
+
+ num_dangling_uploads_ = 0;
+ num_consecutive_flushes_++;
+ num_total_flushes_++;
+ }
+
+ void OnUpload() {
+ // Check for too many consecutive uploads
+ if (num_total_uploads_ < full_upload_count_expected_) {
+ EXPECT_LT(num_dangling_uploads_, kFlushPeriodFull)
+ << "Too many consecutive full uploads detected.";
+ } else {
+ EXPECT_LT(num_dangling_uploads_, kFlushPeriodPartial)
+ << "Too many consecutive partial uploads detected.";
+ }
+
+ num_consecutive_flushes_ = 0;
+ num_dangling_uploads_++;
+ num_total_uploads_++;
+ }
+
+ bool IsQueryResultAvailable() {
+ if (!query_results_available_)
+ return false;
+
+ query_results_available_--;
+ return true;
+ }
+
+ protected:
+ virtual void SetUp() {
+ output_surface_ =
+ FakeOutputSurface::Create3d(scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new WebGraphicsContext3DForUploadTest(this)));
+ bitmap_.setConfig(SkBitmap::kARGB_8888_Config, 300, 150);
+ bitmap_.allocPixels();
+
+ for (int i = 0; i < 4; i++) {
+ textures_[i] = PrioritizedResource::Create(resource_manager_.get(),
+ gfx::Size(300, 150), GL_RGBA);
+ textures_[i]->
+ set_request_priority(PriorityCalculator::VisiblePriority(true));
+ }
+ resource_manager_->PrioritizeTextures();
+
+ resource_provider_ = ResourceProvider::Create(output_surface_.get(), 0);
+ }
+
+ void AppendFullUploadsOfIndexedTextureToUpdateQueue(int count,
+ int texture_index) {
+ full_upload_count_expected_ += count;
+ total_upload_count_expected_ += count;
+
+ const gfx::Rect rect(0, 0, 300, 150);
+ const ResourceUpdate upload = ResourceUpdate::Create(
+ textures_[texture_index].get(), &bitmap_, rect, rect, gfx::Vector2d());
+ for (int i = 0; i < count; i++)
+ queue_->AppendFullUpload(upload);
+ }
+
+ void AppendFullUploadsToUpdateQueue(int count) {
+ AppendFullUploadsOfIndexedTextureToUpdateQueue(count, 0);
+ }
+
+ void AppendPartialUploadsOfIndexedTextureToUpdateQueue(int count,
+ int texture_index) {
+ partial_count_expected_ += count;
+ total_upload_count_expected_ += count;
+
+ const gfx::Rect rect(0, 0, 100, 100);
+ const ResourceUpdate upload = ResourceUpdate::Create(
+ textures_[texture_index].get(), &bitmap_, rect, rect, gfx::Vector2d());
+ for (int i = 0; i < count; i++)
+ queue_->AppendPartialUpload(upload);
+ }
+
+ void AppendPartialUploadsToUpdateQueue(int count) {
+ AppendPartialUploadsOfIndexedTextureToUpdateQueue(count, 0);
+ }
+
+ void SetMaxUploadCountPerUpdate(int count) {
+ max_upload_count_per_update_ = count;
+ }
+
+ void UpdateTextures() {
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ scoped_ptr<ResourceUpdateController> update_controller =
+ ResourceUpdateController::Create(NULL,
+ proxy_.ImplThreadTaskRunner(),
+ queue_.Pass(),
+ resource_provider_.get());
+ update_controller->Finalize();
+ }
+
+ void MakeQueryResultAvailable() { query_results_available_++; }
+
+ protected:
+ // Classes required to interact and test the ResourceUpdateController
+ FakeProxy proxy_;
+ scoped_ptr<OutputSurface> output_surface_;
+ scoped_ptr<ResourceProvider> resource_provider_;
+ scoped_ptr<ResourceUpdateQueue> queue_;
+ scoped_ptr<PrioritizedResource> textures_[4];
+ scoped_ptr<PrioritizedResourceManager> resource_manager_;
+ SkBitmap bitmap_;
+ int query_results_available_;
+
+ // Properties / expectations of this test
+ int full_upload_count_expected_;
+ int partial_count_expected_;
+ int total_upload_count_expected_;
+ int max_upload_count_per_update_;
+
+ // Dynamic properties of this test
+ int num_consecutive_flushes_;
+ int num_dangling_uploads_;
+ int num_total_uploads_;
+ int num_total_flushes_;
+};
+
+void WebGraphicsContext3DForUploadTest::flush(void) { test_->OnFlush(); }
+
+void WebGraphicsContext3DForUploadTest::shallowFlushCHROMIUM(void) {
+ test_->OnFlush();
+}
+
+void WebGraphicsContext3DForUploadTest::texSubImage2D(
+ WGC3Denum target,
+ WGC3Dint level,
+ WGC3Dint xoffset,
+ WGC3Dint yoffset,
+ WGC3Dsizei width,
+ WGC3Dsizei height,
+ WGC3Denum format,
+ WGC3Denum type,
+ const void* pixels) {
+ test_->OnUpload();
+}
+
+void WebGraphicsContext3DForUploadTest::getQueryObjectuivEXT(
+ WebGLId id,
+ WGC3Denum pname,
+ WGC3Duint* params) {
+ if (pname == GL_QUERY_RESULT_AVAILABLE_EXT)
+ *params = test_->IsQueryResultAvailable();
+}
+
+// ZERO UPLOADS TESTS
+TEST_F(ResourceUpdateControllerTest, ZeroUploads) {
+ AppendFullUploadsToUpdateQueue(0);
+ AppendPartialUploadsToUpdateQueue(0);
+ UpdateTextures();
+
+ EXPECT_EQ(0, num_total_flushes_);
+ EXPECT_EQ(0, num_total_uploads_);
+}
+
+// ONE UPLOAD TESTS
+TEST_F(ResourceUpdateControllerTest, OneFullUpload) {
+ AppendFullUploadsToUpdateQueue(1);
+ AppendPartialUploadsToUpdateQueue(0);
+ UpdateTextures();
+
+ EXPECT_EQ(1, num_total_flushes_);
+ EXPECT_EQ(1, num_total_uploads_);
+ EXPECT_EQ(0, num_dangling_uploads_)
+ << "Last upload wasn't followed by a flush.";
+}
+
+TEST_F(ResourceUpdateControllerTest, OnePartialUpload) {
+ AppendFullUploadsToUpdateQueue(0);
+ AppendPartialUploadsToUpdateQueue(1);
+ UpdateTextures();
+
+ EXPECT_EQ(1, num_total_flushes_);
+ EXPECT_EQ(1, num_total_uploads_);
+ EXPECT_EQ(0, num_dangling_uploads_)
+ << "Last upload wasn't followed by a flush.";
+}
+
+TEST_F(ResourceUpdateControllerTest, OneFullOnePartialUpload) {
+ AppendFullUploadsToUpdateQueue(1);
+ AppendPartialUploadsToUpdateQueue(1);
+ UpdateTextures();
+
+ EXPECT_EQ(1, num_total_flushes_);
+ EXPECT_EQ(2, num_total_uploads_);
+ EXPECT_EQ(0, num_dangling_uploads_)
+ << "Last upload wasn't followed by a flush.";
+}
+
+// This class of tests upload a number of textures that is a multiple
+// of the flush period.
+const int full_upload_flush_multipler = 7;
+const int full_count = full_upload_flush_multipler * kFlushPeriodFull;
+
+const int partial_upload_flush_multipler = 11;
+const int partial_count =
+ partial_upload_flush_multipler * kFlushPeriodPartial;
+
+TEST_F(ResourceUpdateControllerTest, ManyFullUploads) {
+ AppendFullUploadsToUpdateQueue(full_count);
+ AppendPartialUploadsToUpdateQueue(0);
+ UpdateTextures();
+
+ EXPECT_EQ(full_upload_flush_multipler, num_total_flushes_);
+ EXPECT_EQ(full_count, num_total_uploads_);
+ EXPECT_EQ(0, num_dangling_uploads_)
+ << "Last upload wasn't followed by a flush.";
+}
+
+TEST_F(ResourceUpdateControllerTest, ManyPartialUploads) {
+ AppendFullUploadsToUpdateQueue(0);
+ AppendPartialUploadsToUpdateQueue(partial_count);
+ UpdateTextures();
+
+ EXPECT_EQ(partial_upload_flush_multipler, num_total_flushes_);
+ EXPECT_EQ(partial_count, num_total_uploads_);
+ EXPECT_EQ(0, num_dangling_uploads_)
+ << "Last upload wasn't followed by a flush.";
+}
+
+TEST_F(ResourceUpdateControllerTest, ManyFullManyPartialUploads) {
+ AppendFullUploadsToUpdateQueue(full_count);
+ AppendPartialUploadsToUpdateQueue(partial_count);
+ UpdateTextures();
+
+ EXPECT_EQ(full_upload_flush_multipler + partial_upload_flush_multipler,
+ num_total_flushes_);
+ EXPECT_EQ(full_count + partial_count, num_total_uploads_);
+ EXPECT_EQ(0, num_dangling_uploads_)
+ << "Last upload wasn't followed by a flush.";
+}
+
+class FakeResourceUpdateControllerClient
+ : public ResourceUpdateControllerClient {
+ public:
+ FakeResourceUpdateControllerClient() { Reset(); }
+ void Reset() { ready_to_finalize_called_ = false; }
+ bool ReadyToFinalizeCalled() const { return ready_to_finalize_called_; }
+
+ virtual void ReadyToFinalizeTextureUpdates() OVERRIDE {
+ ready_to_finalize_called_ = true;
+ }
+
+ protected:
+ bool ready_to_finalize_called_;
+};
+
+class FakeResourceUpdateController : public ResourceUpdateController {
+ public:
+ static scoped_ptr<FakeResourceUpdateController> Create(
+ ResourceUpdateControllerClient* client,
+ base::TestSimpleTaskRunner* task_runner,
+ scoped_ptr<ResourceUpdateQueue> queue,
+ ResourceProvider* resource_provider) {
+ return make_scoped_ptr(new FakeResourceUpdateController(
+ client, task_runner, queue.Pass(), resource_provider));
+ }
+
+ void SetNow(base::TimeTicks time) { now_ = time; }
+ virtual base::TimeTicks Now() const OVERRIDE { return now_; }
+ void SetUpdateMoreTexturesTime(base::TimeDelta time) {
+ update_more_textures_time_ = time;
+ }
+ virtual base::TimeDelta UpdateMoreTexturesTime() const OVERRIDE {
+ return update_more_textures_time_;
+ }
+ void SetUpdateMoreTexturesSize(size_t size) {
+ update_more_textures_size_ = size;
+ }
+ virtual size_t UpdateMoreTexturesSize() const OVERRIDE {
+ return update_more_textures_size_;
+ }
+
+ protected:
+ FakeResourceUpdateController(ResourceUpdateControllerClient* client,
+ base::TestSimpleTaskRunner* task_runner,
+ scoped_ptr<ResourceUpdateQueue> queue,
+ ResourceProvider* resource_provider)
+ : ResourceUpdateController(
+ client, task_runner, queue.Pass(), resource_provider),
+ update_more_textures_size_(0) {}
+
+ base::TimeTicks now_;
+ base::TimeDelta update_more_textures_time_;
+ size_t update_more_textures_size_;
+};
+
+static void RunPendingTask(base::TestSimpleTaskRunner* task_runner,
+ FakeResourceUpdateController* controller) {
+ EXPECT_TRUE(task_runner->HasPendingTask());
+ controller->SetNow(controller->Now() + task_runner->NextPendingTaskDelay());
+ task_runner->RunPendingTasks();
+}
+
+TEST_F(ResourceUpdateControllerTest, UpdateMoreTextures) {
+ FakeResourceUpdateControllerClient client;
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner =
+ new base::TestSimpleTaskRunner;
+
+ SetMaxUploadCountPerUpdate(1);
+ AppendFullUploadsToUpdateQueue(3);
+ AppendPartialUploadsToUpdateQueue(0);
+
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ scoped_ptr<FakeResourceUpdateController> controller(
+ FakeResourceUpdateController::Create(&client,
+ task_runner.get(),
+ queue_.Pass(),
+ resource_provider_.get()));
+
+ controller->SetNow(controller->Now() + base::TimeDelta::FromMilliseconds(1));
+ controller->SetUpdateMoreTexturesTime(base::TimeDelta::FromMilliseconds(100));
+ controller->SetUpdateMoreTexturesSize(1);
+ // Not enough time for any updates.
+ controller->PerformMoreUpdates(controller->Now() +
+ base::TimeDelta::FromMilliseconds(90));
+ EXPECT_FALSE(task_runner->HasPendingTask());
+
+ controller->SetUpdateMoreTexturesTime(base::TimeDelta::FromMilliseconds(100));
+ controller->SetUpdateMoreTexturesSize(1);
+ // Only enough time for 1 update.
+ controller->PerformMoreUpdates(controller->Now() +
+ base::TimeDelta::FromMilliseconds(120));
+ EXPECT_FALSE(task_runner->HasPendingTask());
+ EXPECT_EQ(1, num_total_uploads_);
+
+ // Complete one upload.
+ MakeQueryResultAvailable();
+
+ controller->SetUpdateMoreTexturesTime(base::TimeDelta::FromMilliseconds(100));
+ controller->SetUpdateMoreTexturesSize(1);
+ // Enough time for 2 updates.
+ controller->PerformMoreUpdates(controller->Now() +
+ base::TimeDelta::FromMilliseconds(220));
+ RunPendingTask(task_runner.get(), controller.get());
+ EXPECT_FALSE(task_runner->HasPendingTask());
+ EXPECT_TRUE(client.ReadyToFinalizeCalled());
+ EXPECT_EQ(3, num_total_uploads_);
+}
+
+TEST_F(ResourceUpdateControllerTest, NoMoreUpdates) {
+ FakeResourceUpdateControllerClient client;
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner =
+ new base::TestSimpleTaskRunner;
+
+ SetMaxUploadCountPerUpdate(1);
+ AppendFullUploadsToUpdateQueue(2);
+ AppendPartialUploadsToUpdateQueue(0);
+
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ scoped_ptr<FakeResourceUpdateController> controller(
+ FakeResourceUpdateController::Create(&client,
+ task_runner.get(),
+ queue_.Pass(),
+ resource_provider_.get()));
+
+ controller->SetNow(controller->Now() + base::TimeDelta::FromMilliseconds(1));
+ controller->SetUpdateMoreTexturesTime(base::TimeDelta::FromMilliseconds(100));
+ controller->SetUpdateMoreTexturesSize(1);
+ // Enough time for 3 updates but only 2 necessary.
+ controller->PerformMoreUpdates(controller->Now() +
+ base::TimeDelta::FromMilliseconds(310));
+ RunPendingTask(task_runner.get(), controller.get());
+ EXPECT_FALSE(task_runner->HasPendingTask());
+ EXPECT_TRUE(client.ReadyToFinalizeCalled());
+ EXPECT_EQ(2, num_total_uploads_);
+
+ controller->SetUpdateMoreTexturesTime(base::TimeDelta::FromMilliseconds(100));
+ controller->SetUpdateMoreTexturesSize(1);
+ // Enough time for updates but no more updates left.
+ controller->PerformMoreUpdates(controller->Now() +
+ base::TimeDelta::FromMilliseconds(310));
+ // 0-delay task used to call ReadyToFinalizeTextureUpdates().
+ RunPendingTask(task_runner.get(), controller.get());
+ EXPECT_FALSE(task_runner->HasPendingTask());
+ EXPECT_TRUE(client.ReadyToFinalizeCalled());
+ EXPECT_EQ(2, num_total_uploads_);
+}
+
+TEST_F(ResourceUpdateControllerTest, UpdatesCompleteInFiniteTime) {
+ FakeResourceUpdateControllerClient client;
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner =
+ new base::TestSimpleTaskRunner;
+
+ SetMaxUploadCountPerUpdate(1);
+ AppendFullUploadsToUpdateQueue(2);
+ AppendPartialUploadsToUpdateQueue(0);
+
+ DebugScopedSetImplThreadAndMainThreadBlocked
+ impl_thread_and_main_thread_blocked(&proxy_);
+ scoped_ptr<FakeResourceUpdateController> controller(
+ FakeResourceUpdateController::Create(&client,
+ task_runner.get(),
+ queue_.Pass(),
+ resource_provider_.get()));
+
+ controller->SetNow(controller->Now() + base::TimeDelta::FromMilliseconds(1));
+ controller->SetUpdateMoreTexturesTime(base::TimeDelta::FromMilliseconds(500));
+ controller->SetUpdateMoreTexturesSize(1);
+
+ for (int i = 0; i < 100; i++) {
+ if (client.ReadyToFinalizeCalled())
+ break;
+
+ // Not enough time for any updates.
+ controller->PerformMoreUpdates(controller->Now() +
+ base::TimeDelta::FromMilliseconds(400));
+
+ if (task_runner->HasPendingTask())
+ RunPendingTask(task_runner.get(), controller.get());
+ }
+
+ EXPECT_FALSE(task_runner->HasPendingTask());
+ EXPECT_TRUE(client.ReadyToFinalizeCalled());
+ EXPECT_EQ(2, num_total_uploads_);
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/resources/resource_update_queue.cc b/chromium/cc/resources/resource_update_queue.cc
new file mode 100644
index 00000000000..31727cd808c
--- /dev/null
+++ b/chromium/cc/resources/resource_update_queue.cc
@@ -0,0 +1,56 @@
+// 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 "cc/resources/resource_update_queue.h"
+
+#include "cc/resources/prioritized_resource.h"
+
+namespace cc {
+
+ResourceUpdateQueue::ResourceUpdateQueue() {}
+
+ResourceUpdateQueue::~ResourceUpdateQueue() {}
+
+void ResourceUpdateQueue::AppendFullUpload(const ResourceUpdate& upload) {
+ full_entries_.push_back(upload);
+}
+
+void ResourceUpdateQueue::AppendPartialUpload(const ResourceUpdate& upload) {
+ partial_entries_.push_back(upload);
+}
+
+void ResourceUpdateQueue::ClearUploadsToEvictedResources() {
+ ClearUploadsToEvictedResources(&full_entries_);
+ ClearUploadsToEvictedResources(&partial_entries_);
+}
+
+void ResourceUpdateQueue::ClearUploadsToEvictedResources(
+ std::deque<ResourceUpdate>* entry_queue) {
+ std::deque<ResourceUpdate> temp;
+ entry_queue->swap(temp);
+ while (temp.size()) {
+ ResourceUpdate upload = temp.front();
+ temp.pop_front();
+ if (!upload.texture->BackingResourceWasEvicted())
+ entry_queue->push_back(upload);
+ }
+}
+
+ResourceUpdate ResourceUpdateQueue::TakeFirstFullUpload() {
+ ResourceUpdate first = full_entries_.front();
+ full_entries_.pop_front();
+ return first;
+}
+
+ResourceUpdate ResourceUpdateQueue::TakeFirstPartialUpload() {
+ ResourceUpdate first = partial_entries_.front();
+ partial_entries_.pop_front();
+ return first;
+}
+
+bool ResourceUpdateQueue::HasMoreUpdates() const {
+ return !full_entries_.empty() || !partial_entries_.empty();
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/resource_update_queue.h b/chromium/cc/resources/resource_update_queue.h
new file mode 100644
index 00000000000..de455ce43fe
--- /dev/null
+++ b/chromium/cc/resources/resource_update_queue.h
@@ -0,0 +1,43 @@
+// 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.
+
+#ifndef CC_RESOURCES_RESOURCE_UPDATE_QUEUE_H_
+#define CC_RESOURCES_RESOURCE_UPDATE_QUEUE_H_
+
+#include <deque>
+#include "base/basictypes.h"
+#include "cc/base/cc_export.h"
+#include "cc/resources/resource_update.h"
+
+namespace cc {
+
+class CC_EXPORT ResourceUpdateQueue {
+ public:
+ ResourceUpdateQueue();
+ virtual ~ResourceUpdateQueue();
+
+ void AppendFullUpload(const ResourceUpdate& upload);
+ void AppendPartialUpload(const ResourceUpdate& upload);
+
+ void ClearUploadsToEvictedResources();
+
+ ResourceUpdate TakeFirstFullUpload();
+ ResourceUpdate TakeFirstPartialUpload();
+
+ size_t FullUploadSize() const { return full_entries_.size(); }
+ size_t PartialUploadSize() const { return partial_entries_.size(); }
+
+ bool HasMoreUpdates() const;
+
+ private:
+ void ClearUploadsToEvictedResources(std::deque<ResourceUpdate>* entry_queue);
+ std::deque<ResourceUpdate> full_entries_;
+ std::deque<ResourceUpdate> partial_entries_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResourceUpdateQueue);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_RESOURCE_UPDATE_QUEUE_H_
diff --git a/chromium/cc/resources/scoped_resource.cc b/chromium/cc/resources/scoped_resource.cc
new file mode 100644
index 00000000000..e444eee571f
--- /dev/null
+++ b/chromium/cc/resources/scoped_resource.cc
@@ -0,0 +1,48 @@
+// 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 "cc/resources/scoped_resource.h"
+
+namespace cc {
+
+ScopedResource::ScopedResource(ResourceProvider* resource_provider)
+ : resource_provider_(resource_provider) {
+ DCHECK(resource_provider_);
+}
+
+ScopedResource::~ScopedResource() {
+ Free();
+}
+
+bool ScopedResource::Allocate(gfx::Size size,
+ GLenum format,
+ ResourceProvider::TextureUsageHint hint) {
+ DCHECK(!id());
+ DCHECK(!size.IsEmpty());
+
+ set_dimensions(size, format);
+ set_id(resource_provider_->CreateResource(size, format, hint));
+
+#ifndef NDEBUG
+ allocate_thread_id_ = base::PlatformThread::CurrentId();
+#endif
+
+ return id() != 0;
+}
+
+void ScopedResource::Free() {
+ if (id()) {
+#ifndef NDEBUG
+ DCHECK(allocate_thread_id_ == base::PlatformThread::CurrentId());
+#endif
+ resource_provider_->DeleteResource(id());
+ }
+ set_id(0);
+}
+
+void ScopedResource::Leak() {
+ set_id(0);
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/scoped_resource.h b/chromium/cc/resources/scoped_resource.h
new file mode 100644
index 00000000000..6a2c8f1e727
--- /dev/null
+++ b/chromium/cc/resources/scoped_resource.h
@@ -0,0 +1,49 @@
+// 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.
+
+#ifndef CC_RESOURCES_SCOPED_RESOURCE_H_
+#define CC_RESOURCES_SCOPED_RESOURCE_H_
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/resources/resource.h"
+
+#ifndef NDEBUG
+#include "base/threading/platform_thread.h"
+#endif
+
+namespace cc {
+
+class CC_EXPORT ScopedResource : public Resource {
+ public:
+ static scoped_ptr<ScopedResource> create(
+ ResourceProvider* resource_provider) {
+ return make_scoped_ptr(new ScopedResource(resource_provider));
+ }
+ virtual ~ScopedResource();
+
+ bool Allocate(gfx::Size size,
+ GLenum format,
+ ResourceProvider::TextureUsageHint hint);
+ void Free();
+ void Leak();
+
+ protected:
+ explicit ScopedResource(ResourceProvider* provider);
+
+ private:
+ ResourceProvider* resource_provider_;
+
+#ifndef NDEBUG
+ base::PlatformThreadId allocate_thread_id_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedResource);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_SCOPED_RESOURCE_H_
diff --git a/chromium/cc/resources/scoped_resource_unittest.cc b/chromium/cc/resources/scoped_resource_unittest.cc
new file mode 100644
index 00000000000..b08c1e21daf
--- /dev/null
+++ b/chromium/cc/resources/scoped_resource_unittest.cc
@@ -0,0 +1,105 @@
+// 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 "cc/resources/scoped_resource.h"
+
+#include "cc/output/renderer.h"
+#include "cc/test/fake_output_surface.h"
+#include "cc/test/tiled_layer_test_common.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/khronos/GLES2/gl2.h"
+
+namespace cc {
+namespace {
+
+TEST(ScopedResourceTest, NewScopedResource) {
+ scoped_ptr<OutputSurface> context(CreateFakeOutputSurface());
+ scoped_ptr<ResourceProvider> resource_provider(
+ ResourceProvider::Create(context.get(), 0));
+ scoped_ptr<ScopedResource> texture =
+ ScopedResource::create(resource_provider.get());
+
+ // New scoped textures do not hold a texture yet.
+ EXPECT_EQ(0u, texture->id());
+
+ // New scoped textures do not have a size yet.
+ EXPECT_EQ(gfx::Size(), texture->size());
+ EXPECT_EQ(0u, texture->bytes());
+}
+
+TEST(ScopedResourceTest, CreateScopedResource) {
+ scoped_ptr<OutputSurface> context(CreateFakeOutputSurface());
+ scoped_ptr<ResourceProvider> resource_provider(
+ ResourceProvider::Create(context.get(), 0));
+ scoped_ptr<ScopedResource> texture =
+ ScopedResource::create(resource_provider.get());
+ texture->Allocate(
+ gfx::Size(30, 30), GL_RGBA, ResourceProvider::TextureUsageAny);
+
+ // The texture has an allocated byte-size now.
+ size_t expected_bytes = 30 * 30 * 4;
+ EXPECT_EQ(expected_bytes, texture->bytes());
+
+ EXPECT_LT(0u, texture->id());
+ EXPECT_EQ(static_cast<unsigned>(GL_RGBA), texture->format());
+ EXPECT_EQ(gfx::Size(30, 30), texture->size());
+}
+
+TEST(ScopedResourceTest, ScopedResourceIsDeleted) {
+ scoped_ptr<OutputSurface> context(CreateFakeOutputSurface());
+ scoped_ptr<ResourceProvider> resource_provider(
+ ResourceProvider::Create(context.get(), 0));
+ {
+ scoped_ptr<ScopedResource> texture =
+ ScopedResource::create(resource_provider.get());
+
+ EXPECT_EQ(0u, resource_provider->num_resources());
+ texture->Allocate(
+ gfx::Size(30, 30), GL_RGBA, ResourceProvider::TextureUsageAny);
+ EXPECT_LT(0u, texture->id());
+ EXPECT_EQ(1u, resource_provider->num_resources());
+ }
+
+ EXPECT_EQ(0u, resource_provider->num_resources());
+ {
+ scoped_ptr<ScopedResource> texture =
+ ScopedResource::create(resource_provider.get());
+ EXPECT_EQ(0u, resource_provider->num_resources());
+ texture->Allocate(
+ gfx::Size(30, 30), GL_RGBA, ResourceProvider::TextureUsageAny);
+ EXPECT_LT(0u, texture->id());
+ EXPECT_EQ(1u, resource_provider->num_resources());
+ texture->Free();
+ EXPECT_EQ(0u, resource_provider->num_resources());
+ }
+}
+
+TEST(ScopedResourceTest, LeakScopedResource) {
+ scoped_ptr<OutputSurface> context(CreateFakeOutputSurface());
+ scoped_ptr<ResourceProvider> resource_provider(
+ ResourceProvider::Create(context.get(), 0));
+ {
+ scoped_ptr<ScopedResource> texture =
+ ScopedResource::create(resource_provider.get());
+
+ EXPECT_EQ(0u, resource_provider->num_resources());
+ texture->Allocate(
+ gfx::Size(30, 30), GL_RGBA, ResourceProvider::TextureUsageAny);
+ EXPECT_LT(0u, texture->id());
+ EXPECT_EQ(1u, resource_provider->num_resources());
+
+ texture->Leak();
+ EXPECT_EQ(0u, texture->id());
+ EXPECT_EQ(1u, resource_provider->num_resources());
+
+ texture->Free();
+ EXPECT_EQ(0u, texture->id());
+ EXPECT_EQ(1u, resource_provider->num_resources());
+ }
+
+ EXPECT_EQ(1u, resource_provider->num_resources());
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/resources/scoped_ui_resource.cc b/chromium/cc/resources/scoped_ui_resource.cc
new file mode 100644
index 00000000000..16225528cf7
--- /dev/null
+++ b/chromium/cc/resources/scoped_ui_resource.cc
@@ -0,0 +1,43 @@
+// Copyright 2013 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 "cc/resources/scoped_ui_resource.h"
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "cc/trees/layer_tree_host.h"
+
+namespace cc {
+
+scoped_ptr<ScopedUIResource> ScopedUIResource::Create(
+ LayerTreeHost* host,
+ scoped_refptr<UIResourceBitmap> bitmap) {
+ return make_scoped_ptr(new ScopedUIResource(host, bitmap));
+}
+
+ScopedUIResource::ScopedUIResource(LayerTreeHost* host,
+ scoped_refptr<UIResourceBitmap> bitmap)
+ : bitmap_(bitmap), host_(host) {
+ DCHECK(host_);
+ id_ = host_->CreateUIResource(this);
+}
+
+// User must make sure that host is still valid before this object goes out of
+// scope.
+ScopedUIResource::~ScopedUIResource() {
+ if (id_) {
+ DCHECK(host_);
+ host_->DeleteUIResource(id_);
+ }
+}
+
+scoped_refptr<UIResourceBitmap> ScopedUIResource::GetBitmap(
+ UIResourceId uid,
+ bool resource_lost) {
+ return bitmap_;
+}
+
+ScopedUIResource::ScopedUIResource() {}
+
+} // namespace cc
diff --git a/chromium/cc/resources/scoped_ui_resource.h b/chromium/cc/resources/scoped_ui_resource.h
new file mode 100644
index 00000000000..628d372c552
--- /dev/null
+++ b/chromium/cc/resources/scoped_ui_resource.h
@@ -0,0 +1,46 @@
+// Copyright 2013 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.
+
+#ifndef CC_RESOURCES_SCOPED_UI_RESOURCE_H_
+#define CC_RESOURCES_SCOPED_UI_RESOURCE_H_
+
+#include "base/memory/ref_counted.h"
+#include "cc/base/cc_export.h"
+#include "cc/resources/ui_resource_bitmap.h"
+#include "cc/resources/ui_resource_client.h"
+#include "ui/gfx/size.h"
+
+namespace cc {
+
+class LayerTreeHost;
+
+class CC_EXPORT ScopedUIResource : public UIResourceClient {
+ public:
+ static scoped_ptr<ScopedUIResource> Create(
+ LayerTreeHost* host,
+ scoped_refptr<UIResourceBitmap> bitmap);
+ virtual ~ScopedUIResource();
+
+ virtual scoped_refptr<UIResourceBitmap> GetBitmap(
+ UIResourceId uid,
+ bool resource_lost) OVERRIDE;
+ UIResourceId id() { return id_; }
+
+ protected:
+ ScopedUIResource(LayerTreeHost* host, scoped_refptr<UIResourceBitmap> bitmap);
+
+ // An empty default contructor for testing.
+ ScopedUIResource();
+
+ scoped_refptr<UIResourceBitmap> bitmap_;
+ LayerTreeHost* host_;
+ UIResourceId id_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScopedUIResource);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_SCOPED_UI_RESOURCE_H_
diff --git a/chromium/cc/resources/skpicture_content_layer_updater.cc b/chromium/cc/resources/skpicture_content_layer_updater.cc
new file mode 100644
index 00000000000..e44dbc25205
--- /dev/null
+++ b/chromium/cc/resources/skpicture_content_layer_updater.cc
@@ -0,0 +1,56 @@
+// Copyright 2011 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 "cc/resources/skpicture_content_layer_updater.h"
+
+#include "base/debug/trace_event.h"
+#include "cc/debug/rendering_stats_instrumentation.h"
+#include "cc/resources/layer_painter.h"
+#include "cc/resources/prioritized_resource.h"
+#include "cc/resources/resource_update_queue.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+
+namespace cc {
+
+SkPictureContentLayerUpdater::SkPictureContentLayerUpdater(
+ scoped_ptr<LayerPainter> painter,
+ RenderingStatsInstrumentation* stats_instrumentation,
+ int layer_id)
+ : ContentLayerUpdater(painter.Pass(), stats_instrumentation, layer_id),
+ layer_is_opaque_(false) {}
+
+SkPictureContentLayerUpdater::~SkPictureContentLayerUpdater() {}
+
+void SkPictureContentLayerUpdater::PrepareToUpdate(
+ gfx::Rect content_rect,
+ gfx::Size,
+ float contents_width_scale,
+ float contents_height_scale,
+ gfx::Rect* resulting_opaque_rect) {
+ SkCanvas* canvas =
+ picture_.beginRecording(content_rect.width(), content_rect.height());
+ base::TimeTicks start_time =
+ rendering_stats_instrumentation_->StartRecording();
+ PaintContents(canvas,
+ content_rect,
+ contents_width_scale,
+ contents_height_scale,
+ resulting_opaque_rect);
+ base::TimeDelta duration =
+ rendering_stats_instrumentation_->EndRecording(start_time);
+ rendering_stats_instrumentation_->AddRecord(
+ duration, content_rect.width() * content_rect.height());
+ picture_.endRecording();
+}
+
+void SkPictureContentLayerUpdater::DrawPicture(SkCanvas* canvas) {
+ TRACE_EVENT0("cc", "SkPictureContentLayerUpdater::DrawPicture");
+ canvas->drawPicture(picture_);
+}
+
+void SkPictureContentLayerUpdater::SetOpaque(bool opaque) {
+ layer_is_opaque_ = opaque;
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/skpicture_content_layer_updater.h b/chromium/cc/resources/skpicture_content_layer_updater.h
new file mode 100644
index 00000000000..41a9f01e14e
--- /dev/null
+++ b/chromium/cc/resources/skpicture_content_layer_updater.h
@@ -0,0 +1,53 @@
+// Copyright 2011 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.
+
+#ifndef CC_RESOURCES_SKPICTURE_CONTENT_LAYER_UPDATER_H_
+#define CC_RESOURCES_SKPICTURE_CONTENT_LAYER_UPDATER_H_
+
+#include "cc/resources/content_layer_updater.h"
+#include "third_party/skia/include/core/SkPicture.h"
+
+class SkCanvas;
+
+namespace cc {
+
+class LayerPainter;
+
+// This class records the content_rect into an SkPicture. Subclasses, provide
+// different implementations of tile updating based on this recorded picture.
+// The BitmapSkPictureContentLayerUpdater and
+// FrameBufferSkPictureContentLayerUpdater are two examples of such
+// implementations.
+class SkPictureContentLayerUpdater : public ContentLayerUpdater {
+ public:
+ virtual void SetOpaque(bool opaque) OVERRIDE;
+
+ protected:
+ SkPictureContentLayerUpdater(
+ scoped_ptr<LayerPainter> painter,
+ RenderingStatsInstrumentation* stats_instrumentation,
+ int layer_id);
+ virtual ~SkPictureContentLayerUpdater();
+
+ virtual void PrepareToUpdate(gfx::Rect content_rect,
+ gfx::Size tile_size,
+ float contents_width_scale,
+ float contents_height_scale,
+ gfx::Rect* resulting_opaque_rect) OVERRIDE;
+ void DrawPicture(SkCanvas* canvas);
+
+ bool layer_is_opaque() const { return layer_is_opaque_; }
+
+ private:
+ // Recording canvas.
+ SkPicture picture_;
+ // True when it is known that all output pixels will be opaque.
+ bool layer_is_opaque_;
+
+ DISALLOW_COPY_AND_ASSIGN(SkPictureContentLayerUpdater);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_SKPICTURE_CONTENT_LAYER_UPDATER_H_
diff --git a/chromium/cc/resources/sync_point_helper.cc b/chromium/cc/resources/sync_point_helper.cc
new file mode 100644
index 00000000000..f5822a41ae9
--- /dev/null
+++ b/chromium/cc/resources/sync_point_helper.cc
@@ -0,0 +1,48 @@
+// Copyright 2013 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 "cc/resources/sync_point_helper.h"
+
+#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h"
+
+namespace cc {
+
+class SignalSyncPointCallbackClass
+ : public WebKit::WebGraphicsContext3D::WebGraphicsSyncPointCallback {
+ public:
+ explicit SignalSyncPointCallbackClass(const base::Closure& closure)
+ : closure_(closure) {}
+
+ virtual void onSyncPointReached() {
+ if (!closure_.is_null())
+ closure_.Run();
+ }
+
+ private:
+ base::Closure closure_;
+};
+
+void SyncPointHelper::SignalSyncPoint(
+ WebKit::WebGraphicsContext3D* context3d,
+ unsigned sync_point,
+ const base::Closure& closure) {
+ SignalSyncPointCallbackClass* callback_class =
+ new SignalSyncPointCallbackClass(closure);
+
+ // Pass ownership of the CallbackClass to WebGraphicsContext3D.
+ context3d->signalSyncPoint(sync_point, callback_class);
+}
+
+void SyncPointHelper::SignalQuery(
+ WebKit::WebGraphicsContext3D* context3d,
+ WebKit::WebGLId query,
+ const base::Closure& closure) {
+ SignalSyncPointCallbackClass* callback_class =
+ new SignalSyncPointCallbackClass(closure);
+
+ // Pass ownership of the CallbackClass to WebGraphicsContext3D.
+ context3d->signalQuery(query, callback_class);
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/sync_point_helper.h b/chromium/cc/resources/sync_point_helper.h
new file mode 100644
index 00000000000..e33a7f25364
--- /dev/null
+++ b/chromium/cc/resources/sync_point_helper.h
@@ -0,0 +1,44 @@
+// Copyright 2013 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.
+
+#ifndef CC_RESOURCES_SYNC_POINT_HELPER_H_
+#define CC_RESOURCES_SYNC_POINT_HELPER_H_
+
+#include "base/callback.h"
+#include "cc/base/cc_export.h"
+
+namespace WebKit { class WebGraphicsContext3D; }
+
+namespace cc {
+
+class CC_EXPORT SyncPointHelper {
+ public:
+ // Requests a callback to |closure| when the |sync_point| is reached by the
+ // |context3d|.
+ //
+ // If the |context3d| is destroyed or lost before the callback fires, then
+ // AbortBecauseDidLoseOrDestroyContext() must be called to clean up the
+ // callback's resources.
+ static void SignalSyncPoint(WebKit::WebGraphicsContext3D* context3d,
+ unsigned sync_point,
+ const base::Closure& closure);
+
+ // Requests a callback to |closure| when the results for |query| is available.
+ //
+ // If the |context3d| is destroyed or lost before the callback fires, then
+ // AbortBecauseDidLoseOrDestroyContext() must be called to clean up the
+ // callback's resources.
+ static void SignalQuery(WebKit::WebGraphicsContext3D* context3d,
+ unsigned int query,
+ const base::Closure& closure);
+
+ private:
+ SyncPointHelper();
+
+ DISALLOW_COPY_AND_ASSIGN(SyncPointHelper);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_SYNC_POINT_HELPER_H_
diff --git a/chromium/cc/resources/texture_mailbox.cc b/chromium/cc/resources/texture_mailbox.cc
new file mode 100644
index 00000000000..0d4787438f4
--- /dev/null
+++ b/chromium/cc/resources/texture_mailbox.cc
@@ -0,0 +1,122 @@
+// Copyright 2013 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 "cc/resources/texture_mailbox.h"
+
+#include "base/logging.h"
+#include "third_party/khronos/GLES2/gl2.h"
+
+namespace cc {
+
+TextureMailbox::TextureMailbox()
+ : target_(GL_TEXTURE_2D),
+ sync_point_(0),
+ shared_memory_(NULL) {
+}
+
+TextureMailbox::TextureMailbox(
+ const std::string& mailbox_name,
+ const ReleaseCallback& callback)
+ : callback_(callback),
+ target_(GL_TEXTURE_2D),
+ sync_point_(0),
+ shared_memory_(NULL) {
+ DCHECK(mailbox_name.empty() == callback.is_null());
+ if (!mailbox_name.empty()) {
+ CHECK(mailbox_name.size() == sizeof(name_.name));
+ name_.SetName(reinterpret_cast<const int8*>(mailbox_name.data()));
+ }
+}
+
+TextureMailbox::TextureMailbox(
+ const gpu::Mailbox& mailbox_name,
+ const ReleaseCallback& callback)
+ : callback_(callback),
+ target_(GL_TEXTURE_2D),
+ sync_point_(0),
+ shared_memory_(NULL) {
+ DCHECK(mailbox_name.IsZero() == callback.is_null());
+ name_.SetName(mailbox_name.name);
+}
+
+TextureMailbox::TextureMailbox(
+ const gpu::Mailbox& mailbox_name,
+ const ReleaseCallback& callback,
+ unsigned sync_point)
+ : callback_(callback),
+ target_(GL_TEXTURE_2D),
+ sync_point_(sync_point),
+ shared_memory_(NULL) {
+ DCHECK(mailbox_name.IsZero() == callback.is_null());
+ name_.SetName(mailbox_name.name);
+}
+
+TextureMailbox::TextureMailbox(
+ const gpu::Mailbox& mailbox_name,
+ const ReleaseCallback& callback,
+ unsigned texture_target,
+ unsigned sync_point)
+ : callback_(callback),
+ target_(texture_target),
+ sync_point_(sync_point),
+ shared_memory_(NULL) {
+ DCHECK(mailbox_name.IsZero() == callback.is_null());
+ name_.SetName(mailbox_name.name);
+}
+
+TextureMailbox::TextureMailbox(
+ base::SharedMemory* shared_memory,
+ gfx::Size size,
+ const ReleaseCallback& callback)
+ : callback_(callback),
+ target_(GL_TEXTURE_2D),
+ sync_point_(0),
+ shared_memory_(shared_memory),
+ shared_memory_size_(size) {
+}
+
+TextureMailbox::~TextureMailbox() {
+}
+
+bool TextureMailbox::Equals(const TextureMailbox& other) const {
+ if (other.IsTexture())
+ return ContainsMailbox(other.name());
+ else if (other.IsSharedMemory())
+ return ContainsHandle(other.shared_memory_->handle());
+
+ DCHECK(!other.IsValid());
+ return !IsValid();
+}
+
+bool TextureMailbox::ContainsMailbox(const gpu::Mailbox& other) const {
+ return IsTexture() && !memcmp(data(), other.name, sizeof(name_.name));
+}
+
+bool TextureMailbox::ContainsHandle(base::SharedMemoryHandle handle) const {
+ return shared_memory_ && shared_memory_->handle() == handle;
+}
+
+void TextureMailbox::RunReleaseCallback(unsigned sync_point,
+ bool lost_resource) const {
+ if (!callback_.is_null())
+ callback_.Run(sync_point, lost_resource);
+}
+
+void TextureMailbox::SetName(const gpu::Mailbox& other) {
+ DCHECK(shared_memory_ == NULL);
+ name_.SetName(other.name);
+}
+
+TextureMailbox TextureMailbox::CopyWithNewCallback(
+ const ReleaseCallback& callback) const {
+ TextureMailbox result(*this);
+ result.callback_ = callback;
+ return result;
+}
+
+size_t TextureMailbox::shared_memory_size_in_bytes() const {
+ return 4 * shared_memory_size_.GetArea();
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/texture_mailbox.h b/chromium/cc/resources/texture_mailbox.h
new file mode 100644
index 00000000000..855287df2d1
--- /dev/null
+++ b/chromium/cc/resources/texture_mailbox.h
@@ -0,0 +1,76 @@
+// Copyright 2013 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.
+
+#ifndef CC_RESOURCES_TEXTURE_MAILBOX_H_
+#define CC_RESOURCES_TEXTURE_MAILBOX_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/shared_memory.h"
+#include "cc/base/cc_export.h"
+#include "gpu/command_buffer/common/mailbox.h"
+#include "ui/gfx/size.h"
+
+namespace cc {
+
+// TODO(skaslev, danakj) Rename this class more apropriately since now it
+// can hold a shared memory resource as well as a texture mailbox.
+class CC_EXPORT TextureMailbox {
+ public:
+ typedef base::Callback<void(unsigned sync_point,
+ bool lost_resource)> ReleaseCallback;
+ TextureMailbox();
+ TextureMailbox(const std::string& mailbox_name,
+ const ReleaseCallback& callback);
+ TextureMailbox(const gpu::Mailbox& mailbox_name,
+ const ReleaseCallback& callback);
+ TextureMailbox(const gpu::Mailbox& mailbox_name,
+ const ReleaseCallback& callback,
+ unsigned sync_point);
+ TextureMailbox(const gpu::Mailbox& mailbox_name,
+ const ReleaseCallback& callback,
+ unsigned texture_target,
+ unsigned sync_point);
+ TextureMailbox(base::SharedMemory* shared_memory,
+ gfx::Size size,
+ const ReleaseCallback& callback);
+
+ ~TextureMailbox();
+
+ bool IsValid() const { return IsTexture() || IsSharedMemory(); }
+ bool IsTexture() const { return !name_.IsZero(); }
+ bool IsSharedMemory() const { return shared_memory_ != NULL; }
+
+ bool Equals(const TextureMailbox&) const;
+ bool ContainsMailbox(const gpu::Mailbox&) const;
+ bool ContainsHandle(base::SharedMemoryHandle handle) const;
+
+ const ReleaseCallback& callback() const { return callback_; }
+ const int8* data() const { return name_.name; }
+ const gpu::Mailbox& name() const { return name_; }
+ void ResetSyncPoint() { sync_point_ = 0; }
+ void RunReleaseCallback(unsigned sync_point, bool lost_resource) const;
+ void SetName(const gpu::Mailbox&);
+ unsigned target() const { return target_; }
+ unsigned sync_point() const { return sync_point_; }
+
+ base::SharedMemory* shared_memory() const { return shared_memory_; }
+ gfx::Size shared_memory_size() const { return shared_memory_size_; }
+ size_t shared_memory_size_in_bytes() const;
+
+ TextureMailbox CopyWithNewCallback(const ReleaseCallback& callback) const;
+
+ private:
+ gpu::Mailbox name_;
+ ReleaseCallback callback_;
+ unsigned target_;
+ unsigned sync_point_;
+ base::SharedMemory* shared_memory_;
+ gfx::Size shared_memory_size_;
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_TEXTURE_MAILBOX_H_
diff --git a/chromium/cc/resources/tile.cc b/chromium/cc/resources/tile.cc
new file mode 100644
index 00000000000..ab4fe7af549
--- /dev/null
+++ b/chromium/cc/resources/tile.cc
@@ -0,0 +1,65 @@
+// 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 "cc/resources/tile.h"
+
+#include "cc/base/math_util.h"
+#include "cc/debug/traced_value.h"
+#include "cc/resources/tile_manager.h"
+#include "third_party/khronos/GLES2/gl2.h"
+
+namespace cc {
+
+Tile::Id Tile::s_next_id_ = 0;
+
+Tile::Tile(TileManager* tile_manager,
+ PicturePileImpl* picture_pile,
+ gfx::Size tile_size,
+ gfx::Rect content_rect,
+ gfx::Rect opaque_rect,
+ float contents_scale,
+ int layer_id,
+ int source_frame_number,
+ bool can_use_lcd_text)
+ : tile_manager_(tile_manager),
+ tile_size_(tile_size),
+ content_rect_(content_rect),
+ contents_scale_(contents_scale),
+ opaque_rect_(opaque_rect),
+ layer_id_(layer_id),
+ source_frame_number_(source_frame_number),
+ can_use_lcd_text_(can_use_lcd_text),
+ id_(s_next_id_++) {
+ set_picture_pile(picture_pile);
+ tile_manager_->RegisterTile(this);
+}
+
+Tile::~Tile() {
+ TRACE_EVENT_OBJECT_DELETED_WITH_ID(
+ TRACE_DISABLED_BY_DEFAULT("cc.debug"), "cc::Tile", this);
+ tile_manager_->UnregisterTile(this);
+}
+
+scoped_ptr<base::Value> Tile::AsValue() const {
+ scoped_ptr<base::DictionaryValue> res(new base::DictionaryValue());
+ TracedValue::MakeDictIntoImplicitSnapshot(res.get(), "cc::Tile", this);
+ res->Set("picture_pile",
+ TracedValue::CreateIDRef(picture_pile_.get()).release());
+ res->SetDouble("contents_scale", contents_scale_);
+ res->Set("content_rect", MathUtil::AsValue(content_rect_).release());
+ res->SetInteger("layer_id", layer_id_);
+ res->Set("active_priority", priority_[ACTIVE_TREE].AsValue().release());
+ res->Set("pending_priority", priority_[PENDING_TREE].AsValue().release());
+ res->Set("managed_state", managed_state_.AsValue().release());
+ return res.PassAs<base::Value>();
+}
+
+size_t Tile::GPUMemoryUsageInBytes() const {
+ size_t total_size = 0;
+ for (int mode = 0; mode < NUM_RASTER_MODES; ++mode)
+ total_size += managed_state_.tile_versions[mode].GPUMemoryUsageInBytes();
+ return total_size;
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/tile.h b/chromium/cc/resources/tile.h
new file mode 100644
index 00000000000..be9b19a4f1b
--- /dev/null
+++ b/chromium/cc/resources/tile.h
@@ -0,0 +1,158 @@
+// 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.
+
+#ifndef CC_RESOURCES_TILE_H_
+#define CC_RESOURCES_TILE_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "cc/resources/managed_tile_state.h"
+#include "cc/resources/raster_mode.h"
+#include "cc/resources/tile_priority.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size.h"
+
+namespace cc {
+
+class PicturePileImpl;
+
+class CC_EXPORT Tile : public base::RefCounted<Tile> {
+ public:
+ typedef uint64 Id;
+
+ Tile(TileManager* tile_manager,
+ PicturePileImpl* picture_pile,
+ gfx::Size tile_size,
+ gfx::Rect content_rect,
+ gfx::Rect opaque_rect,
+ float contents_scale,
+ int layer_id,
+ int source_frame_number,
+ bool can_use_lcd_text);
+
+ Id id() const {
+ return id_;
+ }
+
+ PicturePileImpl* picture_pile() {
+ return picture_pile_.get();
+ }
+
+ const PicturePileImpl* picture_pile() const {
+ return picture_pile_.get();
+ }
+
+ const TilePriority& priority(WhichTree tree) const {
+ return priority_[tree];
+ }
+
+ TilePriority combined_priority() const {
+ return TilePriority(priority_[ACTIVE_TREE],
+ priority_[PENDING_TREE]);
+ }
+
+ void SetPriority(WhichTree tree, const TilePriority& priority) {
+ priority_[tree] = priority;
+ }
+
+ void mark_required_for_activation() {
+ priority_[PENDING_TREE].required_for_activation = true;
+ }
+
+ bool required_for_activation() const {
+ return priority_[PENDING_TREE].required_for_activation;
+ }
+
+ void set_can_use_lcd_text(bool can_use_lcd_text) {
+ can_use_lcd_text_ = can_use_lcd_text;
+ }
+
+ bool can_use_lcd_text() const {
+ return can_use_lcd_text_;
+ }
+
+ scoped_ptr<base::Value> AsValue() const;
+
+ bool IsReadyToDraw() const {
+ for (int mode = 0; mode < NUM_RASTER_MODES; ++mode) {
+ if (managed_state_.tile_versions[mode].IsReadyToDraw())
+ return true;
+ }
+ return false;
+ }
+
+ const ManagedTileState::TileVersion& GetTileVersionForDrawing() const {
+ for (int mode = 0; mode < NUM_RASTER_MODES; ++mode) {
+ if (managed_state_.tile_versions[mode].IsReadyToDraw())
+ return managed_state_.tile_versions[mode];
+ }
+ return managed_state_.tile_versions[HIGH_QUALITY_RASTER_MODE];
+ }
+
+ gfx::Rect opaque_rect() const { return opaque_rect_; }
+ bool has_text(RasterMode mode) const {
+ return managed_state_.tile_versions[mode].has_text_;
+ }
+
+ float contents_scale() const { return contents_scale_; }
+ gfx::Rect content_rect() const { return content_rect_; }
+
+ int layer_id() const { return layer_id_; }
+
+ int source_frame_number() const { return source_frame_number_; }
+
+ void set_picture_pile(scoped_refptr<PicturePileImpl> pile) {
+ DCHECK(pile->CanRaster(contents_scale_, content_rect_));
+ picture_pile_ = pile;
+ }
+
+ size_t GPUMemoryUsageInBytes() const;
+
+ RasterMode GetRasterModeForTesting() const {
+ return managed_state().raster_mode;
+ }
+ ManagedTileState::TileVersion& GetTileVersionForTesting(RasterMode mode) {
+ return managed_state_.tile_versions[mode];
+ }
+
+ private:
+ // Methods called by by tile manager.
+ friend class TileManager;
+ friend class PrioritizedTileSet;
+ friend class FakeTileManager;
+ friend class BinComparator;
+ ManagedTileState& managed_state() { return managed_state_; }
+ const ManagedTileState& managed_state() const { return managed_state_; }
+
+ inline size_t bytes_consumed_if_allocated() const {
+ return 4 * tile_size_.width() * tile_size_.height();
+ }
+
+ // Normal private methods.
+ friend class base::RefCounted<Tile>;
+ ~Tile();
+
+ TileManager* tile_manager_;
+ scoped_refptr<PicturePileImpl> picture_pile_;
+ gfx::Rect tile_size_;
+ gfx::Rect content_rect_;
+ float contents_scale_;
+ gfx::Rect opaque_rect_;
+
+ TilePriority priority_[NUM_BIN_PRIORITIES];
+ ManagedTileState managed_state_;
+ int layer_id_;
+ int source_frame_number_;
+ bool can_use_lcd_text_;
+
+ Id id_;
+ static Id s_next_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(Tile);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_TILE_H_
diff --git a/chromium/cc/resources/tile_manager.cc b/chromium/cc/resources/tile_manager.cc
new file mode 100644
index 00000000000..7cf5096ce3b
--- /dev/null
+++ b/chromium/cc/resources/tile_manager.cc
@@ -0,0 +1,847 @@
+// 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 "cc/resources/tile_manager.h"
+
+#include <algorithm>
+#include <limits>
+#include <string>
+
+#include "base/bind.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "cc/debug/devtools_instrumentation.h"
+#include "cc/debug/traced_value.h"
+#include "cc/resources/image_raster_worker_pool.h"
+#include "cc/resources/pixel_buffer_raster_worker_pool.h"
+#include "cc/resources/tile.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "ui/gfx/rect_conversions.h"
+
+namespace cc {
+
+namespace {
+
+// Memory limit policy works by mapping some bin states to the NEVER bin.
+const ManagedTileBin kBinPolicyMap[NUM_TILE_MEMORY_LIMIT_POLICIES][NUM_BINS] = {
+ { // [ALLOW_NOTHING]
+ NEVER_BIN, // [NOW_AND_READY_TO_DRAW_BIN]
+ NEVER_BIN, // [NOW_BIN]
+ NEVER_BIN, // [SOON_BIN]
+ NEVER_BIN, // [EVENTUALLY_AND_ACTIVE_BIN]
+ NEVER_BIN, // [EVENTUALLY_BIN]
+ NEVER_BIN, // [NEVER_AND_ACTIVE_BIN]
+ NEVER_BIN // [NEVER_BIN]
+ }, { // [ALLOW_ABSOLUTE_MINIMUM]
+ NOW_AND_READY_TO_DRAW_BIN, // [NOW_AND_READY_TO_DRAW_BIN]
+ NOW_BIN, // [NOW_BIN]
+ NEVER_BIN, // [SOON_BIN]
+ NEVER_BIN, // [EVENTUALLY_AND_ACTIVE_BIN]
+ NEVER_BIN, // [EVENTUALLY_BIN]
+ NEVER_BIN, // [NEVER_AND_ACTIVE_BIN]
+ NEVER_BIN // [NEVER_BIN]
+ }, { // [ALLOW_PREPAINT_ONLY]
+ NOW_AND_READY_TO_DRAW_BIN, // [NOW_AND_READY_TO_DRAW_BIN]
+ NOW_BIN, // [NOW_BIN]
+ SOON_BIN, // [SOON_BIN]
+ NEVER_BIN, // [EVENTUALLY_AND_ACTIVE_BIN]
+ NEVER_BIN, // [EVENTUALLY_BIN]
+ NEVER_BIN, // [NEVER_AND_ACTIVE_BIN]
+ NEVER_BIN // [NEVER_BIN]
+ }, { // [ALLOW_ANYTHING]
+ NOW_AND_READY_TO_DRAW_BIN, // [NOW_AND_READY_TO_DRAW_BIN]
+ NOW_BIN, // [NOW_BIN]
+ SOON_BIN, // [SOON_BIN]
+ EVENTUALLY_AND_ACTIVE_BIN, // [EVENTUALLY_AND_ACTIVE_BIN]
+ EVENTUALLY_BIN, // [EVENTUALLY_BIN]
+ NEVER_AND_ACTIVE_BIN, // [NEVER_AND_ACTIVE_BIN]
+ NEVER_BIN // [NEVER_BIN]
+ }
+};
+
+// Determine bin based on three categories of tiles: things we need now,
+// things we need soon, and eventually.
+inline ManagedTileBin BinFromTilePriority(const TilePriority& prio,
+ TreePriority tree_priority,
+ bool is_ready_to_draw,
+ bool is_active) {
+ // The amount of time for which we want to have prepainting coverage.
+ const float kPrepaintingWindowTimeSeconds = 1.0f;
+ const float kBackflingGuardDistancePixels = 314.0f;
+
+ // Don't let low res tiles be in the now bin unless we're in a mode where
+ // we're prioritizing checkerboard prevention.
+ bool can_be_in_now_bin = tree_priority == SMOOTHNESS_TAKES_PRIORITY ||
+ prio.resolution != LOW_RESOLUTION;
+
+ if (prio.distance_to_visible_in_pixels ==
+ std::numeric_limits<float>::infinity())
+ return is_active ? NEVER_AND_ACTIVE_BIN : NEVER_BIN;
+
+ if (can_be_in_now_bin && prio.time_to_visible_in_seconds == 0)
+ return is_ready_to_draw ? NOW_AND_READY_TO_DRAW_BIN : NOW_BIN;
+
+ if (prio.resolution == NON_IDEAL_RESOLUTION)
+ return is_active ? EVENTUALLY_AND_ACTIVE_BIN : EVENTUALLY_BIN;
+
+ if (prio.distance_to_visible_in_pixels < kBackflingGuardDistancePixels ||
+ prio.time_to_visible_in_seconds < kPrepaintingWindowTimeSeconds)
+ return SOON_BIN;
+
+ return is_active ? EVENTUALLY_AND_ACTIVE_BIN : EVENTUALLY_BIN;
+}
+
+// Limit to the number of raster tasks that can be scheduled.
+// This is high enough to not cause unnecessary scheduling but
+// gives us an insurance that we're not spending a huge amount
+// of time scheduling one enormous set of tasks.
+const size_t kMaxRasterTasks = 256u;
+
+} // namespace
+
+RasterTaskCompletionStats::RasterTaskCompletionStats()
+ : completed_count(0u),
+ canceled_count(0u) {
+}
+
+scoped_ptr<base::Value> RasterTaskCompletionStatsAsValue(
+ const RasterTaskCompletionStats& stats) {
+ scoped_ptr<base::DictionaryValue> state(new base::DictionaryValue());
+ state->SetInteger("completed_count", stats.completed_count);
+ state->SetInteger("canceled_count", stats.canceled_count);
+ return state.PassAs<base::Value>();
+}
+
+// static
+scoped_ptr<TileManager> TileManager::Create(
+ TileManagerClient* client,
+ ResourceProvider* resource_provider,
+ size_t num_raster_threads,
+ RenderingStatsInstrumentation* rendering_stats_instrumentation,
+ bool use_map_image) {
+ return make_scoped_ptr(
+ new TileManager(client,
+ resource_provider,
+ use_map_image ?
+ ImageRasterWorkerPool::Create(
+ resource_provider, num_raster_threads) :
+ PixelBufferRasterWorkerPool::Create(
+ resource_provider, num_raster_threads),
+ num_raster_threads,
+ rendering_stats_instrumentation,
+ resource_provider->best_texture_format()));
+}
+
+TileManager::TileManager(
+ TileManagerClient* client,
+ ResourceProvider* resource_provider,
+ scoped_ptr<RasterWorkerPool> raster_worker_pool,
+ size_t num_raster_threads,
+ RenderingStatsInstrumentation* rendering_stats_instrumentation,
+ GLenum texture_format)
+ : client_(client),
+ resource_pool_(ResourcePool::Create(resource_provider)),
+ raster_worker_pool_(raster_worker_pool.Pass()),
+ all_tiles_that_need_to_be_rasterized_have_memory_(true),
+ all_tiles_required_for_activation_have_memory_(true),
+ all_tiles_required_for_activation_have_been_initialized_(true),
+ ever_exceeded_memory_budget_(false),
+ rendering_stats_instrumentation_(rendering_stats_instrumentation),
+ did_initialize_visible_tile_(false),
+ texture_format_(texture_format) {
+ raster_worker_pool_->SetClient(this);
+}
+
+TileManager::~TileManager() {
+ // Reset global state and manage. This should cause
+ // our memory usage to drop to zero.
+ global_state_ = GlobalStateThatImpactsTilePriority();
+
+ // Clear |prioritized_tiles_| so that tiles kept alive by it can be freed.
+ prioritized_tiles_.Clear();
+ DCHECK_EQ(0u, tiles_.size());
+
+ TileVector empty;
+ ScheduleTasks(empty);
+
+ // This should finish all pending tasks and release any uninitialized
+ // resources.
+ raster_worker_pool_->Shutdown();
+ raster_worker_pool_->CheckForCompletedTasks();
+}
+
+void TileManager::SetGlobalState(
+ const GlobalStateThatImpactsTilePriority& global_state) {
+ global_state_ = global_state;
+ resource_pool_->SetMemoryUsageLimits(
+ global_state_.memory_limit_in_bytes,
+ global_state_.unused_memory_limit_in_bytes,
+ global_state_.num_resources_limit);
+}
+
+void TileManager::RegisterTile(Tile* tile) {
+ DCHECK(!tile->required_for_activation());
+ DCHECK(tiles_.find(tile->id()) == tiles_.end());
+
+ tiles_[tile->id()] = tile;
+}
+
+void TileManager::UnregisterTile(Tile* tile) {
+ FreeResourcesForTile(tile);
+
+ DCHECK(tiles_.find(tile->id()) != tiles_.end());
+ tiles_.erase(tile->id());
+}
+
+bool TileManager::ShouldForceTasksRequiredForActivationToComplete() const {
+ return GlobalState().tree_priority != SMOOTHNESS_TAKES_PRIORITY;
+}
+
+void TileManager::DidFinishRunningTasks() {
+ TRACE_EVENT0("cc", "TileManager::DidFinishRunningTasks");
+
+ // When OOM, keep re-assigning memory until we reach a steady state
+ // where top-priority tiles are initialized.
+ if (all_tiles_that_need_to_be_rasterized_have_memory_)
+ return;
+
+ raster_worker_pool_->CheckForCompletedTasks();
+
+ TileVector tiles_that_need_to_be_rasterized;
+ AssignGpuMemoryToTiles(&prioritized_tiles_,
+ &tiles_that_need_to_be_rasterized);
+
+ // |tiles_that_need_to_be_rasterized| will be empty when we reach a
+ // steady memory state. Keep scheduling tasks until we reach this state.
+ if (!tiles_that_need_to_be_rasterized.empty()) {
+ ScheduleTasks(tiles_that_need_to_be_rasterized);
+ return;
+ }
+
+ // Use on-demand raster for any required-for-activation tiles that have not
+ // been been assigned memory after reaching a steady memory state. This
+ // ensures that we activate even when OOM.
+ for (TileMap::iterator it = tiles_.begin(); it != tiles_.end(); ++it) {
+ Tile* tile = it->second;
+ ManagedTileState& mts = tile->managed_state();
+ ManagedTileState::TileVersion& tile_version =
+ mts.tile_versions[mts.raster_mode];
+
+ if (tile->required_for_activation() && !tile_version.IsReadyToDraw())
+ tile_version.set_rasterize_on_demand();
+ }
+
+ client_->NotifyReadyToActivate();
+}
+
+void TileManager::DidFinishRunningTasksRequiredForActivation() {
+ // This is only a true indication that all tiles required for
+ // activation are initialized when no tiles are OOM. We need to
+ // wait for DidFinishRunningTasks() to be called, try to re-assign
+ // memory and in worst case use on-demand raster when tiles
+ // required for activation are OOM.
+ if (!all_tiles_required_for_activation_have_memory_)
+ return;
+
+ client_->NotifyReadyToActivate();
+}
+
+void TileManager::GetTilesWithAssignedBins(PrioritizedTileSet* tiles) {
+ TRACE_EVENT0("cc", "TileManager::GetTilesWithAssignedBins");
+
+ const TileMemoryLimitPolicy memory_policy = global_state_.memory_limit_policy;
+ const TreePriority tree_priority = global_state_.tree_priority;
+
+ // For each tree, bin into different categories of tiles.
+ for (TileMap::const_iterator it = tiles_.begin(); it != tiles_.end(); ++it) {
+ Tile* tile = it->second;
+ ManagedTileState& mts = tile->managed_state();
+
+ TilePriority prio[NUM_BIN_PRIORITIES];
+ switch (tree_priority) {
+ case SAME_PRIORITY_FOR_BOTH_TREES:
+ prio[HIGH_PRIORITY_BIN] = prio[LOW_PRIORITY_BIN] =
+ tile->combined_priority();
+ break;
+ case SMOOTHNESS_TAKES_PRIORITY:
+ prio[HIGH_PRIORITY_BIN] = tile->priority(ACTIVE_TREE);
+ prio[LOW_PRIORITY_BIN] = tile->priority(PENDING_TREE);
+ break;
+ case NEW_CONTENT_TAKES_PRIORITY:
+ prio[HIGH_PRIORITY_BIN] = tile->priority(PENDING_TREE);
+ prio[LOW_PRIORITY_BIN] = tile->priority(ACTIVE_TREE);
+ break;
+ }
+
+ bool tile_is_ready_to_draw = tile->IsReadyToDraw();
+ bool tile_is_active =
+ tile_is_ready_to_draw ||
+ !mts.tile_versions[mts.raster_mode].raster_task_.is_null();
+
+ mts.resolution = prio[HIGH_PRIORITY_BIN].resolution;
+ mts.time_to_needed_in_seconds =
+ prio[HIGH_PRIORITY_BIN].time_to_visible_in_seconds;
+ mts.distance_to_visible_in_pixels =
+ prio[HIGH_PRIORITY_BIN].distance_to_visible_in_pixels;
+ mts.required_for_activation =
+ prio[HIGH_PRIORITY_BIN].required_for_activation;
+
+ mts.bin[HIGH_PRIORITY_BIN] =
+ BinFromTilePriority(prio[HIGH_PRIORITY_BIN],
+ tree_priority,
+ tile_is_ready_to_draw,
+ tile_is_active);
+ mts.bin[LOW_PRIORITY_BIN] =
+ BinFromTilePriority(prio[LOW_PRIORITY_BIN],
+ tree_priority,
+ tile_is_ready_to_draw,
+ tile_is_active);
+ mts.gpu_memmgr_stats_bin =
+ BinFromTilePriority(tile->combined_priority(),
+ tree_priority,
+ tile_is_ready_to_draw,
+ tile_is_active);
+
+ ManagedTileBin active_bin =
+ BinFromTilePriority(tile->priority(ACTIVE_TREE),
+ tree_priority,
+ tile_is_ready_to_draw,
+ tile_is_active);
+ mts.tree_bin[ACTIVE_TREE] = kBinPolicyMap[memory_policy][active_bin];
+
+ ManagedTileBin pending_bin =
+ BinFromTilePriority(tile->priority(PENDING_TREE),
+ tree_priority,
+ tile_is_ready_to_draw,
+ tile_is_active);
+ mts.tree_bin[PENDING_TREE] = kBinPolicyMap[memory_policy][pending_bin];
+
+ for (int i = 0; i < NUM_BIN_PRIORITIES; ++i)
+ mts.bin[i] = kBinPolicyMap[memory_policy][mts.bin[i]];
+
+ mts.visible_and_ready_to_draw =
+ mts.tree_bin[ACTIVE_TREE] == NOW_AND_READY_TO_DRAW_BIN;
+
+ if (mts.is_in_never_bin_on_both_trees()) {
+ FreeResourcesForTile(tile);
+ continue;
+ }
+
+ // Note that if the tile is visible_and_ready_to_draw, then we always want
+ // the priority to be NOW_AND_READY_TO_DRAW_BIN, even if HIGH_PRIORITY_BIN
+ // is something different. The reason for this is that if we're prioritizing
+ // the pending tree, we still want visible tiles to take the highest
+ // priority.
+ ManagedTileBin priority_bin = mts.visible_and_ready_to_draw
+ ? NOW_AND_READY_TO_DRAW_BIN
+ : mts.bin[HIGH_PRIORITY_BIN];
+
+ // Insert the tile into a priority set.
+ tiles->InsertTile(tile, priority_bin);
+ }
+}
+
+void TileManager::GetPrioritizedTileSet(PrioritizedTileSet* tiles) {
+ TRACE_EVENT0("cc", "TileManager::GetPrioritizedTileSet");
+
+ GetTilesWithAssignedBins(tiles);
+}
+
+void TileManager::ManageTiles() {
+ TRACE_EVENT0("cc", "TileManager::ManageTiles");
+
+ // Clear |prioritized_tiles_| so that tiles kept alive by it can be freed.
+ prioritized_tiles_.Clear();
+
+ GetPrioritizedTileSet(&prioritized_tiles_);
+
+ TileVector tiles_that_need_to_be_rasterized;
+ AssignGpuMemoryToTiles(&prioritized_tiles_,
+ &tiles_that_need_to_be_rasterized);
+ CleanUpUnusedImageDecodeTasks();
+
+ // Finally, schedule rasterizer tasks.
+ ScheduleTasks(tiles_that_need_to_be_rasterized);
+
+ TRACE_EVENT_INSTANT1(
+ "cc", "DidManage", TRACE_EVENT_SCOPE_THREAD,
+ "state", TracedValue::FromValue(BasicStateAsValue().release()));
+
+ TRACE_COUNTER_ID1("cc", "unused_memory_bytes", this,
+ resource_pool_->total_memory_usage_bytes() -
+ resource_pool_->acquired_memory_usage_bytes());
+}
+
+bool TileManager::UpdateVisibleTiles() {
+ TRACE_EVENT0("cc", "TileManager::UpdateVisibleTiles");
+
+ raster_worker_pool_->CheckForCompletedTasks();
+
+ TRACE_EVENT_INSTANT1(
+ "cc", "DidUpdateVisibleTiles", TRACE_EVENT_SCOPE_THREAD,
+ "stats", TracedValue::FromValue(
+ RasterTaskCompletionStatsAsValue(
+ update_visible_tiles_stats_).release()));
+ update_visible_tiles_stats_ = RasterTaskCompletionStats();
+
+ bool did_initialize_visible_tile = did_initialize_visible_tile_;
+ did_initialize_visible_tile_ = false;
+ return did_initialize_visible_tile;
+}
+
+void TileManager::GetMemoryStats(
+ size_t* memory_required_bytes,
+ size_t* memory_nice_to_have_bytes,
+ size_t* memory_allocated_bytes,
+ size_t* memory_used_bytes) const {
+ *memory_required_bytes = 0;
+ *memory_nice_to_have_bytes = 0;
+ *memory_allocated_bytes = resource_pool_->total_memory_usage_bytes();
+ *memory_used_bytes = resource_pool_->acquired_memory_usage_bytes();
+ for (TileMap::const_iterator it = tiles_.begin();
+ it != tiles_.end();
+ ++it) {
+ const Tile* tile = it->second;
+ const ManagedTileState& mts = tile->managed_state();
+
+ const ManagedTileState::TileVersion& tile_version =
+ tile->GetTileVersionForDrawing();
+ if (tile_version.IsReadyToDraw() &&
+ !tile_version.requires_resource())
+ continue;
+
+ size_t tile_bytes = tile->bytes_consumed_if_allocated();
+ if ((mts.gpu_memmgr_stats_bin == NOW_BIN) ||
+ (mts.gpu_memmgr_stats_bin == NOW_AND_READY_TO_DRAW_BIN))
+ *memory_required_bytes += tile_bytes;
+ if (mts.gpu_memmgr_stats_bin != NEVER_BIN)
+ *memory_nice_to_have_bytes += tile_bytes;
+ }
+}
+
+scoped_ptr<base::Value> TileManager::BasicStateAsValue() const {
+ scoped_ptr<base::DictionaryValue> state(new base::DictionaryValue());
+ state->SetInteger("tile_count", tiles_.size());
+ state->Set("global_state", global_state_.AsValue().release());
+ state->Set("memory_requirements", GetMemoryRequirementsAsValue().release());
+ return state.PassAs<base::Value>();
+}
+
+scoped_ptr<base::Value> TileManager::AllTilesAsValue() const {
+ scoped_ptr<base::ListValue> state(new base::ListValue());
+ for (TileMap::const_iterator it = tiles_.begin();
+ it != tiles_.end();
+ it++) {
+ state->Append(it->second->AsValue().release());
+ }
+ return state.PassAs<base::Value>();
+}
+
+scoped_ptr<base::Value> TileManager::GetMemoryRequirementsAsValue() const {
+ scoped_ptr<base::DictionaryValue> requirements(
+ new base::DictionaryValue());
+
+ size_t memory_required_bytes;
+ size_t memory_nice_to_have_bytes;
+ size_t memory_allocated_bytes;
+ size_t memory_used_bytes;
+ GetMemoryStats(&memory_required_bytes,
+ &memory_nice_to_have_bytes,
+ &memory_allocated_bytes,
+ &memory_used_bytes);
+ requirements->SetInteger("memory_required_bytes", memory_required_bytes);
+ requirements->SetInteger("memory_nice_to_have_bytes",
+ memory_nice_to_have_bytes);
+ requirements->SetInteger("memory_allocated_bytes", memory_allocated_bytes);
+ requirements->SetInteger("memory_used_bytes", memory_used_bytes);
+ return requirements.PassAs<base::Value>();
+}
+
+RasterMode TileManager::DetermineRasterMode(const Tile* tile) const {
+ DCHECK(tile);
+ DCHECK(tile->picture_pile());
+
+ const ManagedTileState& mts = tile->managed_state();
+ RasterMode current_mode = mts.raster_mode;
+
+ RasterMode raster_mode = HIGH_QUALITY_RASTER_MODE;
+ if (tile->managed_state().resolution == LOW_RESOLUTION)
+ raster_mode = LOW_QUALITY_RASTER_MODE;
+ else if (tile->can_use_lcd_text())
+ raster_mode = HIGH_QUALITY_RASTER_MODE;
+ else if (mts.tile_versions[current_mode].has_text_ ||
+ !mts.tile_versions[current_mode].IsReadyToDraw())
+ raster_mode = HIGH_QUALITY_NO_LCD_RASTER_MODE;
+
+ return std::min(raster_mode, current_mode);
+}
+
+void TileManager::AssignGpuMemoryToTiles(
+ PrioritizedTileSet* tiles,
+ TileVector* tiles_that_need_to_be_rasterized) {
+ TRACE_EVENT0("cc", "TileManager::AssignGpuMemoryToTiles");
+
+ // Now give memory out to the tiles until we're out, and build
+ // the needs-to-be-rasterized queue.
+ size_t bytes_releasable = 0;
+ size_t resources_releasable = 0;
+ for (PrioritizedTileSet::Iterator it(tiles, false);
+ it;
+ ++it) {
+ const Tile* tile = *it;
+ const ManagedTileState& mts = tile->managed_state();
+ for (int mode = 0; mode < NUM_RASTER_MODES; ++mode) {
+ if (mts.tile_versions[mode].resource_) {
+ bytes_releasable += tile->bytes_consumed_if_allocated();
+ resources_releasable++;
+ }
+ }
+ }
+
+ all_tiles_that_need_to_be_rasterized_have_memory_ = true;
+ all_tiles_required_for_activation_have_memory_ = true;
+ all_tiles_required_for_activation_have_been_initialized_ = true;
+
+ // Cast to prevent overflow.
+ int64 bytes_available =
+ static_cast<int64>(bytes_releasable) +
+ static_cast<int64>(global_state_.memory_limit_in_bytes) -
+ static_cast<int64>(resource_pool_->acquired_memory_usage_bytes());
+ int resources_available = resources_releasable +
+ global_state_.num_resources_limit -
+ resource_pool_->NumResources();
+
+ size_t bytes_allocatable =
+ std::max(static_cast<int64>(0), bytes_available);
+ size_t resources_allocatable = std::max(0, resources_available);
+
+ size_t bytes_that_exceeded_memory_budget = 0;
+ size_t bytes_left = bytes_allocatable;
+ size_t resources_left = resources_allocatable;
+ bool oomed = false;
+
+ unsigned schedule_priority = 1u;
+ for (PrioritizedTileSet::Iterator it(tiles, true);
+ it;
+ ++it) {
+ Tile* tile = *it;
+ ManagedTileState& mts = tile->managed_state();
+
+ mts.scheduled_priority = schedule_priority++;
+
+ mts.raster_mode = DetermineRasterMode(tile);
+
+ ManagedTileState::TileVersion& tile_version =
+ mts.tile_versions[mts.raster_mode];
+
+ // If this tile doesn't need a resource, then nothing to do.
+ if (!tile_version.requires_resource())
+ continue;
+
+ // If the tile is not needed, free it up.
+ if (mts.is_in_never_bin_on_both_trees()) {
+ FreeResourcesForTile(tile);
+ continue;
+ }
+
+ size_t tile_bytes = 0;
+ size_t tile_resources = 0;
+
+ // It costs to maintain a resource.
+ for (int mode = 0; mode < NUM_RASTER_MODES; ++mode) {
+ if (mts.tile_versions[mode].resource_) {
+ tile_bytes += tile->bytes_consumed_if_allocated();
+ tile_resources++;
+ }
+ }
+
+ // Allow lower priority tiles with initialized resources to keep
+ // their memory by only assigning memory to new raster tasks if
+ // they can be scheduled.
+ if (tiles_that_need_to_be_rasterized->size() < kMaxRasterTasks) {
+ // If we don't have the required version, and it's not in flight
+ // then we'll have to pay to create a new task.
+ if (!tile_version.resource_ && tile_version.raster_task_.is_null()) {
+ tile_bytes += tile->bytes_consumed_if_allocated();
+ tile_resources++;
+ }
+ }
+
+ // Tile is OOM.
+ if (tile_bytes > bytes_left || tile_resources > resources_left) {
+ FreeResourcesForTile(tile);
+
+ // This tile was already on screen and now its resources have been
+ // released. In order to prevent checkerboarding, set this tile as
+ // rasterize on demand immediately.
+ if (mts.visible_and_ready_to_draw)
+ tile_version.set_rasterize_on_demand();
+
+ oomed = true;
+ bytes_that_exceeded_memory_budget += tile_bytes;
+ } else {
+ bytes_left -= tile_bytes;
+ resources_left -= tile_resources;
+
+ if (tile_version.resource_)
+ continue;
+ }
+
+ DCHECK(!tile_version.resource_);
+
+ if (tile->required_for_activation())
+ all_tiles_required_for_activation_have_been_initialized_ = false;
+
+ // Tile shouldn't be rasterized if |tiles_that_need_to_be_rasterized|
+ // has reached it's limit or we've failed to assign gpu memory to this
+ // or any higher priority tile. Preventing tiles that fit into memory
+ // budget to be rasterized when higher priority tile is oom is
+ // important for two reasons:
+ // 1. Tile size should not impact raster priority.
+ // 2. Tiles with existing raster task could otherwise incorrectly
+ // be added as they are not affected by |bytes_allocatable|.
+ if (oomed || tiles_that_need_to_be_rasterized->size() >= kMaxRasterTasks) {
+ all_tiles_that_need_to_be_rasterized_have_memory_ = false;
+ if (tile->required_for_activation())
+ all_tiles_required_for_activation_have_memory_ = false;
+ it.DisablePriorityOrdering();
+ continue;
+ }
+
+ tiles_that_need_to_be_rasterized->push_back(tile);
+ }
+
+ ever_exceeded_memory_budget_ |= bytes_that_exceeded_memory_budget > 0;
+ if (ever_exceeded_memory_budget_) {
+ TRACE_COUNTER_ID2("cc", "over_memory_budget", this,
+ "budget", global_state_.memory_limit_in_bytes,
+ "over", bytes_that_exceeded_memory_budget);
+ }
+ memory_stats_from_last_assign_.total_budget_in_bytes =
+ global_state_.memory_limit_in_bytes;
+ memory_stats_from_last_assign_.bytes_allocated =
+ bytes_allocatable - bytes_left;
+ memory_stats_from_last_assign_.bytes_unreleasable =
+ bytes_allocatable - bytes_releasable;
+ memory_stats_from_last_assign_.bytes_over =
+ bytes_that_exceeded_memory_budget;
+}
+
+void TileManager::CleanUpUnusedImageDecodeTasks() {
+ // Calculate a set of layers that are used by at least one tile.
+ base::hash_set<int> used_layers;
+ for (TileMap::iterator it = tiles_.begin(); it != tiles_.end(); ++it)
+ used_layers.insert(it->second->layer_id());
+
+ // Now calculate the set of layers in |image_decode_tasks_| that are not used
+ // by any tile.
+ std::vector<int> unused_layers;
+ for (LayerPixelRefTaskMap::iterator it = image_decode_tasks_.begin();
+ it != image_decode_tasks_.end();
+ ++it) {
+ if (used_layers.find(it->first) == used_layers.end())
+ unused_layers.push_back(it->first);
+ }
+
+ // Erase unused layers from |image_decode_tasks_|.
+ for (std::vector<int>::iterator it = unused_layers.begin();
+ it != unused_layers.end();
+ ++it) {
+ image_decode_tasks_.erase(*it);
+ }
+}
+
+void TileManager::FreeResourceForTile(Tile* tile, RasterMode mode) {
+ ManagedTileState& mts = tile->managed_state();
+ if (mts.tile_versions[mode].resource_) {
+ resource_pool_->ReleaseResource(
+ mts.tile_versions[mode].resource_.Pass());
+ }
+}
+
+void TileManager::FreeResourcesForTile(Tile* tile) {
+ for (int mode = 0; mode < NUM_RASTER_MODES; ++mode) {
+ FreeResourceForTile(tile, static_cast<RasterMode>(mode));
+ }
+}
+
+void TileManager::FreeUnusedResourcesForTile(Tile* tile) {
+ DCHECK(tile->IsReadyToDraw());
+ ManagedTileState& mts = tile->managed_state();
+ RasterMode used_mode = HIGH_QUALITY_NO_LCD_RASTER_MODE;
+ for (int mode = 0; mode < NUM_RASTER_MODES; ++mode) {
+ if (mts.tile_versions[mode].IsReadyToDraw()) {
+ used_mode = static_cast<RasterMode>(mode);
+ break;
+ }
+ }
+
+ for (int mode = 0; mode < NUM_RASTER_MODES; ++mode) {
+ if (mode != used_mode)
+ FreeResourceForTile(tile, static_cast<RasterMode>(mode));
+ }
+}
+
+void TileManager::ScheduleTasks(
+ const TileVector& tiles_that_need_to_be_rasterized) {
+ TRACE_EVENT1("cc", "TileManager::ScheduleTasks",
+ "count", tiles_that_need_to_be_rasterized.size());
+ RasterWorkerPool::RasterTask::Queue tasks;
+
+ // Build a new task queue containing all task currently needed. Tasks
+ // are added in order of priority, highest priority task first.
+ for (TileVector::const_iterator it = tiles_that_need_to_be_rasterized.begin();
+ it != tiles_that_need_to_be_rasterized.end();
+ ++it) {
+ Tile* tile = *it;
+ ManagedTileState& mts = tile->managed_state();
+ ManagedTileState::TileVersion& tile_version =
+ mts.tile_versions[mts.raster_mode];
+
+ DCHECK(tile_version.requires_resource());
+ DCHECK(!tile_version.resource_);
+
+ if (tile_version.raster_task_.is_null())
+ tile_version.raster_task_ = CreateRasterTask(tile);
+
+ tasks.Append(tile_version.raster_task_, tile->required_for_activation());
+ }
+
+ // Schedule running of |tasks|. This replaces any previously
+ // scheduled tasks and effectively cancels all tasks not present
+ // in |tasks|.
+ raster_worker_pool_->ScheduleTasks(&tasks);
+}
+
+RasterWorkerPool::Task TileManager::CreateImageDecodeTask(
+ Tile* tile, skia::LazyPixelRef* pixel_ref) {
+ return RasterWorkerPool::CreateImageDecodeTask(
+ pixel_ref,
+ tile->layer_id(),
+ rendering_stats_instrumentation_,
+ base::Bind(&TileManager::OnImageDecodeTaskCompleted,
+ base::Unretained(this),
+ tile->layer_id(),
+ base::Unretained(pixel_ref)));
+}
+
+RasterWorkerPool::RasterTask TileManager::CreateRasterTask(Tile* tile) {
+ ManagedTileState& mts = tile->managed_state();
+
+ scoped_ptr<ResourcePool::Resource> resource =
+ resource_pool_->AcquireResource(tile->tile_size_.size(),
+ texture_format_);
+ const Resource* const_resource = resource.get();
+
+ // Create and queue all image decode tasks that this tile depends on.
+ RasterWorkerPool::Task::Set decode_tasks;
+ PixelRefTaskMap& existing_pixel_refs = image_decode_tasks_[tile->layer_id()];
+ for (PicturePileImpl::PixelRefIterator iter(tile->content_rect(),
+ tile->contents_scale(),
+ tile->picture_pile());
+ iter; ++iter) {
+ skia::LazyPixelRef* pixel_ref = *iter;
+ uint32_t id = pixel_ref->getGenerationID();
+
+ // Append existing image decode task if available.
+ PixelRefTaskMap::iterator decode_task_it = existing_pixel_refs.find(id);
+ if (decode_task_it != existing_pixel_refs.end()) {
+ decode_tasks.Insert(decode_task_it->second);
+ continue;
+ }
+
+ // Create and append new image decode task for this pixel ref.
+ RasterWorkerPool::Task decode_task = CreateImageDecodeTask(
+ tile, pixel_ref);
+ decode_tasks.Insert(decode_task);
+ existing_pixel_refs[id] = decode_task;
+ }
+
+ return RasterWorkerPool::CreateRasterTask(
+ const_resource,
+ tile->picture_pile(),
+ tile->content_rect(),
+ tile->contents_scale(),
+ mts.raster_mode,
+ mts.tree_bin[PENDING_TREE] == NOW_BIN,
+ mts.resolution,
+ tile->layer_id(),
+ static_cast<const void *>(tile),
+ tile->source_frame_number(),
+ rendering_stats_instrumentation_,
+ base::Bind(&TileManager::OnRasterTaskCompleted,
+ base::Unretained(this),
+ tile->id(),
+ base::Passed(&resource),
+ mts.raster_mode),
+ &decode_tasks);
+}
+
+void TileManager::OnImageDecodeTaskCompleted(
+ int layer_id,
+ skia::LazyPixelRef* pixel_ref,
+ bool was_canceled) {
+ // If the task was canceled, we need to clean it up
+ // from |image_decode_tasks_|.
+ if (!was_canceled)
+ return;
+
+ LayerPixelRefTaskMap::iterator layer_it =
+ image_decode_tasks_.find(layer_id);
+
+ if (layer_it == image_decode_tasks_.end())
+ return;
+
+ PixelRefTaskMap& pixel_ref_tasks = layer_it->second;
+ PixelRefTaskMap::iterator task_it =
+ pixel_ref_tasks.find(pixel_ref->getGenerationID());
+
+ if (task_it != pixel_ref_tasks.end())
+ pixel_ref_tasks.erase(task_it);
+}
+
+void TileManager::OnRasterTaskCompleted(
+ Tile::Id tile_id,
+ scoped_ptr<ResourcePool::Resource> resource,
+ RasterMode raster_mode,
+ const PicturePileImpl::Analysis& analysis,
+ bool was_canceled) {
+ TileMap::iterator it = tiles_.find(tile_id);
+ if (it == tiles_.end()) {
+ ++update_visible_tiles_stats_.canceled_count;
+ resource_pool_->ReleaseResource(resource.Pass());
+ return;
+ }
+
+ Tile* tile = it->second;
+ ManagedTileState& mts = tile->managed_state();
+ ManagedTileState::TileVersion& tile_version =
+ mts.tile_versions[raster_mode];
+ DCHECK(!tile_version.raster_task_.is_null());
+ tile_version.raster_task_.Reset();
+
+ if (was_canceled) {
+ ++update_visible_tiles_stats_.canceled_count;
+ resource_pool_->ReleaseResource(resource.Pass());
+ return;
+ }
+
+ ++update_visible_tiles_stats_.completed_count;
+
+ tile_version.set_has_text(analysis.has_text);
+ if (analysis.is_solid_color) {
+ tile_version.set_solid_color(analysis.solid_color);
+ resource_pool_->ReleaseResource(resource.Pass());
+ } else {
+ tile_version.set_use_resource();
+ tile_version.resource_ = resource.Pass();
+ }
+
+ FreeUnusedResourcesForTile(tile);
+ if (tile->priority(ACTIVE_TREE).distance_to_visible_in_pixels == 0)
+ did_initialize_visible_tile_ = true;
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/tile_manager.h b/chromium/cc/resources/tile_manager.h
new file mode 100644
index 00000000000..7957fc823f2
--- /dev/null
+++ b/chromium/cc/resources/tile_manager.h
@@ -0,0 +1,171 @@
+// 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.
+
+#ifndef CC_RESOURCES_TILE_MANAGER_H_
+#define CC_RESOURCES_TILE_MANAGER_H_
+
+#include <queue>
+#include <set>
+#include <vector>
+
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "cc/debug/rendering_stats_instrumentation.h"
+#include "cc/resources/managed_tile_state.h"
+#include "cc/resources/memory_history.h"
+#include "cc/resources/picture_pile_impl.h"
+#include "cc/resources/prioritized_tile_set.h"
+#include "cc/resources/raster_worker_pool.h"
+#include "cc/resources/resource_pool.h"
+#include "cc/resources/tile.h"
+
+namespace cc {
+class ResourceProvider;
+
+class CC_EXPORT TileManagerClient {
+ public:
+ virtual void NotifyReadyToActivate() = 0;
+
+ protected:
+ virtual ~TileManagerClient() {}
+};
+
+struct RasterTaskCompletionStats {
+ RasterTaskCompletionStats();
+
+ size_t completed_count;
+ size_t canceled_count;
+};
+scoped_ptr<base::Value> RasterTaskCompletionStatsAsValue(
+ const RasterTaskCompletionStats& stats);
+
+// This class manages tiles, deciding which should get rasterized and which
+// should no longer have any memory assigned to them. Tile objects are "owned"
+// by layers; they automatically register with the manager when they are
+// created, and unregister from the manager when they are deleted.
+class CC_EXPORT TileManager : public RasterWorkerPoolClient {
+ public:
+ static scoped_ptr<TileManager> Create(
+ TileManagerClient* client,
+ ResourceProvider* resource_provider,
+ size_t num_raster_threads,
+ RenderingStatsInstrumentation* rendering_stats_instrumentation,
+ bool use_map_image);
+ virtual ~TileManager();
+
+ const GlobalStateThatImpactsTilePriority& GlobalState() const {
+ return global_state_;
+ }
+ void SetGlobalState(const GlobalStateThatImpactsTilePriority& state);
+
+ void ManageTiles();
+
+ // Returns true when visible tiles have been initialized.
+ bool UpdateVisibleTiles();
+
+ scoped_ptr<base::Value> BasicStateAsValue() const;
+ scoped_ptr<base::Value> AllTilesAsValue() const;
+ void GetMemoryStats(size_t* memory_required_bytes,
+ size_t* memory_nice_to_have_bytes,
+ size_t* memory_allocated_bytes,
+ size_t* memory_used_bytes) const;
+
+ const MemoryHistory::Entry& memory_stats_from_last_assign() const {
+ return memory_stats_from_last_assign_;
+ }
+
+ bool AreTilesRequiredForActivationReady() const {
+ return all_tiles_required_for_activation_have_been_initialized_;
+ }
+
+ protected:
+ TileManager(TileManagerClient* client,
+ ResourceProvider* resource_provider,
+ scoped_ptr<RasterWorkerPool> raster_worker_pool,
+ size_t num_raster_threads,
+ RenderingStatsInstrumentation* rendering_stats_instrumentation,
+ GLenum texture_format);
+
+ // Methods called by Tile
+ friend class Tile;
+ void RegisterTile(Tile* tile);
+ void UnregisterTile(Tile* tile);
+
+ // Overriden from RasterWorkerPoolClient:
+ virtual bool ShouldForceTasksRequiredForActivationToComplete() const
+ OVERRIDE;
+ virtual void DidFinishRunningTasks() OVERRIDE;
+ virtual void DidFinishRunningTasksRequiredForActivation() OVERRIDE;
+
+ typedef std::vector<Tile*> TileVector;
+ typedef std::set<Tile*> TileSet;
+
+ // Virtual for test
+ virtual void ScheduleTasks(
+ const TileVector& tiles_that_need_to_be_rasterized);
+
+ void AssignGpuMemoryToTiles(
+ PrioritizedTileSet* tiles,
+ TileVector* tiles_that_need_to_be_rasterized);
+ void GetTilesWithAssignedBins(PrioritizedTileSet* tiles);
+ void GetPrioritizedTileSet(PrioritizedTileSet* tiles);
+
+ private:
+ void OnImageDecodeTaskCompleted(
+ int layer_id,
+ skia::LazyPixelRef* pixel_ref,
+ bool was_canceled);
+ void OnRasterTaskCompleted(
+ Tile::Id tile,
+ scoped_ptr<ResourcePool::Resource> resource,
+ RasterMode raster_mode,
+ const PicturePileImpl::Analysis& analysis,
+ bool was_canceled);
+
+ RasterMode DetermineRasterMode(const Tile* tile) const;
+ void CleanUpUnusedImageDecodeTasks();
+ void FreeResourceForTile(Tile* tile, RasterMode mode);
+ void FreeResourcesForTile(Tile* tile);
+ void FreeUnusedResourcesForTile(Tile* tile);
+ RasterWorkerPool::Task CreateImageDecodeTask(
+ Tile* tile, skia::LazyPixelRef* pixel_ref);
+ RasterWorkerPool::RasterTask CreateRasterTask(Tile* tile);
+ scoped_ptr<base::Value> GetMemoryRequirementsAsValue() const;
+
+ TileManagerClient* client_;
+ scoped_ptr<ResourcePool> resource_pool_;
+ scoped_ptr<RasterWorkerPool> raster_worker_pool_;
+ GlobalStateThatImpactsTilePriority global_state_;
+
+ typedef base::hash_map<Tile::Id, Tile*> TileMap;
+ TileMap tiles_;
+
+ PrioritizedTileSet prioritized_tiles_;
+
+ bool all_tiles_that_need_to_be_rasterized_have_memory_;
+ bool all_tiles_required_for_activation_have_memory_;
+ bool all_tiles_required_for_activation_have_been_initialized_;
+
+ bool ever_exceeded_memory_budget_;
+ MemoryHistory::Entry memory_stats_from_last_assign_;
+
+ RenderingStatsInstrumentation* rendering_stats_instrumentation_;
+
+ bool did_initialize_visible_tile_;
+
+ GLenum texture_format_;
+
+ typedef base::hash_map<uint32_t, RasterWorkerPool::Task> PixelRefTaskMap;
+ typedef base::hash_map<int, PixelRefTaskMap> LayerPixelRefTaskMap;
+ LayerPixelRefTaskMap image_decode_tasks_;
+
+ RasterTaskCompletionStats update_visible_tiles_stats_;
+
+ DISALLOW_COPY_AND_ASSIGN(TileManager);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_TILE_MANAGER_H_
diff --git a/chromium/cc/resources/tile_manager_perftest.cc b/chromium/cc/resources/tile_manager_perftest.cc
new file mode 100644
index 00000000000..ca6f9971c60
--- /dev/null
+++ b/chromium/cc/resources/tile_manager_perftest.cc
@@ -0,0 +1,141 @@
+// Copyright 2013 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 "base/time/time.h"
+#include "cc/resources/tile.h"
+#include "cc/resources/tile_priority.h"
+#include "cc/test/fake_output_surface.h"
+#include "cc/test/fake_picture_pile_impl.h"
+#include "cc/test/fake_tile_manager.h"
+#include "cc/test/fake_tile_manager_client.h"
+#include "cc/test/test_tile_priorities.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+
+namespace {
+
+static const int kTimeLimitMillis = 2000;
+static const int kWarmupRuns = 5;
+static const int kTimeCheckInterval = 10;
+
+class TileManagerPerfTest : public testing::Test {
+ public:
+ typedef std::vector<scoped_refptr<Tile> > TileVector;
+
+ TileManagerPerfTest() : num_runs_(0) {}
+
+ // Overridden from testing::Test:
+ virtual void SetUp() OVERRIDE {
+ output_surface_ = FakeOutputSurface::Create3d();
+ resource_provider_ = ResourceProvider::Create(output_surface_.get(), 0);
+ tile_manager_ = make_scoped_ptr(
+ new FakeTileManager(&tile_manager_client_, resource_provider_.get()));
+
+ GlobalStateThatImpactsTilePriority state;
+ gfx::Size tile_size = settings_.default_tile_size;
+ state.memory_limit_in_bytes =
+ 10000 * 4 * tile_size.width() * tile_size.height();
+ state.memory_limit_policy = ALLOW_ANYTHING;
+ state.tree_priority = SMOOTHNESS_TAKES_PRIORITY;
+
+ tile_manager_->SetGlobalState(state);
+ picture_pile_ = FakePicturePileImpl::CreatePile();
+ }
+
+ virtual void TearDown() OVERRIDE {
+ tile_manager_.reset(NULL);
+ picture_pile_ = NULL;
+ }
+
+ void EndTest() {
+ elapsed_ = base::TimeTicks::HighResNow() - start_time_;
+ }
+
+ void AfterTest(const std::string test_name) {
+ // Format matches chrome/test/perf/perf_test.h:PrintResult
+ printf("*RESULT %s: %.2f runs/s\n",
+ test_name.c_str(),
+ num_runs_ / elapsed_.InSecondsF());
+ }
+
+ bool DidRun() {
+ ++num_runs_;
+ if (num_runs_ == kWarmupRuns)
+ start_time_ = base::TimeTicks::HighResNow();
+
+ if (!start_time_.is_null() && (num_runs_ % kTimeCheckInterval) == 0) {
+ base::TimeDelta elapsed = base::TimeTicks::HighResNow() - start_time_;
+ if (elapsed >= base::TimeDelta::FromMilliseconds(kTimeLimitMillis)) {
+ elapsed_ = elapsed;
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ void CreateBinTiles(int count, TilePriority priority, TileVector* tiles) {
+ for (int i = 0; i < count; ++i) {
+ scoped_refptr<Tile> tile =
+ make_scoped_refptr(new Tile(tile_manager_.get(),
+ picture_pile_.get(),
+ settings_.default_tile_size,
+ gfx::Rect(),
+ gfx::Rect(),
+ 1.0,
+ 0,
+ 0,
+ true));
+ tile->SetPriority(ACTIVE_TREE, priority);
+ tile->SetPriority(PENDING_TREE, priority);
+ tiles->push_back(tile);
+ }
+ }
+
+ void CreateTiles(int count, TileVector* tiles) {
+ // Roughly an equal amount of all bins.
+ int count_per_bin = count / NUM_BINS;
+ CreateBinTiles(count_per_bin, TilePriorityForNowBin(), tiles);
+ CreateBinTiles(count_per_bin, TilePriorityForSoonBin(), tiles);
+ CreateBinTiles(count_per_bin, TilePriorityForEventualBin(), tiles);
+ CreateBinTiles(count - 3 * count_per_bin, TilePriority(), tiles);
+ }
+
+ void RunManageTilesTest(const std::string test_name,
+ unsigned tile_count) {
+ start_time_ = base::TimeTicks();
+ num_runs_ = 0;
+ TileVector tiles;
+ CreateTiles(tile_count, &tiles);
+ do {
+ tile_manager_->ManageTiles();
+ } while (DidRun());
+
+ AfterTest(test_name);
+ }
+
+ private:
+ FakeTileManagerClient tile_manager_client_;
+ LayerTreeSettings settings_;
+ scoped_ptr<FakeTileManager> tile_manager_;
+ scoped_refptr<FakePicturePileImpl> picture_pile_;
+ scoped_ptr<FakeOutputSurface> output_surface_;
+ scoped_ptr<ResourceProvider> resource_provider_;
+
+ base::TimeTicks start_time_;
+ base::TimeDelta elapsed_;
+ int num_runs_;
+};
+
+TEST_F(TileManagerPerfTest, ManageTiles) {
+ RunManageTilesTest("manage_tiles_100", 100);
+ RunManageTilesTest("manage_tiles_1000", 1000);
+ RunManageTilesTest("manage_tiles_10000", 10000);
+}
+
+} // namespace
+
+} // namespace cc
diff --git a/chromium/cc/resources/tile_manager_unittest.cc b/chromium/cc/resources/tile_manager_unittest.cc
new file mode 100644
index 00000000000..6fb06908568
--- /dev/null
+++ b/chromium/cc/resources/tile_manager_unittest.cc
@@ -0,0 +1,471 @@
+// Copyright 2013 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 "cc/resources/tile.h"
+#include "cc/resources/tile_priority.h"
+#include "cc/test/fake_output_surface.h"
+#include "cc/test/fake_picture_pile_impl.h"
+#include "cc/test/fake_tile_manager.h"
+#include "cc/test/fake_tile_manager_client.h"
+#include "cc/test/test_tile_priorities.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+class TileManagerTest : public testing::TestWithParam<bool> {
+ public:
+ typedef std::vector<scoped_refptr<Tile> > TileVector;
+
+ void Initialize(int max_tiles,
+ TileMemoryLimitPolicy memory_limit_policy,
+ TreePriority tree_priority) {
+ output_surface_ = FakeOutputSurface::Create3d();
+ resource_provider_ = ResourceProvider::Create(output_surface_.get(), 0);
+ tile_manager_ = make_scoped_ptr(
+ new FakeTileManager(&tile_manager_client_, resource_provider_.get()));
+
+ memory_limit_policy_ = memory_limit_policy;
+ max_memory_tiles_ = max_tiles;
+ GlobalStateThatImpactsTilePriority state;
+ gfx::Size tile_size = settings_.default_tile_size;
+
+ // The parametrization specifies whether the max tile limit should
+ // be applied to RAM or to tile limit.
+ if (GetParam()) {
+ state.memory_limit_in_bytes =
+ max_tiles * 4 * tile_size.width() * tile_size.height();
+ state.num_resources_limit = 100;
+ } else {
+ state.memory_limit_in_bytes = 100 * 1000 * 1000;
+ state.num_resources_limit = max_tiles;
+ }
+ state.memory_limit_policy = memory_limit_policy;
+ state.tree_priority = tree_priority;
+
+ tile_manager_->SetGlobalState(state);
+ picture_pile_ = FakePicturePileImpl::CreatePile();
+ }
+
+ void SetTreePriority(TreePriority tree_priority) {
+ GlobalStateThatImpactsTilePriority state;
+ gfx::Size tile_size = settings_.default_tile_size;
+ state.memory_limit_in_bytes =
+ max_memory_tiles_ * 4 * tile_size.width() * tile_size.height();
+ state.memory_limit_policy = memory_limit_policy_;
+ state.num_resources_limit = 100;
+ state.tree_priority = tree_priority;
+ tile_manager_->SetGlobalState(state);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ tile_manager_.reset(NULL);
+ picture_pile_ = NULL;
+
+ testing::Test::TearDown();
+ }
+
+ TileVector CreateTiles(int count,
+ TilePriority active_priority,
+ TilePriority pending_priority) {
+ TileVector tiles;
+ for (int i = 0; i < count; ++i) {
+ scoped_refptr<Tile> tile =
+ make_scoped_refptr(new Tile(tile_manager_.get(),
+ picture_pile_.get(),
+ settings_.default_tile_size,
+ gfx::Rect(),
+ gfx::Rect(),
+ 1.0,
+ 0,
+ 0,
+ true));
+ tile->SetPriority(ACTIVE_TREE, active_priority);
+ tile->SetPriority(PENDING_TREE, pending_priority);
+ tiles.push_back(tile);
+ }
+ return tiles;
+ }
+
+ FakeTileManager* tile_manager() {
+ return tile_manager_.get();
+ }
+
+ int AssignedMemoryCount(const TileVector& tiles) {
+ int has_memory_count = 0;
+ for (TileVector::const_iterator it = tiles.begin();
+ it != tiles.end();
+ ++it) {
+ if (tile_manager_->HasBeenAssignedMemory(*it))
+ ++has_memory_count;
+ }
+ return has_memory_count;
+ }
+
+ int TilesWithLCDCount(const TileVector& tiles) {
+ int has_lcd_count = 0;
+ for (TileVector::const_iterator it = tiles.begin();
+ it != tiles.end();
+ ++it) {
+ if ((*it)->GetRasterModeForTesting() == HIGH_QUALITY_RASTER_MODE)
+ ++has_lcd_count;
+ }
+ return has_lcd_count;
+ }
+
+ private:
+ FakeTileManagerClient tile_manager_client_;
+ LayerTreeSettings settings_;
+ scoped_ptr<FakeTileManager> tile_manager_;
+ scoped_refptr<FakePicturePileImpl> picture_pile_;
+ scoped_ptr<FakeOutputSurface> output_surface_;
+ scoped_ptr<ResourceProvider> resource_provider_;
+ TileMemoryLimitPolicy memory_limit_policy_;
+ int max_memory_tiles_;
+};
+
+TEST_P(TileManagerTest, EnoughMemoryAllowAnything) {
+ // A few tiles of each type of priority, with enough memory for all tiles.
+
+ Initialize(10, ALLOW_ANYTHING, SMOOTHNESS_TAKES_PRIORITY);
+ TileVector active_now =
+ CreateTiles(3, TilePriorityForNowBin(), TilePriority());
+ TileVector pending_now =
+ CreateTiles(3, TilePriority(), TilePriorityForNowBin());
+ TileVector active_pending_soon = CreateTiles(
+ 3, TilePriorityForSoonBin(), TilePriorityForSoonBin());
+ TileVector never_bin = CreateTiles(1, TilePriority(), TilePriority());
+
+ tile_manager()->AssignMemoryToTiles();
+
+ EXPECT_EQ(3, AssignedMemoryCount(active_now));
+ EXPECT_EQ(3, AssignedMemoryCount(pending_now));
+ EXPECT_EQ(3, AssignedMemoryCount(active_pending_soon));
+ EXPECT_EQ(0, AssignedMemoryCount(never_bin));
+}
+
+TEST_P(TileManagerTest, EnoughMemoryAllowPrepaintOnly) {
+ // A few tiles of each type of priority, with enough memory for all tiles,
+ // with the exception of never bin.
+
+ Initialize(10, ALLOW_PREPAINT_ONLY, SMOOTHNESS_TAKES_PRIORITY);
+ TileVector active_now =
+ CreateTiles(3, TilePriorityForNowBin(), TilePriority());
+ TileVector pending_now =
+ CreateTiles(3, TilePriority(), TilePriorityForNowBin());
+ TileVector active_pending_soon = CreateTiles(
+ 3, TilePriorityForSoonBin(), TilePriorityForSoonBin());
+ TileVector never_bin = CreateTiles(1, TilePriority(), TilePriority());
+
+ tile_manager()->AssignMemoryToTiles();
+
+ EXPECT_EQ(3, AssignedMemoryCount(active_now));
+ EXPECT_EQ(3, AssignedMemoryCount(pending_now));
+ EXPECT_EQ(3, AssignedMemoryCount(active_pending_soon));
+ EXPECT_EQ(0, AssignedMemoryCount(never_bin));
+}
+
+TEST_P(TileManagerTest, EnoughMemoryAllowAbsoluteMinimum) {
+ // A few tiles of each type of priority, with enough memory for all tiles,
+ // with the exception of never and soon bins.
+
+ Initialize(10, ALLOW_ABSOLUTE_MINIMUM, SMOOTHNESS_TAKES_PRIORITY);
+ TileVector active_now =
+ CreateTiles(3, TilePriorityForNowBin(), TilePriority());
+ TileVector pending_now =
+ CreateTiles(3, TilePriority(), TilePriorityForNowBin());
+ TileVector active_pending_soon = CreateTiles(
+ 3, TilePriorityForSoonBin(), TilePriorityForSoonBin());
+ TileVector never_bin = CreateTiles(1, TilePriority(), TilePriority());
+
+ tile_manager()->AssignMemoryToTiles();
+
+ EXPECT_EQ(3, AssignedMemoryCount(active_now));
+ EXPECT_EQ(3, AssignedMemoryCount(pending_now));
+ EXPECT_EQ(0, AssignedMemoryCount(active_pending_soon));
+ EXPECT_EQ(0, AssignedMemoryCount(never_bin));
+}
+
+TEST_P(TileManagerTest, EnoughMemoryAllowNothing) {
+ // A few tiles of each type of priority, with enough memory for all tiles,
+ // but allow nothing should not assign any memory.
+
+ Initialize(10, ALLOW_NOTHING, SMOOTHNESS_TAKES_PRIORITY);
+ TileVector active_now =
+ CreateTiles(3, TilePriorityForNowBin(), TilePriority());
+ TileVector pending_now =
+ CreateTiles(3, TilePriority(), TilePriorityForNowBin());
+ TileVector active_pending_soon = CreateTiles(
+ 3, TilePriorityForSoonBin(), TilePriorityForSoonBin());
+ TileVector never_bin = CreateTiles(1, TilePriority(), TilePriority());
+
+ tile_manager()->AssignMemoryToTiles();
+
+ EXPECT_EQ(0, AssignedMemoryCount(active_now));
+ EXPECT_EQ(0, AssignedMemoryCount(pending_now));
+ EXPECT_EQ(0, AssignedMemoryCount(active_pending_soon));
+ EXPECT_EQ(0, AssignedMemoryCount(never_bin));
+}
+
+TEST_P(TileManagerTest, PartialOOMMemoryToPending) {
+ // 5 tiles on active tree eventually bin, 5 tiles on pending tree that are
+ // required for activation, but only enough memory for 8 tiles. The result
+ // is all pending tree tiles get memory, and 3 of the active tree tiles
+ // get memory.
+
+ Initialize(8, ALLOW_ANYTHING, SMOOTHNESS_TAKES_PRIORITY);
+ TileVector active_tree_tiles =
+ CreateTiles(5, TilePriorityForEventualBin(), TilePriority());
+ TileVector pending_tree_tiles =
+ CreateTiles(5, TilePriority(), TilePriorityRequiredForActivation());
+
+ tile_manager()->AssignMemoryToTiles();
+
+ EXPECT_EQ(5, AssignedMemoryCount(active_tree_tiles));
+ EXPECT_EQ(3, AssignedMemoryCount(pending_tree_tiles));
+
+ SetTreePriority(SAME_PRIORITY_FOR_BOTH_TREES);
+ tile_manager()->AssignMemoryToTiles();
+
+ EXPECT_EQ(3, AssignedMemoryCount(active_tree_tiles));
+ EXPECT_EQ(5, AssignedMemoryCount(pending_tree_tiles));
+}
+
+TEST_P(TileManagerTest, PartialOOMMemoryToActive) {
+ // 5 tiles on active tree eventually bin, 5 tiles on pending tree now bin,
+ // but only enough memory for 8 tiles. The result is all active tree tiles
+ // get memory, and 3 of the pending tree tiles get memory.
+
+ Initialize(8, ALLOW_ANYTHING, SMOOTHNESS_TAKES_PRIORITY);
+ TileVector active_tree_tiles =
+ CreateTiles(5, TilePriorityForNowBin(), TilePriority());
+ TileVector pending_tree_tiles =
+ CreateTiles(5, TilePriority(), TilePriorityForNowBin());
+
+ tile_manager()->AssignMemoryToTiles();
+
+ EXPECT_EQ(5, AssignedMemoryCount(active_tree_tiles));
+ EXPECT_EQ(3, AssignedMemoryCount(pending_tree_tiles));
+}
+
+TEST_P(TileManagerTest, TotalOOMMemoryToPending) {
+ // 5 tiles on active tree eventually bin, 5 tiles on pending tree that are
+ // required for activation, but only enough memory for 4 tiles. The result
+ // is 4 pending tree tiles get memory, and none of the active tree tiles
+ // get memory.
+
+ Initialize(4, ALLOW_ANYTHING, SMOOTHNESS_TAKES_PRIORITY);
+ TileVector active_tree_tiles =
+ CreateTiles(5, TilePriorityForEventualBin(), TilePriority());
+ TileVector pending_tree_tiles =
+ CreateTiles(5, TilePriority(), TilePriorityRequiredForActivation());
+
+ tile_manager()->AssignMemoryToTiles();
+
+ EXPECT_EQ(4, AssignedMemoryCount(active_tree_tiles));
+ EXPECT_EQ(0, AssignedMemoryCount(pending_tree_tiles));
+
+ SetTreePriority(SAME_PRIORITY_FOR_BOTH_TREES);
+ tile_manager()->AssignMemoryToTiles();
+
+ EXPECT_EQ(0, AssignedMemoryCount(active_tree_tiles));
+ EXPECT_EQ(4, AssignedMemoryCount(pending_tree_tiles));
+}
+
+TEST_P(TileManagerTest, TotalOOMActiveSoonMemoryToPending) {
+ // 5 tiles on active tree soon bin, 5 tiles on pending tree that are
+ // required for activation, but only enough memory for 4 tiles. The result
+ // is 4 pending tree tiles get memory, and none of the active tree tiles
+ // get memory.
+
+ Initialize(4, ALLOW_ANYTHING, SMOOTHNESS_TAKES_PRIORITY);
+ TileVector active_tree_tiles =
+ CreateTiles(5, TilePriorityForSoonBin(), TilePriority());
+ TileVector pending_tree_tiles =
+ CreateTiles(5, TilePriority(), TilePriorityRequiredForActivation());
+
+ tile_manager()->AssignMemoryToTiles();
+
+ EXPECT_EQ(4, AssignedMemoryCount(active_tree_tiles));
+ EXPECT_EQ(0, AssignedMemoryCount(pending_tree_tiles));
+
+ SetTreePriority(SAME_PRIORITY_FOR_BOTH_TREES);
+ tile_manager()->AssignMemoryToTiles();
+
+ EXPECT_EQ(0, AssignedMemoryCount(active_tree_tiles));
+ EXPECT_EQ(4, AssignedMemoryCount(pending_tree_tiles));
+}
+
+TEST_P(TileManagerTest, TotalOOMMemoryToActive) {
+ // 5 tiles on active tree eventually bin, 5 tiles on pending tree now bin,
+ // but only enough memory for 4 tiles. The result is 5 active tree tiles
+ // get memory, and none of the pending tree tiles get memory.
+
+ Initialize(4, ALLOW_ANYTHING, SMOOTHNESS_TAKES_PRIORITY);
+ TileVector active_tree_tiles =
+ CreateTiles(5, TilePriorityForNowBin(), TilePriority());
+ TileVector pending_tree_tiles =
+ CreateTiles(5, TilePriority(), TilePriorityForNowBin());
+
+ tile_manager()->AssignMemoryToTiles();
+
+ EXPECT_EQ(4, AssignedMemoryCount(active_tree_tiles));
+ EXPECT_EQ(0, AssignedMemoryCount(pending_tree_tiles));
+}
+
+
+
+TEST_P(TileManagerTest, RasterAsLCD) {
+ Initialize(20, ALLOW_ANYTHING, SMOOTHNESS_TAKES_PRIORITY);
+ TileVector active_tree_tiles =
+ CreateTiles(5, TilePriorityForNowBin(), TilePriority());
+ TileVector pending_tree_tiles =
+ CreateTiles(5, TilePriority(), TilePriorityForNowBin());
+
+ tile_manager()->ManageTiles();
+
+ EXPECT_EQ(5, TilesWithLCDCount(active_tree_tiles));
+ EXPECT_EQ(5, TilesWithLCDCount(pending_tree_tiles));
+}
+
+TEST_P(TileManagerTest, RasterAsNoLCD) {
+ Initialize(20, ALLOW_ANYTHING, SMOOTHNESS_TAKES_PRIORITY);
+ TileVector active_tree_tiles =
+ CreateTiles(5, TilePriorityForNowBin(), TilePriority());
+ TileVector pending_tree_tiles =
+ CreateTiles(5, TilePriority(), TilePriorityForNowBin());
+
+ for (TileVector::iterator it = active_tree_tiles.begin();
+ it != active_tree_tiles.end();
+ ++it) {
+ (*it)->set_can_use_lcd_text(false);
+ }
+ for (TileVector::iterator it = pending_tree_tiles.begin();
+ it != pending_tree_tiles.end();
+ ++it) {
+ (*it)->set_can_use_lcd_text(false);
+ }
+
+ tile_manager()->ManageTiles();
+
+ EXPECT_EQ(0, TilesWithLCDCount(active_tree_tiles));
+ EXPECT_EQ(0, TilesWithLCDCount(pending_tree_tiles));
+}
+
+TEST_P(TileManagerTest, ReRasterAsNoLCD) {
+ Initialize(20, ALLOW_ANYTHING, SMOOTHNESS_TAKES_PRIORITY);
+ TileVector active_tree_tiles =
+ CreateTiles(5, TilePriorityForNowBin(), TilePriority());
+ TileVector pending_tree_tiles =
+ CreateTiles(5, TilePriority(), TilePriorityForNowBin());
+
+ tile_manager()->ManageTiles();
+
+ EXPECT_EQ(5, TilesWithLCDCount(active_tree_tiles));
+ EXPECT_EQ(5, TilesWithLCDCount(pending_tree_tiles));
+
+ for (TileVector::iterator it = active_tree_tiles.begin();
+ it != active_tree_tiles.end();
+ ++it) {
+ (*it)->set_can_use_lcd_text(false);
+ }
+ for (TileVector::iterator it = pending_tree_tiles.begin();
+ it != pending_tree_tiles.end();
+ ++it) {
+ (*it)->set_can_use_lcd_text(false);
+ }
+
+ tile_manager()->ManageTiles();
+
+ EXPECT_EQ(0, TilesWithLCDCount(active_tree_tiles));
+ EXPECT_EQ(0, TilesWithLCDCount(pending_tree_tiles));
+}
+
+TEST_P(TileManagerTest, NoTextDontReRasterAsNoLCD) {
+ Initialize(20, ALLOW_ANYTHING, SMOOTHNESS_TAKES_PRIORITY);
+ TileVector active_tree_tiles =
+ CreateTiles(5, TilePriorityForNowBin(), TilePriority());
+ TileVector pending_tree_tiles =
+ CreateTiles(5, TilePriority(), TilePriorityForNowBin());
+
+ tile_manager()->ManageTiles();
+
+ EXPECT_EQ(5, TilesWithLCDCount(active_tree_tiles));
+ EXPECT_EQ(5, TilesWithLCDCount(pending_tree_tiles));
+
+ for (TileVector::iterator it = active_tree_tiles.begin();
+ it != active_tree_tiles.end();
+ ++it) {
+ ManagedTileState::TileVersion& tile_version =
+ (*it)->GetTileVersionForTesting(HIGH_QUALITY_RASTER_MODE);
+ tile_version.SetSolidColorForTesting(SkColorSetARGB(0, 0, 0, 0));
+ (*it)->set_can_use_lcd_text(false);
+ EXPECT_TRUE((*it)->IsReadyToDraw());
+ }
+ for (TileVector::iterator it = pending_tree_tiles.begin();
+ it != pending_tree_tiles.end();
+ ++it) {
+ ManagedTileState::TileVersion& tile_version =
+ (*it)->GetTileVersionForTesting(HIGH_QUALITY_RASTER_MODE);
+ tile_version.SetSolidColorForTesting(SkColorSetARGB(0, 0, 0, 0));
+ (*it)->set_can_use_lcd_text(false);
+ EXPECT_TRUE((*it)->IsReadyToDraw());
+ }
+
+ tile_manager()->ManageTiles();
+
+ EXPECT_EQ(5, TilesWithLCDCount(active_tree_tiles));
+ EXPECT_EQ(5, TilesWithLCDCount(pending_tree_tiles));
+}
+
+TEST_P(TileManagerTest, TextReRasterAsNoLCD) {
+ Initialize(20, ALLOW_ANYTHING, SMOOTHNESS_TAKES_PRIORITY);
+ TileVector active_tree_tiles =
+ CreateTiles(5, TilePriorityForNowBin(), TilePriority());
+ TileVector pending_tree_tiles =
+ CreateTiles(5, TilePriority(), TilePriorityForNowBin());
+
+ tile_manager()->ManageTiles();
+
+ EXPECT_EQ(5, TilesWithLCDCount(active_tree_tiles));
+ EXPECT_EQ(5, TilesWithLCDCount(pending_tree_tiles));
+
+ for (TileVector::iterator it = active_tree_tiles.begin();
+ it != active_tree_tiles.end();
+ ++it) {
+ ManagedTileState::TileVersion& tile_version =
+ (*it)->GetTileVersionForTesting(HIGH_QUALITY_RASTER_MODE);
+ tile_version.SetSolidColorForTesting(SkColorSetARGB(0, 0, 0, 0));
+ tile_version.SetHasTextForTesting(true);
+ (*it)->set_can_use_lcd_text(false);
+
+ EXPECT_TRUE((*it)->IsReadyToDraw());
+ }
+ for (TileVector::iterator it = pending_tree_tiles.begin();
+ it != pending_tree_tiles.end();
+ ++it) {
+ ManagedTileState::TileVersion& tile_version =
+ (*it)->GetTileVersionForTesting(HIGH_QUALITY_RASTER_MODE);
+ tile_version.SetSolidColorForTesting(
+ SkColorSetARGB(0, 0, 0, 0));
+ tile_version.SetHasTextForTesting(true);
+ (*it)->set_can_use_lcd_text(false);
+
+ EXPECT_TRUE((*it)->IsReadyToDraw());
+ }
+
+ tile_manager()->ManageTiles();
+
+ EXPECT_EQ(0, TilesWithLCDCount(active_tree_tiles));
+ EXPECT_EQ(0, TilesWithLCDCount(pending_tree_tiles));
+}
+
+// If true, the max tile limit should be applied as bytes; if false,
+// as num_resources_limit.
+INSTANTIATE_TEST_CASE_P(TileManagerTests,
+ TileManagerTest,
+ ::testing::Values(true, false));
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/resources/tile_priority.cc b/chromium/cc/resources/tile_priority.cc
new file mode 100644
index 00000000000..970ea1d81a5
--- /dev/null
+++ b/chromium/cc/resources/tile_priority.cc
@@ -0,0 +1,198 @@
+// 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 "cc/resources/tile_priority.h"
+
+#include "base/values.h"
+#include "cc/base/math_util.h"
+
+namespace {
+
+// TODO(qinmin): modify ui/range/Range.h to support template so that we
+// don't need to define this.
+struct Range {
+ Range(float start, float end) : start_(start), end_(end) {}
+ bool IsEmpty();
+ float start_;
+ float end_;
+};
+
+inline bool Intersects(const Range& a, const Range& b) {
+ return a.start_ < b.end_ && b.start_ < a.end_;
+}
+
+inline Range Intersect(const Range& a, const Range& b) {
+ return Range(std::max(a.start_, b.start_), std::min(a.end_, b.end_));
+}
+
+bool Range::IsEmpty() {
+ return start_ >= end_;
+}
+
+inline void IntersectNegativeHalfplane(Range* out,
+ float previous,
+ float current,
+ float target,
+ float time_delta) {
+ float time_per_dist = time_delta / (current - previous);
+ float t = (target - current) * time_per_dist;
+ if (time_per_dist > 0.0f)
+ out->start_ = std::max(out->start_, t);
+ else
+ out->end_ = std::min(out->end_, t);
+}
+
+inline void IntersectPositiveHalfplane(Range* out,
+ float previous,
+ float current,
+ float target,
+ float time_delta) {
+ float time_per_dist = time_delta / (current - previous);
+ float t = (target - current) * time_per_dist;
+ if (time_per_dist < 0.0f)
+ out->start_ = std::max(out->start_, t);
+ else
+ out->end_ = std::min(out->end_, t);
+}
+
+} // namespace
+
+namespace cc {
+
+scoped_ptr<base::Value> WhichTreeAsValue(WhichTree tree) {
+ switch (tree) {
+ case ACTIVE_TREE:
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "ACTIVE_TREE"));
+ case PENDING_TREE:
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "PENDING_TREE"));
+ default:
+ DCHECK(false) << "Unrecognized WhichTree value " << tree;
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "<unknown WhichTree value>"));
+ }
+}
+
+scoped_ptr<base::Value> TileResolutionAsValue(
+ TileResolution resolution) {
+ switch (resolution) {
+ case LOW_RESOLUTION:
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "LOW_RESOLUTION"));
+ case HIGH_RESOLUTION:
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "HIGH_RESOLUTION"));
+ case NON_IDEAL_RESOLUTION:
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "NON_IDEAL_RESOLUTION"));
+ default:
+ DCHECK(false) << "Unrecognized TileResolution value " << resolution;
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "<unknown TileResolution value>"));
+ }
+}
+
+scoped_ptr<base::Value> TilePriority::AsValue() const {
+ scoped_ptr<base::DictionaryValue> state(new base::DictionaryValue());
+ state->Set("resolution", TileResolutionAsValue(resolution).release());
+ state->Set("time_to_visible_in_seconds",
+ MathUtil::AsValueSafely(time_to_visible_in_seconds).release());
+ state->Set("distance_to_visible_in_pixels",
+ MathUtil::AsValueSafely(distance_to_visible_in_pixels).release());
+ return state.PassAs<base::Value>();
+}
+
+float TilePriority::TimeForBoundsToIntersect(const gfx::RectF& previous_bounds,
+ const gfx::RectF& current_bounds,
+ float time_delta,
+ const gfx::RectF& target_bounds) {
+ // Perform an intersection test explicitly between current and target.
+ if (current_bounds.x() < target_bounds.right() &&
+ current_bounds.y() < target_bounds.bottom() &&
+ target_bounds.x() < current_bounds.right() &&
+ target_bounds.y() < current_bounds.bottom())
+ return 0.0f;
+
+ const float kMaxTimeToVisibleInSeconds =
+ std::numeric_limits<float>::infinity();
+
+ if (time_delta == 0.0f)
+ return kMaxTimeToVisibleInSeconds;
+
+ // As we are trying to solve the case of both scaling and scrolling, using
+ // a single coordinate with velocity is not enough. The logic here is to
+ // calculate the velocity for each edge. Then we calculate the time range that
+ // each edge will stay on the same side of the target bounds. If there is an
+ // overlap between these time ranges, the bounds must have intersect with
+ // each other during that period of time.
+ Range range(0.0f, kMaxTimeToVisibleInSeconds);
+ IntersectPositiveHalfplane(
+ &range, previous_bounds.x(), current_bounds.x(),
+ target_bounds.right(), time_delta);
+ IntersectNegativeHalfplane(
+ &range, previous_bounds.right(), current_bounds.right(),
+ target_bounds.x(), time_delta);
+ IntersectPositiveHalfplane(
+ &range, previous_bounds.y(), current_bounds.y(),
+ target_bounds.bottom(), time_delta);
+ IntersectNegativeHalfplane(
+ &range, previous_bounds.bottom(), current_bounds.bottom(),
+ target_bounds.y(), time_delta);
+ return range.IsEmpty() ? kMaxTimeToVisibleInSeconds : range.start_;
+}
+
+scoped_ptr<base::Value> TileMemoryLimitPolicyAsValue(
+ TileMemoryLimitPolicy policy) {
+ switch (policy) {
+ case ALLOW_NOTHING:
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "ALLOW_NOTHING"));
+ case ALLOW_ABSOLUTE_MINIMUM:
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "ALLOW_ABSOLUTE_MINIMUM"));
+ case ALLOW_PREPAINT_ONLY:
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "ALLOW_PREPAINT_ONLY"));
+ case ALLOW_ANYTHING:
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "ALLOW_ANYTHING"));
+ default:
+ DCHECK(false) << "Unrecognized policy value";
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "<unknown>"));
+ }
+}
+
+scoped_ptr<base::Value> TreePriorityAsValue(TreePriority prio) {
+ switch (prio) {
+ case SAME_PRIORITY_FOR_BOTH_TREES:
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "SAME_PRIORITY_FOR_BOTH_TREES"));
+ case SMOOTHNESS_TAKES_PRIORITY:
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "SMOOTHNESS_TAKES_PRIORITY"));
+ case NEW_CONTENT_TAKES_PRIORITY:
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "NEW_CONTENT_TAKES_PRIORITY"));
+ default:
+ DCHECK(false) << "Unrecognized priority value " << prio;
+ return scoped_ptr<base::Value>(base::Value::CreateStringValue(
+ "<unknown>"));
+ }
+}
+
+scoped_ptr<base::Value> GlobalStateThatImpactsTilePriority::AsValue() const {
+ scoped_ptr<base::DictionaryValue> state(new base::DictionaryValue());
+ state->Set("memory_limit_policy",
+ TileMemoryLimitPolicyAsValue(memory_limit_policy).release());
+ state->SetInteger("memory_limit_in_bytes", memory_limit_in_bytes);
+ state->SetInteger("unused_memory_limit_in_bytes",
+ unused_memory_limit_in_bytes);
+ state->SetInteger("num_resources_limit", num_resources_limit);
+ state->Set("tree_priority", TreePriorityAsValue(tree_priority).release());
+ return state.PassAs<base::Value>();
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/tile_priority.h b/chromium/cc/resources/tile_priority.h
new file mode 100644
index 00000000000..5c92d2f178e
--- /dev/null
+++ b/chromium/cc/resources/tile_priority.h
@@ -0,0 +1,179 @@
+// 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.
+
+#ifndef CC_RESOURCES_TILE_PRIORITY_H_
+#define CC_RESOURCES_TILE_PRIORITY_H_
+
+#include <algorithm>
+#include <limits>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/resources/picture_pile.h"
+#include "ui/gfx/quad_f.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size.h"
+
+namespace base {
+class Value;
+}
+
+namespace cc {
+
+enum WhichTree {
+ // Note: these must be 0 and 1 because we index with them in various places,
+ // e.g. in Tile::priority_.
+ ACTIVE_TREE = 0,
+ PENDING_TREE = 1,
+ NUM_TREES = 2
+ // Be sure to update WhichTreeAsValue when adding new fields.
+};
+scoped_ptr<base::Value> WhichTreeAsValue(
+ WhichTree tree);
+
+enum TileResolution {
+ LOW_RESOLUTION = 0 ,
+ HIGH_RESOLUTION = 1,
+ NON_IDEAL_RESOLUTION = 2,
+};
+scoped_ptr<base::Value> TileResolutionAsValue(
+ TileResolution resolution);
+
+struct CC_EXPORT TilePriority {
+ TilePriority()
+ : resolution(NON_IDEAL_RESOLUTION),
+ required_for_activation(false),
+ time_to_visible_in_seconds(std::numeric_limits<float>::infinity()),
+ distance_to_visible_in_pixels(std::numeric_limits<float>::infinity()) {}
+
+ TilePriority(TileResolution resolution,
+ float time_to_visible_in_seconds,
+ float distance_to_visible_in_pixels)
+ : resolution(resolution),
+ required_for_activation(false),
+ time_to_visible_in_seconds(time_to_visible_in_seconds),
+ distance_to_visible_in_pixels(distance_to_visible_in_pixels) {}
+
+ TilePriority(const TilePriority& active, const TilePriority& pending) {
+ if (active.resolution == HIGH_RESOLUTION ||
+ pending.resolution == HIGH_RESOLUTION)
+ resolution = HIGH_RESOLUTION;
+ else if (active.resolution == LOW_RESOLUTION ||
+ pending.resolution == LOW_RESOLUTION)
+ resolution = LOW_RESOLUTION;
+ else
+ resolution = NON_IDEAL_RESOLUTION;
+
+ required_for_activation =
+ active.required_for_activation || pending.required_for_activation;
+
+ time_to_visible_in_seconds =
+ std::min(active.time_to_visible_in_seconds,
+ pending.time_to_visible_in_seconds);
+ distance_to_visible_in_pixels =
+ std::min(active.distance_to_visible_in_pixels,
+ pending.distance_to_visible_in_pixels);
+ }
+ void set_current_screen_quad(const gfx::QuadF& q) { current_screen_quad = q; }
+
+ scoped_ptr<base::Value> AsValue() const;
+
+ static inline float manhattanDistance(const gfx::RectF& a,
+ const gfx::RectF& b) {
+ // Compute the union explicitly.
+ gfx::RectF c = gfx::RectF(
+ std::min(a.x(), b.x()),
+ std::min(a.y(), b.y()),
+ std::max(a.right(), b.right()) - std::min(a.x(), b.x()),
+ std::max(a.bottom(), b.bottom()) - std::min(a.y(), b.y()));
+
+ // Rects touching the edge of the screen should not be considered visible.
+ // So we add 1 pixel here to avoid that situation.
+ float x = std::max(0.0f, c.width() - a.width() - b.width() + 1.0f);
+ float y = std::max(0.0f, c.height() - a.height() - b.height() + 1.0f);
+ return (x + y);
+ }
+
+ // Calculate the time for the |current_bounds| to intersect with the
+ // |target_bounds| given its previous location and time delta.
+ // This function should work for both scaling and scrolling case.
+ static float TimeForBoundsToIntersect(const gfx::RectF& previous_bounds,
+ const gfx::RectF& current_bounds,
+ float time_delta,
+ const gfx::RectF& target_bounds);
+
+ bool operator ==(const TilePriority& other) const {
+ return resolution == other.resolution &&
+ time_to_visible_in_seconds == other.time_to_visible_in_seconds &&
+ distance_to_visible_in_pixels == other.distance_to_visible_in_pixels;
+ // No need to compare current_screen_quad which is for debug only and
+ // never changes by itself.
+ }
+
+ bool operator !=(const TilePriority& other) const {
+ return !(*this == other);
+ }
+
+ TileResolution resolution;
+ bool required_for_activation;
+ float time_to_visible_in_seconds;
+ float distance_to_visible_in_pixels;
+
+ private:
+ gfx::QuadF current_screen_quad;
+};
+
+enum TileMemoryLimitPolicy {
+ // Nothing.
+ ALLOW_NOTHING = 0,
+
+ // You might be made visible, but you're not being interacted with.
+ ALLOW_ABSOLUTE_MINIMUM = 1, // Tall.
+
+ // You're being interacted with, but we're low on memory.
+ ALLOW_PREPAINT_ONLY = 2, // Grande.
+
+ // You're the only thing in town. Go crazy.
+ ALLOW_ANYTHING = 3, // Venti.
+
+ NUM_TILE_MEMORY_LIMIT_POLICIES = 4,
+
+ // NOTE: Be sure to update TreePriorityAsValue and kBinPolicyMap when adding
+ // or reordering fields.
+};
+scoped_ptr<base::Value> TileMemoryLimitPolicyAsValue(
+ TileMemoryLimitPolicy policy);
+
+enum TreePriority {
+ SAME_PRIORITY_FOR_BOTH_TREES,
+ SMOOTHNESS_TAKES_PRIORITY,
+ NEW_CONTENT_TAKES_PRIORITY
+
+ // Be sure to update TreePriorityAsValue when adding new fields.
+};
+scoped_ptr<base::Value> TreePriorityAsValue(TreePriority prio);
+
+class GlobalStateThatImpactsTilePriority {
+ public:
+ GlobalStateThatImpactsTilePriority()
+ : memory_limit_policy(ALLOW_NOTHING),
+ memory_limit_in_bytes(0),
+ unused_memory_limit_in_bytes(0),
+ num_resources_limit(0),
+ tree_priority(SAME_PRIORITY_FOR_BOTH_TREES) {}
+
+ TileMemoryLimitPolicy memory_limit_policy;
+
+ size_t memory_limit_in_bytes;
+ size_t unused_memory_limit_in_bytes;
+ size_t num_resources_limit;
+
+ TreePriority tree_priority;
+
+ scoped_ptr<base::Value> AsValue() const;
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_TILE_PRIORITY_H_
diff --git a/chromium/cc/resources/tile_priority_unittest.cc b/chromium/cc/resources/tile_priority_unittest.cc
new file mode 100644
index 00000000000..97de199b094
--- /dev/null
+++ b/chromium/cc/resources/tile_priority_unittest.cc
@@ -0,0 +1,67 @@
+// 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 "cc/resources/tile_priority.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+TEST(TilePriorityTest, TimeForBoundsToIntersectWithScroll) {
+ const float inf = std::numeric_limits<float>::infinity();
+ gfx::Rect target(0, 0, 800, 600);
+ gfx::Rect current(100, 100, 100, 100);
+ EXPECT_EQ(0, TilePriority::TimeForBoundsToIntersect(
+ gfx::Rect(-200, 0, 100, 100), current, 1, target));
+ EXPECT_EQ(0, TilePriority::TimeForBoundsToIntersect(
+ gfx::Rect(-100, 0, 100, 100), current, 1, target));
+ EXPECT_EQ(0, TilePriority::TimeForBoundsToIntersect(
+ gfx::Rect(400, 400, 100, 100), current, 1, target));
+
+ current = gfx::Rect(-300, -300, 100, 100);
+ EXPECT_EQ(inf, TilePriority::TimeForBoundsToIntersect(
+ gfx::Rect(0, 0, 100, 100), current, 1, target));
+ EXPECT_EQ(inf, TilePriority::TimeForBoundsToIntersect(
+ gfx::Rect(-200, -200, 100, 100), current, 1, target));
+ EXPECT_EQ(2, TilePriority::TimeForBoundsToIntersect(
+ gfx::Rect(-400, -400, 100, 100), current, 1, target));
+}
+
+TEST(TilePriorityTest, TimeForBoundsToIntersectWithScale) {
+ const float inf = std::numeric_limits<float>::infinity();
+ gfx::Rect target(0, 0, 800, 600);
+ gfx::Rect current(100, 100, 100, 100);
+ EXPECT_EQ(0, TilePriority::TimeForBoundsToIntersect(
+ gfx::Rect(-200, 0, 200, 200), current, 1, target));
+ EXPECT_EQ(0, TilePriority::TimeForBoundsToIntersect(
+ gfx::Rect(-100, 0, 50, 50), current, 1, target));
+ EXPECT_EQ(0, TilePriority::TimeForBoundsToIntersect(
+ gfx::Rect(400, 400, 400, 400), current, 1, target));
+
+ current = gfx::Rect(-300, -300, 100, 100);
+ EXPECT_EQ(inf, TilePriority::TimeForBoundsToIntersect(
+ gfx::Rect(-400, -400, 300, 300), current, 1, target));
+ EXPECT_EQ(8, TilePriority::TimeForBoundsToIntersect(
+ gfx::Rect(-275, -275, 50, 50), current, 1, target));
+ EXPECT_EQ(1, TilePriority::TimeForBoundsToIntersect(
+ gfx::Rect(-450, -450, 50, 50), current, 1, target));
+}
+
+TEST(TilePriorityTest, ManhattanDistanceBetweenRects) {
+ EXPECT_EQ(0, TilePriority::manhattanDistance(
+ gfx::RectF(0, 0, 400, 400), gfx::RectF(0, 0, 100, 100)));
+
+ EXPECT_EQ(2, TilePriority::manhattanDistance(
+ gfx::Rect(0, 0, 400, 400), gfx::Rect(-100, -100, 100, 100)));
+
+ EXPECT_EQ(1, TilePriority::manhattanDistance(
+ gfx::Rect(0, 0, 400, 400), gfx::Rect(0, -100, 100, 100)));
+
+ EXPECT_EQ(202, TilePriority::manhattanDistance(
+ gfx::Rect(0, 0, 100, 100), gfx::Rect(200, 200, 100, 100)));
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/resources/transferable_resource.cc b/chromium/cc/resources/transferable_resource.cc
new file mode 100644
index 00000000000..be2be1a448c
--- /dev/null
+++ b/chromium/cc/resources/transferable_resource.cc
@@ -0,0 +1,20 @@
+// 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 "base/logging.h"
+#include "cc/resources/transferable_resource.h"
+
+namespace cc {
+
+TransferableResource::TransferableResource()
+ : id(0),
+ sync_point(0),
+ format(0),
+ filter(0) {
+}
+
+TransferableResource::~TransferableResource() {
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/transferable_resource.h b/chromium/cc/resources/transferable_resource.h
new file mode 100644
index 00000000000..5c979433c90
--- /dev/null
+++ b/chromium/cc/resources/transferable_resource.h
@@ -0,0 +1,33 @@
+// 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.
+
+#ifndef CC_RESOURCES_TRANSFERABLE_RESOURCE_H_
+#define CC_RESOURCES_TRANSFERABLE_RESOURCE_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "cc/base/cc_export.h"
+#include "gpu/command_buffer/common/mailbox.h"
+#include "ui/gfx/size.h"
+
+namespace cc {
+
+struct CC_EXPORT TransferableResource {
+ TransferableResource();
+ ~TransferableResource();
+
+ unsigned id;
+ unsigned sync_point;
+ uint32 format;
+ uint32 filter;
+ gfx::Size size;
+ gpu::Mailbox mailbox;
+};
+
+typedef std::vector<TransferableResource> TransferableResourceArray;
+
+} // namespace cc
+
+#endif // CC_RESOURCES_TRANSFERABLE_RESOURCE_H_
diff --git a/chromium/cc/resources/ui_resource_bitmap.cc b/chromium/cc/resources/ui_resource_bitmap.cc
new file mode 100644
index 00000000000..8bbfb37deee
--- /dev/null
+++ b/chromium/cc/resources/ui_resource_bitmap.cc
@@ -0,0 +1,26 @@
+// Copyright 2013 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 "cc/resources/ui_resource_bitmap.h"
+
+#include "base/memory/scoped_ptr.h"
+
+namespace cc {
+
+scoped_refptr<UIResourceBitmap>
+UIResourceBitmap::Create(uint8_t* pixels,
+ UIResourceFormat format,
+ gfx::Size size) {
+ scoped_refptr<UIResourceBitmap> ret = new UIResourceBitmap();
+ ret->pixels_ = scoped_ptr<uint8_t[]>(pixels);
+ ret->format_ = format;
+ ret->size_ = size;
+
+ return ret;
+}
+
+UIResourceBitmap::UIResourceBitmap() {}
+UIResourceBitmap::~UIResourceBitmap() {}
+
+} // namespace cc
diff --git a/chromium/cc/resources/ui_resource_bitmap.h b/chromium/cc/resources/ui_resource_bitmap.h
new file mode 100644
index 00000000000..dbf70690792
--- /dev/null
+++ b/chromium/cc/resources/ui_resource_bitmap.h
@@ -0,0 +1,50 @@
+// Copyright 2013 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.
+
+#ifndef CC_RESOURCES_UI_RESOURCE_BITMAP_H_
+#define CC_RESOURCES_UI_RESOURCE_BITMAP_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "third_party/skia/include/core/SkTypes.h"
+#include "ui/gfx/size.h"
+
+namespace cc {
+
+// Ref-counted bitmap class (can’t use SkBitmap because of ETC1). Thread-safety
+// ensures that both main and impl threads can hold references to the bitmap and
+// that asynchronous uploads are allowed.
+class CC_EXPORT UIResourceBitmap
+ : public base::RefCountedThreadSafe<UIResourceBitmap> {
+ public:
+ enum UIResourceFormat {
+ RGBA8
+ };
+
+ // Takes ownership of “pixels”.
+ static scoped_refptr<UIResourceBitmap> Create(uint8_t* pixels,
+ UIResourceFormat format,
+ gfx::Size size);
+
+ gfx::Size GetSize() const { return size_; }
+ UIResourceFormat GetFormat() const { return format_; }
+ uint8_t* GetPixels() { return pixels_.get(); }
+
+ private:
+ friend class base::RefCountedThreadSafe<UIResourceBitmap>;
+
+ UIResourceBitmap();
+ ~UIResourceBitmap();
+
+ scoped_ptr<uint8_t[]> pixels_;
+ UIResourceFormat format_;
+ gfx::Size size_;
+
+ DISALLOW_COPY_AND_ASSIGN(UIResourceBitmap);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_UI_RESOURCE_BITMAP_H_
diff --git a/chromium/cc/resources/ui_resource_client.h b/chromium/cc/resources/ui_resource_client.h
new file mode 100644
index 00000000000..d647936baee
--- /dev/null
+++ b/chromium/cc/resources/ui_resource_client.h
@@ -0,0 +1,34 @@
+// Copyright 2013 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.
+
+#ifndef CC_RESOURCES_UI_RESOURCE_CLIENT_H_
+#define CC_RESOURCES_UI_RESOURCE_CLIENT_H_
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "cc/base/cc_export.h"
+
+namespace cc {
+
+class UIResourceBitmap;
+
+typedef int UIResourceId;
+
+class CC_EXPORT UIResourceClient {
+ public:
+ // GetBitmap() will be called once soon after resource creation and then will
+ // be called afterwards whenever the GL context is lost, on the same thread
+ // that LayerTreeHost::CreateUIResource was called on. It is only safe to
+ // delete a UIResourceClient object after DeleteUIResource has been called for
+ // all IDs associated with it. A valid bitmap always must be returned but it
+ // doesn't need to be the same size or format as the original.
+ virtual scoped_refptr<UIResourceBitmap> GetBitmap(UIResourceId uid,
+ bool resource_lost) = 0;
+ virtual ~UIResourceClient() {}
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_UI_RESOURCE_CLIENT_H_
diff --git a/chromium/cc/resources/video_resource_updater.cc b/chromium/cc/resources/video_resource_updater.cc
new file mode 100644
index 00000000000..4239e989e56
--- /dev/null
+++ b/chromium/cc/resources/video_resource_updater.cc
@@ -0,0 +1,405 @@
+// Copyright 2013 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 "cc/resources/video_resource_updater.h"
+
+#include "base/bind.h"
+#include "cc/output/gl_renderer.h"
+#include "cc/resources/resource_provider.h"
+#include "gpu/GLES2/gl2extchromium.h"
+#include "media/base/video_frame.h"
+#include "media/filters/skcanvas_video_renderer.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "third_party/khronos/GLES2/gl2ext.h"
+#include "ui/gfx/size_conversions.h"
+
+const unsigned kYUVResourceFormat = GL_LUMINANCE;
+const unsigned kRGBResourceFormat = GL_RGBA;
+
+namespace cc {
+
+VideoFrameExternalResources::VideoFrameExternalResources() : type(NONE) {}
+
+VideoFrameExternalResources::~VideoFrameExternalResources() {}
+
+VideoResourceUpdater::VideoResourceUpdater(ResourceProvider* resource_provider)
+ : resource_provider_(resource_provider) {
+}
+
+VideoResourceUpdater::~VideoResourceUpdater() {
+ while (!all_resources_.empty()) {
+ resource_provider_->DeleteResource(all_resources_.back());
+ all_resources_.pop_back();
+ }
+}
+
+void VideoResourceUpdater::DeleteResource(unsigned resource_id) {
+ resource_provider_->DeleteResource(resource_id);
+ all_resources_.erase(std::remove(all_resources_.begin(),
+ all_resources_.end(),
+ resource_id));
+}
+
+VideoFrameExternalResources VideoResourceUpdater::
+ CreateExternalResourcesFromVideoFrame(
+ const scoped_refptr<media::VideoFrame>& video_frame) {
+ if (!VerifyFrame(video_frame))
+ return VideoFrameExternalResources();
+
+ if (video_frame->format() == media::VideoFrame::NATIVE_TEXTURE)
+ return CreateForHardwarePlanes(video_frame);
+ else
+ return CreateForSoftwarePlanes(video_frame);
+}
+
+bool VideoResourceUpdater::VerifyFrame(
+ const scoped_refptr<media::VideoFrame>& video_frame) {
+ // If these fail, we'll have to add logic that handles offset bitmap/texture
+ // UVs. For now, just expect (0, 0) offset, since all our decoders so far
+ // don't offset.
+ DCHECK_EQ(video_frame->visible_rect().x(), 0);
+ DCHECK_EQ(video_frame->visible_rect().y(), 0);
+
+ switch (video_frame->format()) {
+ // Acceptable inputs.
+ case media::VideoFrame::YV12:
+ case media::VideoFrame::YV12A:
+ case media::VideoFrame::YV16:
+ case media::VideoFrame::NATIVE_TEXTURE:
+#if defined(GOOGLE_TV)
+ case media::VideoFrame::HOLE:
+#endif
+ return true;
+
+ // Unacceptable inputs. ¯\(°_o)/¯
+ case media::VideoFrame::INVALID:
+ case media::VideoFrame::RGB32:
+ case media::VideoFrame::EMPTY:
+ case media::VideoFrame::I420:
+ break;
+ }
+ return false;
+}
+
+// For frames that we receive in software format, determine the dimensions of
+// each plane in the frame.
+static gfx::Size SoftwarePlaneDimension(
+ media::VideoFrame::Format input_frame_format,
+ gfx::Size coded_size,
+ GLenum output_resource_format,
+ int plane_index) {
+ if (output_resource_format == kYUVResourceFormat) {
+ if (plane_index == media::VideoFrame::kYPlane ||
+ plane_index == media::VideoFrame::kAPlane)
+ return coded_size;
+
+ switch (input_frame_format) {
+ case media::VideoFrame::YV12:
+ case media::VideoFrame::YV12A:
+ return gfx::ToFlooredSize(gfx::ScaleSize(coded_size, 0.5f, 0.5f));
+ case media::VideoFrame::YV16:
+ return gfx::ToFlooredSize(gfx::ScaleSize(coded_size, 0.5f, 1.f));
+
+ case media::VideoFrame::INVALID:
+ case media::VideoFrame::RGB32:
+ case media::VideoFrame::EMPTY:
+ case media::VideoFrame::I420:
+ case media::VideoFrame::NATIVE_TEXTURE:
+#if defined(GOOGLE_TV)
+ case media::VideoFrame::HOLE:
+#endif
+ NOTREACHED();
+ }
+ }
+
+ DCHECK_EQ(output_resource_format, static_cast<unsigned>(kRGBResourceFormat));
+ return coded_size;
+}
+
+VideoFrameExternalResources VideoResourceUpdater::CreateForSoftwarePlanes(
+ const scoped_refptr<media::VideoFrame>& video_frame) {
+ media::VideoFrame::Format input_frame_format = video_frame->format();
+
+#if defined(GOOGLE_TV)
+ if (input_frame_format == media::VideoFrame::HOLE) {
+ VideoFrameExternalResources external_resources;
+ external_resources.type = VideoFrameExternalResources::HOLE;
+ return external_resources;
+ }
+#endif
+
+ // Only YUV software video frames are supported.
+ DCHECK(input_frame_format == media::VideoFrame::YV12 ||
+ input_frame_format == media::VideoFrame::YV12A ||
+ input_frame_format == media::VideoFrame::YV16);
+ if (input_frame_format != media::VideoFrame::YV12 &&
+ input_frame_format != media::VideoFrame::YV12A &&
+ input_frame_format != media::VideoFrame::YV16)
+ return VideoFrameExternalResources();
+
+ bool software_compositor = !resource_provider_->GraphicsContext3D();
+
+ GLenum output_resource_format = kYUVResourceFormat;
+ size_t output_plane_count =
+ (input_frame_format == media::VideoFrame::YV12A) ? 4 : 3;
+
+ // TODO(skaslev): If we're in software compositing mode, we do the YUV -> RGB
+ // conversion here. That involves an extra copy of each frame to a bitmap.
+ // Obviously, this is suboptimal and should be addressed once ubercompositor
+ // starts shaping up.
+ if (software_compositor) {
+ output_resource_format = kRGBResourceFormat;
+ output_plane_count = 1;
+ }
+
+ int max_resource_size = resource_provider_->max_texture_size();
+ gfx::Size coded_frame_size = video_frame->coded_size();
+
+ std::vector<PlaneResource> plane_resources;
+ bool allocation_success = true;
+
+ for (size_t i = 0; i < output_plane_count; ++i) {
+ gfx::Size output_plane_resource_size =
+ SoftwarePlaneDimension(input_frame_format,
+ coded_frame_size,
+ output_resource_format,
+ i);
+ if (output_plane_resource_size.IsEmpty() ||
+ output_plane_resource_size.width() > max_resource_size ||
+ output_plane_resource_size.height() > max_resource_size) {
+ allocation_success = false;
+ break;
+ }
+
+ ResourceProvider::ResourceId resource_id = 0;
+ gpu::Mailbox mailbox;
+
+ // Try recycle a previously-allocated resource.
+ for (size_t i = 0; i < recycled_resources_.size(); ++i) {
+ if (recycled_resources_[i].resource_format == output_resource_format &&
+ recycled_resources_[i].resource_size == output_plane_resource_size) {
+ resource_id = recycled_resources_[i].resource_id;
+ mailbox = recycled_resources_[i].mailbox;
+ recycled_resources_.erase(recycled_resources_.begin() + i);
+ break;
+ }
+ }
+
+ if (resource_id == 0) {
+ // TODO(danakj): Abstract out hw/sw resource create/delete from
+ // ResourceProvider and stop using ResourceProvider in this class.
+ resource_id =
+ resource_provider_->CreateResource(output_plane_resource_size,
+ output_resource_format,
+ ResourceProvider::TextureUsageAny);
+
+ DCHECK(mailbox.IsZero());
+
+ if (!software_compositor) {
+ WebKit::WebGraphicsContext3D* context =
+ resource_provider_->GraphicsContext3D();
+ DCHECK(context);
+
+ GLC(context, context->genMailboxCHROMIUM(mailbox.name));
+ if (mailbox.IsZero()) {
+ resource_provider_->DeleteResource(resource_id);
+ resource_id = 0;
+ } else {
+ ResourceProvider::ScopedWriteLockGL lock(
+ resource_provider_, resource_id);
+ GLC(context, context->bindTexture(GL_TEXTURE_2D, lock.texture_id()));
+ GLC(context, context->produceTextureCHROMIUM(GL_TEXTURE_2D,
+ mailbox.name));
+ GLC(context, context->bindTexture(GL_TEXTURE_2D, 0));
+ }
+ }
+
+ if (resource_id)
+ all_resources_.push_back(resource_id);
+ }
+
+ if (resource_id == 0) {
+ allocation_success = false;
+ break;
+ }
+
+ DCHECK(software_compositor || !mailbox.IsZero());
+ plane_resources.push_back(PlaneResource(resource_id,
+ output_plane_resource_size,
+ output_resource_format,
+ mailbox));
+ }
+
+ if (!allocation_success) {
+ for (size_t i = 0; i < plane_resources.size(); ++i)
+ DeleteResource(plane_resources[i].resource_id);
+ return VideoFrameExternalResources();
+ }
+
+ VideoFrameExternalResources external_resources;
+
+ if (software_compositor) {
+ DCHECK_EQ(plane_resources.size(), 1u);
+ DCHECK_EQ(plane_resources[0].resource_format, kRGBResourceFormat);
+ DCHECK(plane_resources[0].mailbox.IsZero());
+
+ if (!video_renderer_)
+ video_renderer_.reset(new media::SkCanvasVideoRenderer);
+
+ {
+ ResourceProvider::ScopedWriteLockSoftware lock(
+ resource_provider_, plane_resources[0].resource_id);
+ video_renderer_->Paint(video_frame.get(),
+ lock.sk_canvas(),
+ video_frame->visible_rect(),
+ 0xff);
+ }
+
+ // In software mode, the resource provider won't be lost. Soon this callback
+ // will be called directly from the resource provider, same as 3d
+ // compositing mode, so this raw unretained resource_provider will always
+ // be valid when the callback is fired.
+ RecycleResourceData recycle_data = {
+ plane_resources[0].resource_id,
+ plane_resources[0].resource_size,
+ plane_resources[0].resource_format,
+ gpu::Mailbox()
+ };
+ TextureMailbox::ReleaseCallback callback_to_free_resource =
+ base::Bind(&RecycleResource,
+ AsWeakPtr(),
+ recycle_data);
+ external_resources.software_resources.push_back(
+ plane_resources[0].resource_id);
+ external_resources.software_release_callback = callback_to_free_resource;
+
+ external_resources.type = VideoFrameExternalResources::SOFTWARE_RESOURCE;
+ return external_resources;
+ }
+
+ for (size_t i = 0; i < plane_resources.size(); ++i) {
+ // Update each plane's resource id with its content.
+ DCHECK_EQ(plane_resources[i].resource_format,
+ static_cast<unsigned>(kYUVResourceFormat));
+
+ const uint8_t* input_plane_pixels = video_frame->data(i);
+
+ gfx::Rect image_rect(0,
+ 0,
+ video_frame->stride(i),
+ plane_resources[i].resource_size.height());
+ gfx::Rect source_rect(plane_resources[i].resource_size);
+ resource_provider_->SetPixels(plane_resources[i].resource_id,
+ input_plane_pixels,
+ image_rect,
+ source_rect,
+ gfx::Vector2d());
+
+ RecycleResourceData recycle_data = {
+ plane_resources[i].resource_id,
+ plane_resources[i].resource_size,
+ plane_resources[i].resource_format,
+ plane_resources[i].mailbox
+ };
+ TextureMailbox::ReleaseCallback callback_to_free_resource =
+ base::Bind(&RecycleResource,
+ AsWeakPtr(),
+ recycle_data);
+ external_resources.mailboxes.push_back(
+ TextureMailbox(plane_resources[i].mailbox,
+ callback_to_free_resource));
+ }
+
+ external_resources.type = VideoFrameExternalResources::YUV_RESOURCE;
+ return external_resources;
+}
+
+static void ReturnTexture(
+ scoped_refptr<media::VideoFrame::MailboxHolder> mailbox_holder,
+ unsigned sync_point,
+ bool lost_resource) {
+ mailbox_holder->Return(sync_point);
+}
+
+VideoFrameExternalResources VideoResourceUpdater::CreateForHardwarePlanes(
+ const scoped_refptr<media::VideoFrame>& video_frame) {
+ media::VideoFrame::Format frame_format = video_frame->format();
+
+ DCHECK_EQ(frame_format, media::VideoFrame::NATIVE_TEXTURE);
+ if (frame_format != media::VideoFrame::NATIVE_TEXTURE)
+ return VideoFrameExternalResources();
+
+ WebKit::WebGraphicsContext3D* context =
+ resource_provider_->GraphicsContext3D();
+ if (!context)
+ return VideoFrameExternalResources();
+
+ VideoFrameExternalResources external_resources;
+ switch (video_frame->texture_target()) {
+ case GL_TEXTURE_2D:
+ external_resources.type = VideoFrameExternalResources::RGB_RESOURCE;
+ break;
+ case GL_TEXTURE_EXTERNAL_OES:
+ external_resources.type =
+ VideoFrameExternalResources::STREAM_TEXTURE_RESOURCE;
+ break;
+ case GL_TEXTURE_RECTANGLE_ARB:
+ external_resources.type = VideoFrameExternalResources::IO_SURFACE;
+ break;
+ default:
+ NOTREACHED();
+ return VideoFrameExternalResources();
+ }
+
+ scoped_refptr<media::VideoFrame::MailboxHolder> mailbox_holder =
+ video_frame->texture_mailbox();
+
+ TextureMailbox::ReleaseCallback callback_to_return_resource =
+ base::Bind(&ReturnTexture, mailbox_holder);
+
+ external_resources.mailboxes.push_back(
+ TextureMailbox(mailbox_holder->mailbox(),
+ callback_to_return_resource,
+ video_frame->texture_target(),
+ mailbox_holder->sync_point()));
+ return external_resources;
+}
+
+// static
+void VideoResourceUpdater::RecycleResource(
+ base::WeakPtr<VideoResourceUpdater> updater,
+ RecycleResourceData data,
+ unsigned sync_point,
+ bool lost_resource) {
+ if (!updater.get()) {
+ // Resource was already deleted.
+ return;
+ }
+
+ WebKit::WebGraphicsContext3D* context =
+ updater->resource_provider_->GraphicsContext3D();
+ if (context && sync_point)
+ GLC(context, context->waitSyncPoint(sync_point));
+
+ if (lost_resource) {
+ updater->DeleteResource(data.resource_id);
+ return;
+ }
+
+ // Drop recycled resources that are the wrong format.
+ while (!updater->recycled_resources_.empty() &&
+ updater->recycled_resources_.back().resource_format !=
+ data.resource_format) {
+ updater->DeleteResource(updater->recycled_resources_.back().resource_id);
+ updater->recycled_resources_.pop_back();
+ }
+
+ PlaneResource recycled_resource(data.resource_id,
+ data.resource_size,
+ data.resource_format,
+ data.mailbox);
+ updater->recycled_resources_.push_back(recycled_resource);
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/video_resource_updater.h b/chromium/cc/resources/video_resource_updater.h
new file mode 100644
index 00000000000..9be79d12b33
--- /dev/null
+++ b/chromium/cc/resources/video_resource_updater.h
@@ -0,0 +1,116 @@
+// Copyright 2013 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.
+
+#ifndef CC_RESOURCES_VIDEO_RESOURCE_UPDATER_H_
+#define CC_RESOURCES_VIDEO_RESOURCE_UPDATER_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/resources/texture_mailbox.h"
+#include "ui/gfx/size.h"
+
+namespace media {
+class SkCanvasVideoRenderer;
+class VideoFrame;
+}
+
+namespace cc {
+class ResourceProvider;
+
+class CC_EXPORT VideoFrameExternalResources {
+ public:
+ // Specifies what type of data is contained in the mailboxes, as well as how
+ // many mailboxes will be present.
+ enum ResourceType {
+ NONE,
+ YUV_RESOURCE,
+ RGB_RESOURCE,
+ STREAM_TEXTURE_RESOURCE,
+ IO_SURFACE,
+
+#if defined(GOOGLE_TV)
+ // TODO(danakj): Implement this with a solid color layer instead of a video
+ // frame and video layer.
+ HOLE,
+#endif
+
+ // TODO(danakj): Remove this and abstract TextureMailbox into
+ // "ExternalResource" that can hold a hardware or software backing.
+ SOFTWARE_RESOURCE
+ };
+
+ ResourceType type;
+ std::vector<TextureMailbox> mailboxes;
+
+ // TODO(danakj): Remove these too.
+ std::vector<unsigned> software_resources;
+ TextureMailbox::ReleaseCallback software_release_callback;
+
+ VideoFrameExternalResources();
+ ~VideoFrameExternalResources();
+};
+
+// VideoResourceUpdater is by the video system to produce frame content as
+// resources consumable by the compositor.
+class CC_EXPORT VideoResourceUpdater
+ : public base::SupportsWeakPtr<VideoResourceUpdater> {
+ public:
+ explicit VideoResourceUpdater(ResourceProvider* resource_provider);
+ ~VideoResourceUpdater();
+
+ VideoFrameExternalResources CreateExternalResourcesFromVideoFrame(
+ const scoped_refptr<media::VideoFrame>& video_frame);
+
+ private:
+ struct PlaneResource {
+ unsigned resource_id;
+ gfx::Size resource_size;
+ unsigned resource_format;
+ gpu::Mailbox mailbox;
+
+ PlaneResource(unsigned resource_id,
+ gfx::Size resource_size,
+ unsigned resource_format,
+ gpu::Mailbox mailbox)
+ : resource_id(resource_id),
+ resource_size(resource_size),
+ resource_format(resource_format),
+ mailbox(mailbox) {}
+ };
+
+ void DeleteResource(unsigned resource_id);
+ bool VerifyFrame(const scoped_refptr<media::VideoFrame>& video_frame);
+ VideoFrameExternalResources CreateForHardwarePlanes(
+ const scoped_refptr<media::VideoFrame>& video_frame);
+ VideoFrameExternalResources CreateForSoftwarePlanes(
+ const scoped_refptr<media::VideoFrame>& video_frame);
+
+ struct RecycleResourceData {
+ unsigned resource_id;
+ gfx::Size resource_size;
+ unsigned resource_format;
+ gpu::Mailbox mailbox;
+ };
+ static void RecycleResource(base::WeakPtr<VideoResourceUpdater> updater,
+ RecycleResourceData data,
+ unsigned sync_point,
+ bool lost_resource);
+
+ ResourceProvider* resource_provider_;
+ scoped_ptr<media::SkCanvasVideoRenderer> video_renderer_;
+
+ std::vector<unsigned> all_resources_;
+ std::vector<PlaneResource> recycled_resources_;
+
+ DISALLOW_COPY_AND_ASSIGN(VideoResourceUpdater);
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_VIDEO_RESOURCE_UPDATER_H_
diff --git a/chromium/cc/resources/video_resource_updater_unittest.cc b/chromium/cc/resources/video_resource_updater_unittest.cc
new file mode 100644
index 00000000000..ca6ebd0d117
--- /dev/null
+++ b/chromium/cc/resources/video_resource_updater_unittest.cc
@@ -0,0 +1,79 @@
+// Copyright 2013 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 "cc/resources/video_resource_updater.h"
+
+#include "base/memory/shared_memory.h"
+#include "cc/debug/test_web_graphics_context_3d.h"
+#include "cc/resources/resource_provider.h"
+#include "cc/test/fake_output_surface.h"
+#include "media/base/video_frame.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+class VideoResourceUpdaterTest : public testing::Test {
+ protected:
+ VideoResourceUpdaterTest() {
+ scoped_ptr<TestWebGraphicsContext3D> context3d =
+ TestWebGraphicsContext3D::Create();
+ context3d_ = context3d.get();
+
+ output_surface3d_ = FakeOutputSurface::Create3d(
+ context3d.PassAs<WebKit::WebGraphicsContext3D>());
+ resource_provider3d_ =
+ ResourceProvider::Create(output_surface3d_.get(), 0);
+ }
+
+ scoped_refptr<media::VideoFrame> CreateTestYUVVideoFrame() {
+ const int kDimension = 10;
+ gfx::Size size(kDimension, kDimension);
+ static uint8 y_data[kDimension * kDimension] = { 0 };
+ static uint8 u_data[kDimension * kDimension / 2] = { 0 };
+ static uint8 v_data[kDimension * kDimension / 2] = { 0 };
+
+ return media::VideoFrame::WrapExternalYuvData(
+ media::VideoFrame::YV16, // format
+ size, // coded_size
+ gfx::Rect(size), // visible_rect
+ size, // natural_size
+ size.width(), // y_stride
+ size.width() / 2, // u_stride
+ size.width() / 2, // v_stride
+ y_data, // y_data
+ u_data, // u_data
+ v_data, // v_data
+ base::TimeDelta(), // timestamp,
+ base::Closure()); // no_longer_needed_cb
+ }
+
+ TestWebGraphicsContext3D* context3d_;
+ scoped_ptr<FakeOutputSurface> output_surface3d_;
+ scoped_ptr<ResourceProvider> resource_provider3d_;
+};
+
+TEST_F(VideoResourceUpdaterTest, SoftwareFrame) {
+ VideoResourceUpdater updater(resource_provider3d_.get());
+ scoped_refptr<media::VideoFrame> video_frame = CreateTestYUVVideoFrame();
+
+ VideoFrameExternalResources resources =
+ updater.CreateExternalResourcesFromVideoFrame(video_frame);
+ EXPECT_EQ(VideoFrameExternalResources::YUV_RESOURCE, resources.type);
+}
+
+TEST_F(VideoResourceUpdaterTest, LostContextForSoftwareFrame) {
+ VideoResourceUpdater updater(resource_provider3d_.get());
+ scoped_refptr<media::VideoFrame> video_frame = CreateTestYUVVideoFrame();
+
+ // Fail while creating the mailbox for the second YUV plane.
+ context3d_->set_times_gen_mailbox_succeeds(1);
+
+ VideoFrameExternalResources resources =
+ updater.CreateExternalResourcesFromVideoFrame(video_frame);
+ EXPECT_EQ(VideoFrameExternalResources::NONE, resources.type);
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/resources/worker_pool.cc b/chromium/cc/resources/worker_pool.cc
new file mode 100644
index 00000000000..dca0c704f09
--- /dev/null
+++ b/chromium/cc/resources/worker_pool.cc
@@ -0,0 +1,433 @@
+// Copyright 2013 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 "cc/resources/worker_pool.h"
+
+#include <algorithm>
+#include <queue>
+
+#include "base/bind.h"
+#include "base/containers/hash_tables.h"
+#include "base/debug/trace_event.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/threading/simple_thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "cc/base/scoped_ptr_deque.h"
+
+namespace cc {
+
+namespace internal {
+
+WorkerPoolTask::WorkerPoolTask()
+ : did_schedule_(false),
+ did_run_(false),
+ did_complete_(false) {
+}
+
+WorkerPoolTask::~WorkerPoolTask() {
+ DCHECK_EQ(did_schedule_, did_complete_);
+ DCHECK(!did_run_ || did_schedule_);
+ DCHECK(!did_run_ || did_complete_);
+}
+
+void WorkerPoolTask::DidSchedule() {
+ DCHECK(!did_complete_);
+ did_schedule_ = true;
+}
+
+void WorkerPoolTask::WillRun() {
+ DCHECK(did_schedule_);
+ DCHECK(!did_complete_);
+ DCHECK(!did_run_);
+}
+
+void WorkerPoolTask::DidRun() {
+ did_run_ = true;
+}
+
+void WorkerPoolTask::WillComplete() {
+ DCHECK(!did_complete_);
+}
+
+void WorkerPoolTask::DidComplete() {
+ DCHECK(did_schedule_);
+ DCHECK(!did_complete_);
+ did_complete_ = true;
+}
+
+bool WorkerPoolTask::HasFinishedRunning() const {
+ return did_run_;
+}
+
+bool WorkerPoolTask::HasCompleted() const {
+ return did_complete_;
+}
+
+GraphNode::GraphNode(internal::WorkerPoolTask* task, unsigned priority)
+ : task_(task),
+ priority_(priority),
+ num_dependencies_(0) {
+}
+
+GraphNode::~GraphNode() {
+}
+
+} // namespace internal
+
+// Internal to the worker pool. Any data or logic that needs to be
+// shared between threads lives in this class. All members are guarded
+// by |lock_|.
+class WorkerPool::Inner : public base::DelegateSimpleThread::Delegate {
+ public:
+ Inner(size_t num_threads, const std::string& thread_name_prefix);
+ virtual ~Inner();
+
+ void Shutdown();
+
+ // Schedule running of tasks in |graph|. Tasks previously scheduled but
+ // no longer needed will be canceled unless already running. Canceled
+ // tasks are moved to |completed_tasks_| without being run. The result
+ // is that once scheduled, a task is guaranteed to end up in the
+ // |completed_tasks_| queue even if they later get canceled by another
+ // call to SetTaskGraph().
+ void SetTaskGraph(TaskGraph* graph);
+
+ // Collect all completed tasks in |completed_tasks|.
+ void CollectCompletedTasks(TaskVector* completed_tasks);
+
+ private:
+ class PriorityComparator {
+ public:
+ bool operator()(const internal::GraphNode* a,
+ const internal::GraphNode* b) {
+ // In this system, numerically lower priority is run first.
+ if (a->priority() != b->priority())
+ return a->priority() > b->priority();
+
+ // Run task with most dependents first when priority is the same.
+ return a->dependents().size() < b->dependents().size();
+ }
+ };
+
+ // Overridden from base::DelegateSimpleThread:
+ virtual void Run() OVERRIDE;
+
+ // This lock protects all members of this class except
+ // |worker_pool_on_origin_thread_|. Do not read or modify anything
+ // without holding this lock. Do not block while holding this lock.
+ mutable base::Lock lock_;
+
+ // Condition variable that is waited on by worker threads until new
+ // tasks are ready to run or shutdown starts.
+ base::ConditionVariable has_ready_to_run_tasks_cv_;
+
+ // Provides each running thread loop with a unique index. First thread
+ // loop index is 0.
+ unsigned next_thread_index_;
+
+ // Set during shutdown. Tells workers to exit when no more tasks
+ // are pending.
+ bool shutdown_;
+
+ // This set contains all pending tasks.
+ GraphNodeMap pending_tasks_;
+
+ // Ordered set of tasks that are ready to run.
+ typedef std::priority_queue<internal::GraphNode*,
+ std::vector<internal::GraphNode*>,
+ PriorityComparator> TaskQueue;
+ TaskQueue ready_to_run_tasks_;
+
+ // This set contains all currently running tasks.
+ GraphNodeMap running_tasks_;
+
+ // Completed tasks not yet collected by origin thread.
+ TaskVector completed_tasks_;
+
+ ScopedPtrDeque<base::DelegateSimpleThread> workers_;
+
+ DISALLOW_COPY_AND_ASSIGN(Inner);
+};
+
+WorkerPool::Inner::Inner(
+ size_t num_threads, const std::string& thread_name_prefix)
+ : lock_(),
+ has_ready_to_run_tasks_cv_(&lock_),
+ next_thread_index_(0),
+ shutdown_(false) {
+ base::AutoLock lock(lock_);
+
+ while (workers_.size() < num_threads) {
+ scoped_ptr<base::DelegateSimpleThread> worker = make_scoped_ptr(
+ new base::DelegateSimpleThread(
+ this,
+ thread_name_prefix +
+ base::StringPrintf(
+ "Worker%u",
+ static_cast<unsigned>(workers_.size() + 1)).c_str()));
+ worker->Start();
+#if defined(OS_ANDROID) || defined(OS_LINUX)
+ worker->SetThreadPriority(base::kThreadPriority_Background);
+#endif
+ workers_.push_back(worker.Pass());
+ }
+}
+
+WorkerPool::Inner::~Inner() {
+ base::AutoLock lock(lock_);
+
+ DCHECK(shutdown_);
+
+ DCHECK_EQ(0u, pending_tasks_.size());
+ DCHECK_EQ(0u, ready_to_run_tasks_.size());
+ DCHECK_EQ(0u, running_tasks_.size());
+ DCHECK_EQ(0u, completed_tasks_.size());
+}
+
+void WorkerPool::Inner::Shutdown() {
+ {
+ base::AutoLock lock(lock_);
+
+ DCHECK(!shutdown_);
+ shutdown_ = true;
+
+ // Wake up a worker so it knows it should exit. This will cause all workers
+ // to exit as each will wake up another worker before exiting.
+ has_ready_to_run_tasks_cv_.Signal();
+ }
+
+ while (workers_.size()) {
+ scoped_ptr<base::DelegateSimpleThread> worker = workers_.take_front();
+ // http://crbug.com/240453 - Join() is considered IO and will block this
+ // thread. See also http://crbug.com/239423 for further ideas.
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+ worker->Join();
+ }
+}
+
+void WorkerPool::Inner::SetTaskGraph(TaskGraph* graph) {
+ // It is OK to call SetTaskGraph() after shutdown if |graph| is empty.
+ DCHECK(graph->empty() || !shutdown_);
+
+ GraphNodeMap new_pending_tasks;
+ GraphNodeMap new_running_tasks;
+ TaskQueue new_ready_to_run_tasks;
+
+ new_pending_tasks.swap(*graph);
+
+ {
+ base::AutoLock lock(lock_);
+
+ // First remove all completed tasks from |new_pending_tasks| and
+ // adjust number of dependencies.
+ for (TaskVector::iterator it = completed_tasks_.begin();
+ it != completed_tasks_.end(); ++it) {
+ internal::WorkerPoolTask* task = it->get();
+
+ scoped_ptr<internal::GraphNode> node = new_pending_tasks.take_and_erase(
+ task);
+ if (node) {
+ for (internal::GraphNode::Vector::const_iterator it =
+ node->dependents().begin();
+ it != node->dependents().end(); ++it) {
+ internal::GraphNode* dependent_node = *it;
+ dependent_node->remove_dependency();
+ }
+ }
+ }
+
+ // Build new running task set.
+ for (GraphNodeMap::iterator it = running_tasks_.begin();
+ it != running_tasks_.end(); ++it) {
+ internal::WorkerPoolTask* task = it->first;
+ // Transfer scheduled task value from |new_pending_tasks| to
+ // |new_running_tasks| if currently running. Value must be set to
+ // NULL if |new_pending_tasks| doesn't contain task. This does
+ // the right in both cases.
+ new_running_tasks.set(task, new_pending_tasks.take_and_erase(task));
+ }
+
+ // Build new "ready to run" tasks queue.
+ // TODO(reveman): Create this queue when building the task graph instead.
+ for (GraphNodeMap::iterator it = new_pending_tasks.begin();
+ it != new_pending_tasks.end(); ++it) {
+ internal::WorkerPoolTask* task = it->first;
+ DCHECK(task);
+ internal::GraphNode* node = it->second;
+
+ // Completed tasks should not exist in |new_pending_tasks|.
+ DCHECK(!task->HasFinishedRunning());
+
+ // Call DidSchedule() to indicate that this task has been scheduled.
+ // Note: This is only for debugging purposes.
+ task->DidSchedule();
+
+ if (!node->num_dependencies())
+ new_ready_to_run_tasks.push(node);
+
+ // Erase the task from old pending tasks.
+ pending_tasks_.erase(task);
+ }
+
+ completed_tasks_.reserve(completed_tasks_.size() + pending_tasks_.size());
+
+ // The items left in |pending_tasks_| need to be canceled.
+ for (GraphNodeMap::const_iterator it = pending_tasks_.begin();
+ it != pending_tasks_.end();
+ ++it) {
+ completed_tasks_.push_back(it->first);
+ }
+
+ // Swap task sets.
+ // Note: old tasks are intentionally destroyed after releasing |lock_|.
+ pending_tasks_.swap(new_pending_tasks);
+ running_tasks_.swap(new_running_tasks);
+ std::swap(ready_to_run_tasks_, new_ready_to_run_tasks);
+
+ // If |ready_to_run_tasks_| is empty, it means we either have
+ // running tasks, or we have no pending tasks.
+ DCHECK(!ready_to_run_tasks_.empty() ||
+ (pending_tasks_.empty() || !running_tasks_.empty()));
+
+ // If there is more work available, wake up worker thread.
+ if (!ready_to_run_tasks_.empty())
+ has_ready_to_run_tasks_cv_.Signal();
+ }
+}
+
+void WorkerPool::Inner::CollectCompletedTasks(TaskVector* completed_tasks) {
+ base::AutoLock lock(lock_);
+
+ DCHECK_EQ(0u, completed_tasks->size());
+ completed_tasks->swap(completed_tasks_);
+}
+
+void WorkerPool::Inner::Run() {
+ base::AutoLock lock(lock_);
+
+ // Get a unique thread index.
+ int thread_index = next_thread_index_++;
+
+ while (true) {
+ if (ready_to_run_tasks_.empty()) {
+ // Exit when shutdown is set and no more tasks are pending.
+ if (shutdown_ && pending_tasks_.empty())
+ break;
+
+ // Wait for more tasks.
+ has_ready_to_run_tasks_cv_.Wait();
+ continue;
+ }
+
+ // Take top priority task from |ready_to_run_tasks_|.
+ scoped_refptr<internal::WorkerPoolTask> task(
+ ready_to_run_tasks_.top()->task());
+ ready_to_run_tasks_.pop();
+
+ // Move task from |pending_tasks_| to |running_tasks_|.
+ DCHECK(pending_tasks_.contains(task.get()));
+ DCHECK(!running_tasks_.contains(task.get()));
+ running_tasks_.set(task.get(), pending_tasks_.take_and_erase(task.get()));
+
+ // There may be more work available, so wake up another worker thread.
+ has_ready_to_run_tasks_cv_.Signal();
+
+ // Call WillRun() before releasing |lock_| and running task.
+ task->WillRun();
+
+ {
+ base::AutoUnlock unlock(lock_);
+
+ task->RunOnWorkerThread(thread_index);
+ }
+
+ // This will mark task as finished running.
+ task->DidRun();
+
+ // Now iterate over all dependents to remove dependency and check
+ // if they are ready to run.
+ scoped_ptr<internal::GraphNode> node = running_tasks_.take_and_erase(
+ task.get());
+ if (node) {
+ for (internal::GraphNode::Vector::const_iterator it =
+ node->dependents().begin();
+ it != node->dependents().end(); ++it) {
+ internal::GraphNode* dependent_node = *it;
+
+ dependent_node->remove_dependency();
+ // Task is ready if it has no dependencies. Add it to
+ // |ready_to_run_tasks_|.
+ if (!dependent_node->num_dependencies())
+ ready_to_run_tasks_.push(dependent_node);
+ }
+ }
+
+ // Finally add task to |completed_tasks_|.
+ completed_tasks_.push_back(task);
+ }
+
+ // We noticed we should exit. Wake up the next worker so it knows it should
+ // exit as well (because the Shutdown() code only signals once).
+ has_ready_to_run_tasks_cv_.Signal();
+}
+
+WorkerPool::WorkerPool(size_t num_threads,
+ const std::string& thread_name_prefix)
+ : in_dispatch_completion_callbacks_(false),
+ inner_(make_scoped_ptr(new Inner(num_threads, thread_name_prefix))) {
+}
+
+WorkerPool::~WorkerPool() {
+}
+
+void WorkerPool::Shutdown() {
+ TRACE_EVENT0("cc", "WorkerPool::Shutdown");
+
+ DCHECK(!in_dispatch_completion_callbacks_);
+
+ inner_->Shutdown();
+}
+
+void WorkerPool::CheckForCompletedTasks() {
+ TRACE_EVENT0("cc", "WorkerPool::CheckForCompletedTasks");
+
+ DCHECK(!in_dispatch_completion_callbacks_);
+
+ TaskVector completed_tasks;
+ inner_->CollectCompletedTasks(&completed_tasks);
+ ProcessCompletedTasks(completed_tasks);
+}
+
+void WorkerPool::ProcessCompletedTasks(
+ const TaskVector& completed_tasks) {
+ TRACE_EVENT1("cc", "WorkerPool::ProcessCompletedTasks",
+ "completed_task_count", completed_tasks.size());
+
+ // Worker pool instance is not reentrant while processing completed tasks.
+ in_dispatch_completion_callbacks_ = true;
+
+ for (TaskVector::const_iterator it = completed_tasks.begin();
+ it != completed_tasks.end();
+ ++it) {
+ internal::WorkerPoolTask* task = it->get();
+
+ task->WillComplete();
+ task->CompleteOnOriginThread();
+ task->DidComplete();
+ }
+
+ in_dispatch_completion_callbacks_ = false;
+}
+
+void WorkerPool::SetTaskGraph(TaskGraph* graph) {
+ TRACE_EVENT1("cc", "WorkerPool::SetTaskGraph",
+ "num_tasks", graph->size());
+
+ DCHECK(!in_dispatch_completion_callbacks_);
+
+ inner_->SetTaskGraph(graph);
+}
+
+} // namespace cc
diff --git a/chromium/cc/resources/worker_pool.h b/chromium/cc/resources/worker_pool.h
new file mode 100644
index 00000000000..c26ed077eb6
--- /dev/null
+++ b/chromium/cc/resources/worker_pool.h
@@ -0,0 +1,143 @@
+// Copyright 2013 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.
+
+#ifndef CC_RESOURCES_WORKER_POOL_H_
+#define CC_RESOURCES_WORKER_POOL_H_
+
+#include <deque>
+#include <string>
+#include <vector>
+
+#include "base/cancelable_callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "cc/base/cc_export.h"
+#include "cc/base/scoped_ptr_hash_map.h"
+
+namespace cc {
+namespace internal {
+
+class CC_EXPORT WorkerPoolTask
+ : public base::RefCountedThreadSafe<WorkerPoolTask> {
+ public:
+ virtual void RunOnWorkerThread(unsigned thread_index) = 0;
+ virtual void CompleteOnOriginThread() = 0;
+
+ void DidSchedule();
+ void WillRun();
+ void DidRun();
+ void WillComplete();
+ void DidComplete();
+
+ bool HasFinishedRunning() const;
+ bool HasCompleted() const;
+
+ protected:
+ friend class base::RefCountedThreadSafe<WorkerPoolTask>;
+
+ WorkerPoolTask();
+ virtual ~WorkerPoolTask();
+
+ private:
+ bool did_schedule_;
+ bool did_run_;
+ bool did_complete_;
+};
+
+class CC_EXPORT GraphNode {
+ public:
+ typedef std::vector<GraphNode*> Vector;
+
+ GraphNode(internal::WorkerPoolTask* task, unsigned priority);
+ ~GraphNode();
+
+ WorkerPoolTask* task() { return task_; }
+
+ void add_dependent(GraphNode* dependent) {
+ DCHECK(dependent);
+ dependents_.push_back(dependent);
+ }
+ const Vector& dependents() const { return dependents_; }
+
+ unsigned priority() const { return priority_; }
+
+ unsigned num_dependencies() const { return num_dependencies_; }
+ void add_dependency() { ++num_dependencies_; }
+ void remove_dependency() {
+ DCHECK(num_dependencies_);
+ --num_dependencies_;
+ }
+
+ private:
+ WorkerPoolTask* task_;
+ Vector dependents_;
+ unsigned priority_;
+ unsigned num_dependencies_;
+
+ DISALLOW_COPY_AND_ASSIGN(GraphNode);
+};
+
+} // namespace internal
+} // namespace cc
+
+#if defined(COMPILER_GCC)
+namespace BASE_HASH_NAMESPACE {
+template <> struct hash<cc::internal::WorkerPoolTask*> {
+ size_t operator()(cc::internal::WorkerPoolTask* ptr) const {
+ return hash<size_t>()(reinterpret_cast<size_t>(ptr));
+ }
+};
+} // namespace BASE_HASH_NAMESPACE
+#endif // COMPILER
+
+namespace cc {
+
+// A worker thread pool that runs tasks provided by task graph and
+// guarantees completion of all pending tasks at shutdown.
+class CC_EXPORT WorkerPool {
+ public:
+ virtual ~WorkerPool();
+
+ // Tells the worker pool to shutdown and returns once all pending tasks have
+ // completed.
+ virtual void Shutdown();
+
+ // Force a check for completed tasks.
+ virtual void CheckForCompletedTasks();
+
+ protected:
+ // A task graph contains a unique set of tasks with edges between
+ // dependencies pointing in the direction of the dependents. Each task
+ // need to be assigned a unique priority and a run count that matches
+ // the number of dependencies.
+ typedef ScopedPtrHashMap<internal::WorkerPoolTask*, internal::GraphNode>
+ GraphNodeMap;
+ typedef GraphNodeMap TaskGraph;
+
+ WorkerPool(size_t num_threads, const std::string& thread_name_prefix);
+
+ // Schedule running of tasks in |graph|. Any previously scheduled tasks
+ // that are not already running will be canceled. Canceled tasks don't run
+ // but completion of them is still processed.
+ void SetTaskGraph(TaskGraph* graph);
+
+ private:
+ class Inner;
+ friend class Inner;
+
+ typedef std::vector<scoped_refptr<internal::WorkerPoolTask> > TaskVector;
+
+ void ProcessCompletedTasks(const TaskVector& completed_tasks);
+
+ bool in_dispatch_completion_callbacks_;
+
+ // Hide the gory details of the worker pool in |inner_|.
+ const scoped_ptr<Inner> inner_;
+};
+
+} // namespace cc
+
+#endif // CC_RESOURCES_WORKER_POOL_H_
diff --git a/chromium/cc/resources/worker_pool_perftest.cc b/chromium/cc/resources/worker_pool_perftest.cc
new file mode 100644
index 00000000000..aee1f24a27d
--- /dev/null
+++ b/chromium/cc/resources/worker_pool_perftest.cc
@@ -0,0 +1,251 @@
+// Copyright 2013 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 "cc/resources/worker_pool.h"
+
+#include "base/time/time.h"
+#include "cc/base/completion_event.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+
+namespace {
+
+static const int kTimeLimitMillis = 2000;
+static const int kWarmupRuns = 5;
+static const int kTimeCheckInterval = 10;
+
+class PerfWorkerPoolTaskImpl : public internal::WorkerPoolTask {
+ public:
+ // Overridden from internal::WorkerPoolTask:
+ virtual void RunOnWorkerThread(unsigned thread_index) OVERRIDE {}
+ virtual void CompleteOnOriginThread() OVERRIDE {}
+
+ private:
+ virtual ~PerfWorkerPoolTaskImpl() {}
+};
+
+class PerfControlWorkerPoolTaskImpl : public internal::WorkerPoolTask {
+ public:
+ PerfControlWorkerPoolTaskImpl() : did_start_(new CompletionEvent),
+ can_finish_(new CompletionEvent) {}
+
+ // Overridden from internal::WorkerPoolTask:
+ virtual void RunOnWorkerThread(unsigned thread_index) OVERRIDE {
+ did_start_->Signal();
+ can_finish_->Wait();
+ }
+ virtual void CompleteOnOriginThread() OVERRIDE {}
+
+ void WaitForTaskToStartRunning() {
+ did_start_->Wait();
+ }
+
+ void AllowTaskToFinish() {
+ can_finish_->Signal();
+ }
+
+ private:
+ virtual ~PerfControlWorkerPoolTaskImpl() {}
+
+ scoped_ptr<CompletionEvent> did_start_;
+ scoped_ptr<CompletionEvent> can_finish_;
+
+ DISALLOW_COPY_AND_ASSIGN(PerfControlWorkerPoolTaskImpl);
+};
+
+class PerfWorkerPool : public WorkerPool {
+ public:
+ PerfWorkerPool() : WorkerPool(1, "test") {}
+ virtual ~PerfWorkerPool() {}
+
+ static scoped_ptr<PerfWorkerPool> Create() {
+ return make_scoped_ptr(new PerfWorkerPool);
+ }
+
+ void ScheduleTasks(internal::WorkerPoolTask* root_task,
+ internal::WorkerPoolTask* leaf_task,
+ unsigned max_depth,
+ unsigned num_children_per_node) {
+ TaskVector tasks;
+ TaskGraph graph;
+
+ scoped_ptr<internal::GraphNode> root_node;
+ if (root_task)
+ root_node = make_scoped_ptr(new internal::GraphNode(root_task, 0u));
+
+ scoped_ptr<internal::GraphNode> leaf_node;
+ if (leaf_task)
+ leaf_node = make_scoped_ptr(new internal::GraphNode(leaf_task, 0u));
+
+ if (max_depth) {
+ BuildTaskGraph(&tasks,
+ &graph,
+ root_node.get(),
+ leaf_node.get(),
+ 0,
+ max_depth,
+ num_children_per_node);
+ }
+
+ if (leaf_node)
+ graph.set(leaf_task, leaf_node.Pass());
+
+ if (root_node)
+ graph.set(root_task, root_node.Pass());
+
+ SetTaskGraph(&graph);
+
+ tasks_.swap(tasks);
+ }
+
+ private:
+ typedef std::vector<scoped_refptr<internal::WorkerPoolTask> > TaskVector;
+
+ void BuildTaskGraph(TaskVector* tasks,
+ TaskGraph* graph,
+ internal::GraphNode* dependent_node,
+ internal::GraphNode* leaf_node,
+ unsigned current_depth,
+ unsigned max_depth,
+ unsigned num_children_per_node) {
+ scoped_refptr<PerfWorkerPoolTaskImpl> task(new PerfWorkerPoolTaskImpl);
+ scoped_ptr<internal::GraphNode> node(
+ new internal::GraphNode(task.get(), 0u));
+
+ if (current_depth < max_depth) {
+ for (unsigned i = 0; i < num_children_per_node; ++i) {
+ BuildTaskGraph(tasks,
+ graph,
+ node.get(),
+ leaf_node,
+ current_depth + 1,
+ max_depth,
+ num_children_per_node);
+ }
+ } else if (leaf_node) {
+ leaf_node->add_dependent(node.get());
+ node->add_dependency();
+ }
+
+ if (dependent_node) {
+ node->add_dependent(dependent_node);
+ dependent_node->add_dependency();
+ }
+ graph->set(task.get(), node.Pass());
+ tasks->push_back(task.get());
+ }
+
+ TaskVector tasks_;
+
+ DISALLOW_COPY_AND_ASSIGN(PerfWorkerPool);
+};
+
+class WorkerPoolPerfTest : public testing::Test {
+ public:
+ WorkerPoolPerfTest() : num_runs_(0) {}
+
+ // Overridden from testing::Test:
+ virtual void SetUp() OVERRIDE {
+ worker_pool_ = PerfWorkerPool::Create();
+ }
+ virtual void TearDown() OVERRIDE {
+ worker_pool_->Shutdown();
+ worker_pool_->CheckForCompletedTasks();
+ }
+
+ void EndTest() {
+ elapsed_ = base::TimeTicks::HighResNow() - start_time_;
+ }
+
+ void AfterTest(const std::string test_name) {
+ // Format matches chrome/test/perf/perf_test.h:PrintResult
+ printf("*RESULT %s: %.2f runs/s\n",
+ test_name.c_str(),
+ num_runs_ / elapsed_.InSecondsF());
+ }
+
+ bool DidRun() {
+ ++num_runs_;
+ if (num_runs_ == kWarmupRuns)
+ start_time_ = base::TimeTicks::HighResNow();
+
+ if (!start_time_.is_null() && (num_runs_ % kTimeCheckInterval) == 0) {
+ base::TimeDelta elapsed = base::TimeTicks::HighResNow() - start_time_;
+ if (elapsed >= base::TimeDelta::FromMilliseconds(kTimeLimitMillis)) {
+ elapsed_ = elapsed;
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ void RunScheduleTasksTest(const std::string test_name,
+ unsigned max_depth,
+ unsigned num_children_per_node) {
+ start_time_ = base::TimeTicks();
+ num_runs_ = 0;
+ do {
+ scoped_refptr<PerfControlWorkerPoolTaskImpl> leaf_task(
+ new PerfControlWorkerPoolTaskImpl);
+ worker_pool_->ScheduleTasks(
+ NULL, leaf_task.get(), max_depth, num_children_per_node);
+ leaf_task->WaitForTaskToStartRunning();
+ worker_pool_->ScheduleTasks(NULL, NULL, 0, 0);
+ worker_pool_->CheckForCompletedTasks();
+ leaf_task->AllowTaskToFinish();
+ } while (DidRun());
+
+ AfterTest(test_name);
+ }
+
+ void RunExecuteTasksTest(const std::string test_name,
+ unsigned max_depth,
+ unsigned num_children_per_node) {
+ start_time_ = base::TimeTicks();
+ num_runs_ = 0;
+ do {
+ scoped_refptr<PerfControlWorkerPoolTaskImpl> root_task(
+ new PerfControlWorkerPoolTaskImpl);
+ worker_pool_->ScheduleTasks(
+ root_task.get(), NULL, max_depth, num_children_per_node);
+ root_task->WaitForTaskToStartRunning();
+ root_task->AllowTaskToFinish();
+ worker_pool_->CheckForCompletedTasks();
+ } while (DidRun());
+
+ AfterTest(test_name);
+ }
+
+ protected:
+ scoped_ptr<PerfWorkerPool> worker_pool_;
+ base::TimeTicks start_time_;
+ base::TimeDelta elapsed_;
+ int num_runs_;
+};
+
+TEST_F(WorkerPoolPerfTest, ScheduleTasks) {
+ RunScheduleTasksTest("schedule_tasks_1_10", 1, 10);
+ RunScheduleTasksTest("schedule_tasks_1_1000", 1, 1000);
+ RunScheduleTasksTest("schedule_tasks_2_10", 2, 10);
+ RunScheduleTasksTest("schedule_tasks_5_5", 5, 5);
+ RunScheduleTasksTest("schedule_tasks_10_2", 10, 2);
+ RunScheduleTasksTest("schedule_tasks_1000_1", 1000, 1);
+ RunScheduleTasksTest("schedule_tasks_10_1", 10, 1);
+}
+
+TEST_F(WorkerPoolPerfTest, ExecuteTasks) {
+ RunExecuteTasksTest("execute_tasks_1_10", 1, 10);
+ RunExecuteTasksTest("execute_tasks_1_1000", 1, 1000);
+ RunExecuteTasksTest("execute_tasks_2_10", 2, 10);
+ RunExecuteTasksTest("execute_tasks_5_5", 5, 5);
+ RunExecuteTasksTest("execute_tasks_10_2", 10, 2);
+ RunExecuteTasksTest("execute_tasks_1000_1", 1000, 1);
+ RunExecuteTasksTest("execute_tasks_10_1", 10, 1);
+}
+
+} // namespace
+
+} // namespace cc
diff --git a/chromium/cc/resources/worker_pool_unittest.cc b/chromium/cc/resources/worker_pool_unittest.cc
new file mode 100644
index 00000000000..f7c5a951df6
--- /dev/null
+++ b/chromium/cc/resources/worker_pool_unittest.cc
@@ -0,0 +1,394 @@
+// Copyright 2013 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 "cc/resources/worker_pool.h"
+
+#include <vector>
+
+#include "cc/base/completion_event.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+
+namespace {
+
+class FakeWorkerPoolTaskImpl : public internal::WorkerPoolTask {
+ public:
+ FakeWorkerPoolTaskImpl(const base::Closure& callback,
+ const base::Closure& reply)
+ : callback_(callback),
+ reply_(reply) {
+ }
+
+ // Overridden from internal::WorkerPoolTask:
+ virtual void RunOnWorkerThread(unsigned thread_index) OVERRIDE {
+ if (!callback_.is_null())
+ callback_.Run();
+ }
+ virtual void CompleteOnOriginThread() OVERRIDE {
+ if (!reply_.is_null())
+ reply_.Run();
+ }
+
+ private:
+ virtual ~FakeWorkerPoolTaskImpl() {}
+
+ const base::Closure callback_;
+ const base::Closure reply_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeWorkerPoolTaskImpl);
+};
+
+class FakeWorkerPool : public WorkerPool {
+ public:
+ struct Task {
+ Task(const base::Closure& callback,
+ const base::Closure& reply,
+ const base::Closure& dependent,
+ unsigned dependent_count,
+ unsigned priority) : callback(callback),
+ reply(reply),
+ dependent(dependent),
+ dependent_count(dependent_count),
+ priority(priority) {
+ }
+
+ base::Closure callback;
+ base::Closure reply;
+ base::Closure dependent;
+ unsigned dependent_count;
+ unsigned priority;
+ };
+ FakeWorkerPool() : WorkerPool(1, "test") {}
+ virtual ~FakeWorkerPool() {}
+
+ static scoped_ptr<FakeWorkerPool> Create() {
+ return make_scoped_ptr(new FakeWorkerPool);
+ }
+
+ void ScheduleTasks(const std::vector<Task>& tasks) {
+ TaskVector new_tasks;
+ TaskVector new_dependents;
+ TaskGraph new_graph;
+
+ scoped_refptr<FakeWorkerPoolTaskImpl> new_completion_task(
+ new FakeWorkerPoolTaskImpl(
+ base::Bind(&FakeWorkerPool::OnTasksCompleted,
+ base::Unretained(this)),
+ base::Closure()));
+ scoped_ptr<internal::GraphNode> completion_node(
+ new internal::GraphNode(new_completion_task.get(), 0u));
+
+ for (std::vector<Task>::const_iterator it = tasks.begin();
+ it != tasks.end(); ++it) {
+ scoped_refptr<FakeWorkerPoolTaskImpl> new_task(
+ new FakeWorkerPoolTaskImpl(it->callback, it->reply));
+ scoped_ptr<internal::GraphNode> node(
+ new internal::GraphNode(new_task.get(), it->priority));
+
+ DCHECK(it->dependent_count);
+ for (unsigned i = 0; i < it->dependent_count; ++i) {
+ scoped_refptr<FakeWorkerPoolTaskImpl> new_dependent_task(
+ new FakeWorkerPoolTaskImpl(it->dependent, base::Closure()));
+ scoped_ptr<internal::GraphNode> dependent_node(
+ new internal::GraphNode(new_dependent_task.get(), it->priority));
+ dependent_node->add_dependent(completion_node.get());
+ completion_node->add_dependency();
+ node->add_dependent(dependent_node.get());
+ dependent_node->add_dependency();
+ new_graph.set(new_dependent_task.get(), dependent_node.Pass());
+ new_dependents.push_back(new_dependent_task.get());
+ }
+
+ new_graph.set(new_task.get(), node.Pass());
+ new_tasks.push_back(new_task.get());
+ }
+
+ new_graph.set(new_completion_task.get(), completion_node.Pass());
+
+ scheduled_tasks_completion_.reset(new CompletionEvent);
+
+ SetTaskGraph(&new_graph);
+
+ dependents_.swap(new_dependents);
+ completion_task_.swap(new_completion_task);
+ tasks_.swap(new_tasks);
+ }
+
+ void WaitForTasksToComplete() {
+ DCHECK(scheduled_tasks_completion_);
+ scheduled_tasks_completion_->Wait();
+ }
+
+ private:
+ typedef std::vector<scoped_refptr<internal::WorkerPoolTask> > TaskVector;
+
+ void OnTasksCompleted() {
+ DCHECK(scheduled_tasks_completion_);
+ scheduled_tasks_completion_->Signal();
+ }
+
+ TaskVector tasks_;
+ TaskVector dependents_;
+ scoped_refptr<FakeWorkerPoolTaskImpl> completion_task_;
+ scoped_ptr<CompletionEvent> scheduled_tasks_completion_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeWorkerPool);
+};
+
+class WorkerPoolTest : public testing::Test {
+ public:
+ WorkerPoolTest() {}
+ virtual ~WorkerPoolTest() {}
+
+ // Overridden from testing::Test:
+ virtual void SetUp() OVERRIDE {
+ worker_pool_ = FakeWorkerPool::Create();
+ }
+ virtual void TearDown() OVERRIDE {
+ worker_pool_->Shutdown();
+ worker_pool_->CheckForCompletedTasks();
+ }
+
+ void ResetIds() {
+ run_task_ids_.clear();
+ on_task_completed_ids_.clear();
+ }
+
+ void RunAllTasks() {
+ worker_pool_->WaitForTasksToComplete();
+ worker_pool_->CheckForCompletedTasks();
+ }
+
+ FakeWorkerPool* worker_pool() {
+ return worker_pool_.get();
+ }
+
+ void RunTask(unsigned id) {
+ run_task_ids_.push_back(id);
+ }
+
+ void OnTaskCompleted(unsigned id) {
+ on_task_completed_ids_.push_back(id);
+ }
+
+ const std::vector<unsigned>& run_task_ids() {
+ return run_task_ids_;
+ }
+
+ const std::vector<unsigned>& on_task_completed_ids() {
+ return on_task_completed_ids_;
+ }
+
+ private:
+ scoped_ptr<FakeWorkerPool> worker_pool_;
+ std::vector<unsigned> run_task_ids_;
+ std::vector<unsigned> on_task_completed_ids_;
+};
+
+TEST_F(WorkerPoolTest, Basic) {
+ EXPECT_EQ(0u, run_task_ids().size());
+ EXPECT_EQ(0u, on_task_completed_ids().size());
+
+ worker_pool()->ScheduleTasks(
+ std::vector<FakeWorkerPool::Task>(
+ 1,
+ FakeWorkerPool::Task(base::Bind(&WorkerPoolTest::RunTask,
+ base::Unretained(this),
+ 0u),
+ base::Bind(&WorkerPoolTest::OnTaskCompleted,
+ base::Unretained(this),
+ 0u),
+ base::Closure(),
+ 1u,
+ 0u)));
+ RunAllTasks();
+
+ EXPECT_EQ(1u, run_task_ids().size());
+ EXPECT_EQ(1u, on_task_completed_ids().size());
+
+ worker_pool()->ScheduleTasks(
+ std::vector<FakeWorkerPool::Task>(
+ 1,
+ FakeWorkerPool::Task(base::Bind(&WorkerPoolTest::RunTask,
+ base::Unretained(this),
+ 0u),
+ base::Bind(&WorkerPoolTest::OnTaskCompleted,
+ base::Unretained(this),
+ 0u),
+ base::Bind(&WorkerPoolTest::RunTask,
+ base::Unretained(this),
+ 0u),
+ 1u,
+ 0u)));
+ RunAllTasks();
+
+ EXPECT_EQ(3u, run_task_ids().size());
+ EXPECT_EQ(2u, on_task_completed_ids().size());
+
+ worker_pool()->ScheduleTasks(
+ std::vector<FakeWorkerPool::Task>(
+ 1, FakeWorkerPool::Task(base::Bind(&WorkerPoolTest::RunTask,
+ base::Unretained(this),
+ 0u),
+ base::Bind(&WorkerPoolTest::OnTaskCompleted,
+ base::Unretained(this),
+ 0u),
+ base::Bind(&WorkerPoolTest::RunTask,
+ base::Unretained(this),
+ 0u),
+ 2u,
+ 0u)));
+ RunAllTasks();
+
+ EXPECT_EQ(6u, run_task_ids().size());
+ EXPECT_EQ(3u, on_task_completed_ids().size());
+}
+
+TEST_F(WorkerPoolTest, Dependencies) {
+ worker_pool()->ScheduleTasks(
+ std::vector<FakeWorkerPool::Task>(
+ 1, FakeWorkerPool::Task(base::Bind(&WorkerPoolTest::RunTask,
+ base::Unretained(this),
+ 0u),
+ base::Bind(&WorkerPoolTest::OnTaskCompleted,
+ base::Unretained(this),
+ 0u),
+ base::Bind(&WorkerPoolTest::RunTask,
+ base::Unretained(this),
+ 1u),
+ 1u,
+ 0u)));
+ RunAllTasks();
+
+ // Check if task ran before dependent.
+ ASSERT_EQ(2u, run_task_ids().size());
+ EXPECT_EQ(0u, run_task_ids()[0]);
+ EXPECT_EQ(1u, run_task_ids()[1]);
+ ASSERT_EQ(1u, on_task_completed_ids().size());
+ EXPECT_EQ(0u, on_task_completed_ids()[0]);
+
+ worker_pool()->ScheduleTasks(
+ std::vector<FakeWorkerPool::Task>(
+ 1, FakeWorkerPool::Task(base::Bind(&WorkerPoolTest::RunTask,
+ base::Unretained(this),
+ 2u),
+ base::Bind(&WorkerPoolTest::OnTaskCompleted,
+ base::Unretained(this),
+ 2u),
+ base::Bind(&WorkerPoolTest::RunTask,
+ base::Unretained(this),
+ 3u),
+ 2u,
+ 0u)));
+ RunAllTasks();
+
+ // Task should only run once.
+ ASSERT_EQ(5u, run_task_ids().size());
+ EXPECT_EQ(2u, run_task_ids()[2]);
+ EXPECT_EQ(3u, run_task_ids()[3]);
+ EXPECT_EQ(3u, run_task_ids()[4]);
+ ASSERT_EQ(2u, on_task_completed_ids().size());
+ EXPECT_EQ(2u, on_task_completed_ids()[1]);
+}
+
+TEST_F(WorkerPoolTest, Priority) {
+ {
+ FakeWorkerPool::Task tasks[] = {
+ FakeWorkerPool::Task(base::Bind(&WorkerPoolTest::RunTask,
+ base::Unretained(this),
+ 0u),
+ base::Bind(&WorkerPoolTest::OnTaskCompleted,
+ base::Unretained(this),
+ 0u),
+ base::Bind(&WorkerPoolTest::RunTask,
+ base::Unretained(this),
+ 2u),
+ 1u,
+ 1u), // Priority 1
+ FakeWorkerPool::Task(base::Bind(&WorkerPoolTest::RunTask,
+ base::Unretained(this),
+ 1u),
+ base::Bind(&WorkerPoolTest::OnTaskCompleted,
+ base::Unretained(this),
+ 1u),
+ base::Bind(&WorkerPoolTest::RunTask,
+ base::Unretained(this),
+ 3u),
+ 1u,
+ 0u) // Priority 0
+ };
+ worker_pool()->ScheduleTasks(
+ std::vector<FakeWorkerPool::Task>(tasks, tasks + arraysize(tasks)));
+ }
+ RunAllTasks();
+
+ // Check if tasks ran in order of priority.
+ ASSERT_EQ(4u, run_task_ids().size());
+ EXPECT_EQ(1u, run_task_ids()[0]);
+ EXPECT_EQ(3u, run_task_ids()[1]);
+ EXPECT_EQ(0u, run_task_ids()[2]);
+ EXPECT_EQ(2u, run_task_ids()[3]);
+ ASSERT_EQ(2u, on_task_completed_ids().size());
+ EXPECT_EQ(1u, on_task_completed_ids()[0]);
+ EXPECT_EQ(0u, on_task_completed_ids()[1]);
+
+ ResetIds();
+ {
+ std::vector<FakeWorkerPool::Task> tasks;
+ tasks.push_back(
+ FakeWorkerPool::Task(base::Bind(&WorkerPoolTest::RunTask,
+ base::Unretained(this),
+ 0u),
+ base::Bind(&WorkerPoolTest::OnTaskCompleted,
+ base::Unretained(this),
+ 0u),
+ base::Bind(&WorkerPoolTest::RunTask,
+ base::Unretained(this),
+ 3u),
+ 1u, // 1 dependent
+ 1u)); // Priority 1
+ tasks.push_back(
+ FakeWorkerPool::Task(base::Bind(&WorkerPoolTest::RunTask,
+ base::Unretained(this),
+ 1u),
+ base::Bind(&WorkerPoolTest::OnTaskCompleted,
+ base::Unretained(this),
+ 1u),
+ base::Bind(&WorkerPoolTest::RunTask,
+ base::Unretained(this),
+ 4u),
+ 2u, // 2 dependents
+ 1u)); // Priority 1
+ tasks.push_back(
+ FakeWorkerPool::Task(base::Bind(&WorkerPoolTest::RunTask,
+ base::Unretained(this),
+ 2u),
+ base::Bind(&WorkerPoolTest::OnTaskCompleted,
+ base::Unretained(this),
+ 2u),
+ base::Bind(&WorkerPoolTest::RunTask,
+ base::Unretained(this),
+ 5u),
+ 1u, // 1 dependent
+ 0u)); // Priority 0
+ worker_pool()->ScheduleTasks(tasks);
+ }
+ RunAllTasks();
+
+ // Check if tasks ran in order of priority and that task with more
+ // dependents ran first when priority is the same.
+ ASSERT_LE(3u, run_task_ids().size());
+ EXPECT_EQ(2u, run_task_ids()[0]);
+ EXPECT_EQ(5u, run_task_ids()[1]);
+ EXPECT_EQ(1u, run_task_ids()[2]);
+ ASSERT_EQ(3u, on_task_completed_ids().size());
+ EXPECT_EQ(2u, on_task_completed_ids()[0]);
+ EXPECT_EQ(1u, on_task_completed_ids()[1]);
+ EXPECT_EQ(0u, on_task_completed_ids()[2]);
+}
+
+} // namespace
+
+} // namespace cc
diff --git a/chromium/cc/scheduler/delay_based_time_source.cc b/chromium/cc/scheduler/delay_based_time_source.cc
new file mode 100644
index 00000000000..a5b6f738df8
--- /dev/null
+++ b/chromium/cc/scheduler/delay_based_time_source.cc
@@ -0,0 +1,253 @@
+// Copyright 2011 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 "cc/scheduler/delay_based_time_source.h"
+
+#include <algorithm>
+#include <cmath>
+
+#include "base/bind.h"
+#include "base/debug/trace_event.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/single_thread_task_runner.h"
+
+namespace cc {
+
+namespace {
+
+// kDoubleTickDivisor prevents ticks from running within the specified
+// fraction of an interval. This helps account for jitter in the timebase as
+// well as quick timer reactivation.
+static const int kDoubleTickDivisor = 2;
+
+// kIntervalChangeThreshold is the fraction of the interval that will trigger an
+// immediate interval change. kPhaseChangeThreshold is the fraction of the
+// interval that will trigger an immediate phase change. If the changes are
+// within the thresholds, the change will take place on the next tick. If
+// either change is outside the thresholds, the next tick will be canceled and
+// reissued immediately.
+static const double kIntervalChangeThreshold = 0.25;
+static const double kPhaseChangeThreshold = 0.25;
+
+} // namespace
+
+scoped_refptr<DelayBasedTimeSource> DelayBasedTimeSource::Create(
+ base::TimeDelta interval,
+ base::SingleThreadTaskRunner* task_runner) {
+ return make_scoped_refptr(new DelayBasedTimeSource(interval, task_runner));
+}
+
+DelayBasedTimeSource::DelayBasedTimeSource(
+ base::TimeDelta interval, base::SingleThreadTaskRunner* task_runner)
+ : client_(NULL),
+ last_tick_time_(base::TimeTicks() - interval),
+ current_parameters_(interval, base::TimeTicks()),
+ next_parameters_(interval, base::TimeTicks()),
+ active_(false),
+ task_runner_(task_runner),
+ weak_factory_(this) {}
+
+DelayBasedTimeSource::~DelayBasedTimeSource() {}
+
+base::TimeTicks DelayBasedTimeSource::SetActive(bool active) {
+ TRACE_EVENT1("cc", "DelayBasedTimeSource::SetActive", "active", active);
+ if (active == active_)
+ return base::TimeTicks();
+ active_ = active;
+
+ if (!active_) {
+ weak_factory_.InvalidateWeakPtrs();
+ return base::TimeTicks();
+ }
+
+ PostNextTickTask(Now());
+
+ // Determine if there was a tick that was missed while not active.
+ base::TimeTicks last_tick_time_if_always_active =
+ current_parameters_.tick_target - current_parameters_.interval;
+ base::TimeTicks new_tick_time_threshold =
+ last_tick_time_ + current_parameters_.interval / kDoubleTickDivisor;
+ if (last_tick_time_if_always_active > new_tick_time_threshold) {
+ last_tick_time_ = last_tick_time_if_always_active;
+ return last_tick_time_;
+ }
+
+ return base::TimeTicks();
+}
+
+bool DelayBasedTimeSource::Active() const { return active_; }
+
+base::TimeTicks DelayBasedTimeSource::LastTickTime() { return last_tick_time_; }
+
+base::TimeTicks DelayBasedTimeSource::NextTickTime() {
+ return Active() ? current_parameters_.tick_target : base::TimeTicks();
+}
+
+void DelayBasedTimeSource::OnTimerFired() {
+ DCHECK(active_);
+
+ last_tick_time_ = current_parameters_.tick_target;
+
+ PostNextTickTask(Now());
+
+ // Fire the tick.
+ if (client_)
+ client_->OnTimerTick();
+}
+
+void DelayBasedTimeSource::SetClient(TimeSourceClient* client) {
+ client_ = client;
+}
+
+void DelayBasedTimeSource::SetTimebaseAndInterval(base::TimeTicks timebase,
+ base::TimeDelta interval) {
+ next_parameters_.interval = interval;
+ next_parameters_.tick_target = timebase;
+
+ if (!active_) {
+ // If we aren't active, there's no need to reset the timer.
+ return;
+ }
+
+ // If the change in interval is larger than the change threshold,
+ // request an immediate reset.
+ double interval_delta =
+ std::abs((interval - current_parameters_.interval).InSecondsF());
+ double interval_change = interval_delta / interval.InSecondsF();
+ if (interval_change > kIntervalChangeThreshold) {
+ TRACE_EVENT_INSTANT0("cc", "DelayBasedTimeSource::IntervalChanged",
+ TRACE_EVENT_SCOPE_THREAD);
+ SetActive(false);
+ SetActive(true);
+ return;
+ }
+
+ // If the change in phase is greater than the change threshold in either
+ // direction, request an immediate reset. This logic might result in a false
+ // negative if there is a simultaneous small change in the interval and the
+ // fmod just happens to return something near zero. Assuming the timebase
+ // is very recent though, which it should be, we'll still be ok because the
+ // old clock and new clock just happen to line up.
+ double target_delta =
+ std::abs((timebase - current_parameters_.tick_target).InSecondsF());
+ double phase_change =
+ fmod(target_delta, interval.InSecondsF()) / interval.InSecondsF();
+ if (phase_change > kPhaseChangeThreshold &&
+ phase_change < (1.0 - kPhaseChangeThreshold)) {
+ TRACE_EVENT_INSTANT0("cc", "DelayBasedTimeSource::PhaseChanged",
+ TRACE_EVENT_SCOPE_THREAD);
+ SetActive(false);
+ SetActive(true);
+ return;
+ }
+}
+
+base::TimeTicks DelayBasedTimeSource::Now() const {
+ return base::TimeTicks::Now();
+}
+
+// This code tries to achieve an average tick rate as close to interval_ as
+// possible. To do this, it has to deal with a few basic issues:
+// 1. PostDelayedTask can delay only at a millisecond granularity. So, 16.666
+// has to posted as 16 or 17.
+// 2. A delayed task may come back a bit late (a few ms), or really late
+// (frames later)
+//
+// The basic idea with this scheduler here is to keep track of where we *want*
+// to run in tick_target_. We update this with the exact interval.
+//
+// Then, when we post our task, we take the floor of (tick_target_ and Now()).
+// If we started at now=0, and 60FPs (all times in milliseconds):
+// now=0 target=16.667 PostDelayedTask(16)
+//
+// When our callback runs, we figure out how far off we were from that goal.
+// Because of the flooring operation, and assuming our timer runs exactly when
+// it should, this yields:
+// now=16 target=16.667
+//
+// Since we can't post a 0.667 ms task to get to now=16, we just treat this as a
+// tick. Then, we update target to be 33.333. We now post another task based on
+// the difference between our target and now:
+// now=16 tick_target=16.667 new_target=33.333 -->
+// PostDelayedTask(floor(33.333 - 16)) --> PostDelayedTask(17)
+//
+// Over time, with no late tasks, this leads to us posting tasks like this:
+// now=0 tick_target=0 new_target=16.667 -->
+// tick(), PostDelayedTask(16)
+// now=16 tick_target=16.667 new_target=33.333 -->
+// tick(), PostDelayedTask(17)
+// now=33 tick_target=33.333 new_target=50.000 -->
+// tick(), PostDelayedTask(17)
+// now=50 tick_target=50.000 new_target=66.667 -->
+// tick(), PostDelayedTask(16)
+//
+// We treat delays in tasks differently depending on the amount of delay we
+// encounter. Suppose we posted a task with a target=16.667:
+// Case 1: late but not unrecoverably-so
+// now=18 tick_target=16.667
+//
+// Case 2: so late we obviously missed the tick
+// now=25.0 tick_target=16.667
+//
+// We treat the first case as a tick anyway, and assume the delay was unusual.
+// Thus, we compute the new_target based on the old timebase:
+// now=18 tick_target=16.667 new_target=33.333 -->
+// tick(), PostDelayedTask(floor(33.333-18)) --> PostDelayedTask(15)
+// This brings us back to 18+15 = 33, which was where we would have been if the
+// task hadn't been late.
+//
+// For the really late delay, we we move to the next logical tick. The timebase
+// is not reset.
+// now=37 tick_target=16.667 new_target=50.000 -->
+// tick(), PostDelayedTask(floor(50.000-37)) --> PostDelayedTask(13)
+base::TimeTicks DelayBasedTimeSource::NextTickTarget(base::TimeTicks now) {
+ const base::TimeDelta epsilon(base::TimeDelta::FromMicroseconds(1));
+ base::TimeDelta new_interval = next_parameters_.interval;
+
+ // Integer division rounds towards 0, but we always want to round down the
+ // number of intervals_elapsed, so we need the extra condition here.
+ int intervals_elapsed;
+ if (next_parameters_.tick_target < now) {
+ intervals_elapsed =
+ (now - next_parameters_.tick_target + new_interval - epsilon) /
+ new_interval;
+ } else {
+ intervals_elapsed = (now - next_parameters_.tick_target) / new_interval;
+ }
+ base::TimeTicks new_tick_target =
+ next_parameters_.tick_target + new_interval * intervals_elapsed;
+ DCHECK(now <= new_tick_target)
+ << "now = " << now.ToInternalValue()
+ << "; new_tick_target = " << new_tick_target.ToInternalValue()
+ << "; new_interval = " << new_interval.InMicroseconds()
+ << "; tick_target = " << next_parameters_.tick_target.ToInternalValue()
+ << "; intervals_elapsed = " << intervals_elapsed;
+
+ // Avoid double ticks when:
+ // 1) Turning off the timer and turning it right back on.
+ // 2) Jittery data is passed to SetTimebaseAndInterval().
+ if (new_tick_target - last_tick_time_ <= new_interval / kDoubleTickDivisor)
+ new_tick_target += new_interval;
+
+ return new_tick_target;
+}
+
+void DelayBasedTimeSource::PostNextTickTask(base::TimeTicks now) {
+ base::TimeTicks new_tick_target = NextTickTarget(now);
+
+ // Post another task *before* the tick and update state
+ base::TimeDelta delay;
+ if (now <= new_tick_target)
+ delay = new_tick_target - now;
+ task_runner_->PostDelayedTask(FROM_HERE,
+ base::Bind(&DelayBasedTimeSource::OnTimerFired,
+ weak_factory_.GetWeakPtr()),
+ delay);
+
+ next_parameters_.tick_target = new_tick_target;
+ current_parameters_ = next_parameters_;
+}
+
+} // namespace cc
diff --git a/chromium/cc/scheduler/delay_based_time_source.h b/chromium/cc/scheduler/delay_based_time_source.h
new file mode 100644
index 00000000000..55aac5a97fb
--- /dev/null
+++ b/chromium/cc/scheduler/delay_based_time_source.h
@@ -0,0 +1,78 @@
+// Copyright 2011 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.
+
+#ifndef CC_SCHEDULER_DELAY_BASED_TIME_SOURCE_H_
+#define CC_SCHEDULER_DELAY_BASED_TIME_SOURCE_H_
+
+#include "base/memory/weak_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/scheduler/time_source.h"
+
+namespace base { class SingleThreadTaskRunner; }
+
+namespace cc {
+
+// This timer implements a time source that achieves the specified interval
+// in face of millisecond-precision delayed callbacks and random queueing
+// delays.
+class CC_EXPORT DelayBasedTimeSource : public TimeSource {
+ public:
+ static scoped_refptr<DelayBasedTimeSource> Create(
+ base::TimeDelta interval, base::SingleThreadTaskRunner* task_runner);
+
+ virtual void SetClient(TimeSourceClient* client) OVERRIDE;
+
+ // TimeSource implementation
+ virtual void SetTimebaseAndInterval(base::TimeTicks timebase,
+ base::TimeDelta interval) OVERRIDE;
+
+ virtual base::TimeTicks SetActive(bool active) OVERRIDE;
+ virtual bool Active() const OVERRIDE;
+
+ // Get the last and next tick times. nextTimeTime() returns null when
+ // inactive.
+ virtual base::TimeTicks LastTickTime() OVERRIDE;
+ virtual base::TimeTicks NextTickTime() OVERRIDE;
+
+ // Virtual for testing.
+ virtual base::TimeTicks Now() const;
+
+ protected:
+ DelayBasedTimeSource(base::TimeDelta interval,
+ base::SingleThreadTaskRunner* task_runner);
+ virtual ~DelayBasedTimeSource();
+
+ base::TimeTicks NextTickTarget(base::TimeTicks now);
+ void PostNextTickTask(base::TimeTicks now);
+ void OnTimerFired();
+
+ struct Parameters {
+ Parameters(base::TimeDelta interval, base::TimeTicks tick_target)
+ : interval(interval), tick_target(tick_target) {}
+ base::TimeDelta interval;
+ base::TimeTicks tick_target;
+ };
+
+ TimeSourceClient* client_;
+ base::TimeTicks last_tick_time_;
+
+ // current_parameters_ should only be written by PostNextTickTask.
+ // next_parameters_ will take effect on the next call to PostNextTickTask.
+ // Maintaining a pending set of parameters allows NextTickTime() to always
+ // reflect the actual time we expect OnTimerFired to be called.
+ Parameters current_parameters_;
+ Parameters next_parameters_;
+
+ bool active_;
+
+ base::SingleThreadTaskRunner* task_runner_;
+ base::WeakPtrFactory<DelayBasedTimeSource> weak_factory_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DelayBasedTimeSource);
+};
+
+} // namespace cc
+
+#endif // CC_SCHEDULER_DELAY_BASED_TIME_SOURCE_H_
diff --git a/chromium/cc/scheduler/delay_based_time_source_unittest.cc b/chromium/cc/scheduler/delay_based_time_source_unittest.cc
new file mode 100644
index 00000000000..77d570f1d92
--- /dev/null
+++ b/chromium/cc/scheduler/delay_based_time_source_unittest.cc
@@ -0,0 +1,510 @@
+// Copyright 2011 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 "cc/scheduler/delay_based_time_source.h"
+
+#include "base/basictypes.h"
+#include "base/test/test_simple_task_runner.h"
+#include "cc/test/scheduler_test_common.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+base::TimeDelta Interval() {
+ return base::TimeDelta::FromMicroseconds(base::Time::kMicrosecondsPerSecond /
+ 60);
+}
+
+TEST(DelayBasedTimeSourceTest, TaskPostedAndTickCalled) {
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner =
+ new base::TestSimpleTaskRunner;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer =
+ FakeDelayBasedTimeSource::Create(Interval(), task_runner.get());
+ timer->SetClient(&client);
+
+ timer->SetActive(true);
+ EXPECT_TRUE(timer->Active());
+ EXPECT_TRUE(task_runner->HasPendingTask());
+
+ timer->SetNow(timer->Now() + base::TimeDelta::FromMilliseconds(16));
+ task_runner->RunPendingTasks();
+ EXPECT_TRUE(timer->Active());
+ EXPECT_TRUE(client.TickCalled());
+}
+
+TEST(DelayBasedTimeSource, TickNotCalledWithTaskPosted) {
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner =
+ new base::TestSimpleTaskRunner;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer =
+ FakeDelayBasedTimeSource::Create(Interval(), task_runner.get());
+ timer->SetClient(&client);
+ timer->SetActive(true);
+ EXPECT_TRUE(task_runner->HasPendingTask());
+ timer->SetActive(false);
+ task_runner->RunPendingTasks();
+ EXPECT_FALSE(client.TickCalled());
+}
+
+TEST(DelayBasedTimeSource, StartTwiceEnqueuesOneTask) {
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner =
+ new base::TestSimpleTaskRunner;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer =
+ FakeDelayBasedTimeSource::Create(Interval(), task_runner.get());
+ timer->SetClient(&client);
+ timer->SetActive(true);
+ EXPECT_TRUE(task_runner->HasPendingTask());
+ task_runner->ClearPendingTasks();
+ timer->SetActive(true);
+ EXPECT_FALSE(task_runner->HasPendingTask());
+}
+
+TEST(DelayBasedTimeSource, StartWhenRunningDoesntTick) {
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner =
+ new base::TestSimpleTaskRunner;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer =
+ FakeDelayBasedTimeSource::Create(Interval(), task_runner.get());
+ timer->SetClient(&client);
+ timer->SetActive(true);
+ EXPECT_TRUE(task_runner->HasPendingTask());
+ task_runner->RunPendingTasks();
+ task_runner->ClearPendingTasks();
+ timer->SetActive(true);
+ EXPECT_FALSE(task_runner->HasPendingTask());
+}
+
+// At 60Hz, when the tick returns at exactly the requested next time, make sure
+// a 16ms next delay is posted.
+TEST(DelayBasedTimeSource, NextDelaySaneWhenExactlyOnRequestedTime) {
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner =
+ new base::TestSimpleTaskRunner;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer =
+ FakeDelayBasedTimeSource::Create(Interval(), task_runner.get());
+ timer->SetClient(&client);
+ timer->SetActive(true);
+ // Run the first tick.
+ task_runner->RunPendingTasks();
+
+ EXPECT_EQ(16, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ timer->SetNow(timer->Now() + Interval());
+ task_runner->RunPendingTasks();
+
+ EXPECT_EQ(16, task_runner->NextPendingTaskDelay().InMilliseconds());
+}
+
+// At 60Hz, when the tick returns at slightly after the requested next time,
+// make sure a 16ms next delay is posted.
+TEST(DelayBasedTimeSource, NextDelaySaneWhenSlightlyAfterRequestedTime) {
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner =
+ new base::TestSimpleTaskRunner;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer =
+ FakeDelayBasedTimeSource::Create(Interval(), task_runner.get());
+ timer->SetClient(&client);
+ timer->SetActive(true);
+ // Run the first tick.
+ task_runner->RunPendingTasks();
+
+ EXPECT_EQ(16, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ timer->SetNow(timer->Now() + Interval() +
+ base::TimeDelta::FromMicroseconds(1));
+ task_runner->RunPendingTasks();
+
+ EXPECT_EQ(16, task_runner->NextPendingTaskDelay().InMilliseconds());
+}
+
+// At 60Hz, when the tick returns at exactly 2*interval after the requested next
+// time, make sure a 0ms next delay is posted.
+TEST(DelayBasedTimeSource, NextDelaySaneWhenExactlyTwiceAfterRequestedTime) {
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner =
+ new base::TestSimpleTaskRunner;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer =
+ FakeDelayBasedTimeSource::Create(Interval(), task_runner.get());
+ timer->SetClient(&client);
+ timer->SetActive(true);
+ // Run the first tick.
+ task_runner->RunPendingTasks();
+
+ EXPECT_EQ(16, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ timer->SetNow(timer->Now() + 2 * Interval());
+ task_runner->RunPendingTasks();
+
+ EXPECT_EQ(0, task_runner->NextPendingTaskDelay().InMilliseconds());
+}
+
+// At 60Hz, when the tick returns at 2*interval and a bit after the requested
+// next time, make sure a 16ms next delay is posted.
+TEST(DelayBasedTimeSource, NextDelaySaneWhenSlightlyAfterTwiceRequestedTime) {
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner =
+ new base::TestSimpleTaskRunner;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer =
+ FakeDelayBasedTimeSource::Create(Interval(), task_runner.get());
+ timer->SetClient(&client);
+ timer->SetActive(true);
+ // Run the first tick.
+ task_runner->RunPendingTasks();
+
+ EXPECT_EQ(16, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ timer->SetNow(timer->Now() + 2 * Interval() +
+ base::TimeDelta::FromMicroseconds(1));
+ task_runner->RunPendingTasks();
+
+ EXPECT_EQ(16, task_runner->NextPendingTaskDelay().InMilliseconds());
+}
+
+// At 60Hz, when the tick returns halfway to the next frame time, make sure
+// a correct next delay value is posted.
+TEST(DelayBasedTimeSource, NextDelaySaneWhenHalfAfterRequestedTime) {
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner =
+ new base::TestSimpleTaskRunner;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer =
+ FakeDelayBasedTimeSource::Create(Interval(), task_runner.get());
+ timer->SetClient(&client);
+ timer->SetActive(true);
+ // Run the first tick.
+ task_runner->RunPendingTasks();
+
+ EXPECT_EQ(16, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ timer->SetNow(timer->Now() + Interval() +
+ base::TimeDelta::FromMilliseconds(8));
+ task_runner->RunPendingTasks();
+
+ EXPECT_EQ(8, task_runner->NextPendingTaskDelay().InMilliseconds());
+}
+
+// If the timebase and interval are updated with a jittery source, we want to
+// make sure we do not double tick.
+TEST(DelayBasedTimeSource, SaneHandlingOfJitteryTimebase) {
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner =
+ new base::TestSimpleTaskRunner;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer =
+ FakeDelayBasedTimeSource::Create(Interval(), task_runner.get());
+ timer->SetClient(&client);
+ timer->SetActive(true);
+ // Run the first tick.
+ task_runner->RunPendingTasks();
+
+ EXPECT_EQ(16, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ // Jitter timebase ~1ms late
+ timer->SetNow(timer->Now() + Interval());
+ timer->SetTimebaseAndInterval(
+ timer->Now() + base::TimeDelta::FromMilliseconds(1), Interval());
+ task_runner->RunPendingTasks();
+
+ // Without double tick prevention, NextPendingTaskDelay would be 1.
+ EXPECT_EQ(17, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ // Jitter timebase ~1ms early
+ timer->SetNow(timer->Now() + Interval());
+ timer->SetTimebaseAndInterval(
+ timer->Now() - base::TimeDelta::FromMilliseconds(1), Interval());
+ task_runner->RunPendingTasks();
+
+ EXPECT_EQ(15, task_runner->NextPendingTaskDelay().InMilliseconds());
+}
+
+TEST(DelayBasedTimeSource, HandlesSignificantTimebaseChangesImmediately) {
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner =
+ new base::TestSimpleTaskRunner;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer =
+ FakeDelayBasedTimeSource::Create(Interval(), task_runner.get());
+ timer->SetClient(&client);
+ timer->SetActive(true);
+ // Run the first tick.
+ task_runner->RunPendingTasks();
+
+ EXPECT_EQ(16, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ // Tick, then shift timebase by +7ms.
+ timer->SetNow(timer->Now() + Interval());
+ task_runner->RunPendingTasks();
+
+ EXPECT_EQ(16, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ client.Reset();
+ task_runner->ClearPendingTasks();
+ task_runner->RunPendingTasks();
+ base::TimeDelta jitter = base::TimeDelta::FromMilliseconds(7) +
+ base::TimeDelta::FromMicroseconds(1);
+ timer->SetTimebaseAndInterval(timer->Now() + jitter, Interval());
+
+ EXPECT_FALSE(client.TickCalled()); // Make sure pending tasks were canceled.
+ EXPECT_EQ(16 + 7, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ // Tick, then shift timebase by -7ms.
+ timer->SetNow(timer->Now() + Interval() + jitter);
+ task_runner->RunPendingTasks();
+
+ EXPECT_EQ(16, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ client.Reset();
+ task_runner->ClearPendingTasks();
+ task_runner->RunPendingTasks();
+ timer->SetTimebaseAndInterval(base::TimeTicks() + Interval(), Interval());
+
+ EXPECT_FALSE(client.TickCalled()); // Make sure pending tasks were canceled.
+ EXPECT_EQ(16 - 7, task_runner->NextPendingTaskDelay().InMilliseconds());
+}
+
+TEST(DelayBasedTimeSource, HanldlesSignificantIntervalChangesImmediately) {
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner =
+ new base::TestSimpleTaskRunner;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer =
+ FakeDelayBasedTimeSource::Create(Interval(), task_runner.get());
+ timer->SetClient(&client);
+ timer->SetActive(true);
+ // Run the first tick.
+ task_runner->RunPendingTasks();
+
+ EXPECT_EQ(16, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ // Tick, then double the interval.
+ timer->SetNow(timer->Now() + Interval());
+ task_runner->RunPendingTasks();
+
+ EXPECT_EQ(16, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ client.Reset();
+ task_runner->ClearPendingTasks();
+ task_runner->RunPendingTasks();
+ timer->SetTimebaseAndInterval(base::TimeTicks() + Interval(), Interval() * 2);
+
+ EXPECT_FALSE(client.TickCalled()); // Make sure pending tasks were canceled.
+ EXPECT_EQ(33, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ // Tick, then halve the interval.
+ timer->SetNow(timer->Now() + Interval() * 2);
+ task_runner->RunPendingTasks();
+
+ EXPECT_EQ(33, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ client.Reset();
+ task_runner->ClearPendingTasks();
+ task_runner->RunPendingTasks();
+ timer->SetTimebaseAndInterval(base::TimeTicks() + Interval() * 3, Interval());
+
+ EXPECT_FALSE(client.TickCalled()); // Make sure pending tasks were canceled.
+ EXPECT_EQ(16, task_runner->NextPendingTaskDelay().InMilliseconds());
+}
+
+TEST(DelayBasedTimeSource, JitteryRuntimeWithFutureTimebases) {
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner =
+ new base::TestSimpleTaskRunner;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer =
+ FakeDelayBasedTimeSource::Create(Interval(), task_runner.get());
+ timer->SetClient(&client);
+ timer->SetActive(true);
+
+ // Run the first tick.
+ task_runner->RunPendingTasks();
+ EXPECT_EQ(16, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ base::TimeTicks future_timebase = timer->Now() + Interval() * 10;
+
+ // 1ms jitter
+ base::TimeDelta jitter1 = base::TimeDelta::FromMilliseconds(1);
+
+ // Tick with +1ms of jitter
+ future_timebase += Interval();
+ timer->SetTimebaseAndInterval(future_timebase, Interval());
+ timer->SetNow(timer->Now() + Interval() + jitter1);
+ task_runner->RunPendingTasks();
+ EXPECT_EQ(15, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ // Tick with 0ms of jitter
+ future_timebase += Interval();
+ timer->SetTimebaseAndInterval(future_timebase, Interval());
+ timer->SetNow(timer->Now() + Interval() - jitter1);
+ task_runner->RunPendingTasks();
+ EXPECT_EQ(16, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ // Tick with -1ms of jitter
+ future_timebase += Interval();
+ timer->SetTimebaseAndInterval(future_timebase, Interval());
+ timer->SetNow(timer->Now() + Interval() - jitter1);
+ task_runner->RunPendingTasks();
+ EXPECT_EQ(17, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ // Tick with 0ms of jitter
+ future_timebase += Interval();
+ timer->SetTimebaseAndInterval(future_timebase, Interval());
+ timer->SetNow(timer->Now() + Interval() + jitter1);
+ task_runner->RunPendingTasks();
+ EXPECT_EQ(16, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ // 8 ms jitter
+ base::TimeDelta jitter8 = base::TimeDelta::FromMilliseconds(8);
+
+ // Tick with +8ms of jitter
+ future_timebase += Interval();
+ timer->SetTimebaseAndInterval(future_timebase, Interval());
+ timer->SetNow(timer->Now() + Interval() + jitter8);
+ task_runner->RunPendingTasks();
+ EXPECT_EQ(8, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ // Tick with 0ms of jitter
+ future_timebase += Interval();
+ timer->SetTimebaseAndInterval(future_timebase, Interval());
+ timer->SetNow(timer->Now() + Interval() - jitter8);
+ task_runner->RunPendingTasks();
+ EXPECT_EQ(16, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ // Tick with -8ms of jitter
+ future_timebase += Interval();
+ timer->SetTimebaseAndInterval(future_timebase, Interval());
+ timer->SetNow(timer->Now() + Interval() - jitter8);
+ task_runner->RunPendingTasks();
+ EXPECT_EQ(24, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ // Tick with 0ms of jitter
+ future_timebase += Interval();
+ timer->SetTimebaseAndInterval(future_timebase, Interval());
+ timer->SetNow(timer->Now() + Interval() + jitter8);
+ task_runner->RunPendingTasks();
+ EXPECT_EQ(16, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ // 15 ms jitter
+ base::TimeDelta jitter15 = base::TimeDelta::FromMilliseconds(15);
+
+ // Tick with +15ms jitter
+ future_timebase += Interval();
+ timer->SetTimebaseAndInterval(future_timebase, Interval());
+ timer->SetNow(timer->Now() + Interval() + jitter15);
+ task_runner->RunPendingTasks();
+ EXPECT_EQ(1, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ // Tick with 0ms of jitter
+ future_timebase += Interval();
+ timer->SetTimebaseAndInterval(future_timebase, Interval());
+ timer->SetNow(timer->Now() + Interval() - jitter15);
+ task_runner->RunPendingTasks();
+ EXPECT_EQ(16, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ // Tick with -15ms of jitter
+ future_timebase += Interval();
+ timer->SetTimebaseAndInterval(future_timebase, Interval());
+ timer->SetNow(timer->Now() + Interval() - jitter15);
+ task_runner->RunPendingTasks();
+ EXPECT_EQ(31, task_runner->NextPendingTaskDelay().InMilliseconds());
+
+ // Tick with 0ms of jitter
+ future_timebase += Interval();
+ timer->SetTimebaseAndInterval(future_timebase, Interval());
+ timer->SetNow(timer->Now() + Interval() + jitter15);
+ task_runner->RunPendingTasks();
+ EXPECT_EQ(16, task_runner->NextPendingTaskDelay().InMilliseconds());
+}
+
+TEST(DelayBasedTimeSourceTest, AchievesTargetRateWithNoNoise) {
+ int num_iterations = 10;
+
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner =
+ new base::TestSimpleTaskRunner;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer =
+ FakeDelayBasedTimeSource::Create(Interval(), task_runner.get());
+ timer->SetClient(&client);
+ timer->SetActive(true);
+
+ double total_frame_time = 0.0;
+ for (int i = 0; i < num_iterations; ++i) {
+ int64 delay_ms = task_runner->NextPendingTaskDelay().InMilliseconds();
+
+ // accumulate the "delay"
+ total_frame_time += delay_ms / 1000.0;
+
+ // Run the callback exactly when asked
+ timer->SetNow(timer->Now() + base::TimeDelta::FromMilliseconds(delay_ms));
+ task_runner->RunPendingTasks();
+ }
+ double average_interval =
+ total_frame_time / static_cast<double>(num_iterations);
+ EXPECT_NEAR(1.0 / 60.0, average_interval, 0.1);
+}
+
+TEST(DelayBasedTimeSource, TestDeactivateWhilePending) {
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner =
+ new base::TestSimpleTaskRunner;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer =
+ FakeDelayBasedTimeSource::Create(Interval(), task_runner.get());
+ timer->SetClient(&client);
+ timer->SetActive(true); // Should post a task.
+ timer->SetActive(false);
+ timer = NULL;
+ // Should run the posted task without crashing.
+ EXPECT_TRUE(task_runner->HasPendingTask());
+ task_runner->RunPendingTasks();
+}
+
+TEST(DelayBasedTimeSource, TestDeactivateAndReactivateBeforeNextTickTime) {
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner =
+ new base::TestSimpleTaskRunner;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer =
+ FakeDelayBasedTimeSource::Create(Interval(), task_runner.get());
+ timer->SetClient(&client);
+
+ // Should run the activate task, and pick up a new timebase.
+ timer->SetActive(true);
+ task_runner->RunPendingTasks();
+
+ // Stop the timer
+ timer->SetActive(false);
+
+ // Task will be pending anyway, run it
+ task_runner->RunPendingTasks();
+
+ // Start the timer again, but before the next tick time the timer previously
+ // planned on using. That same tick time should still be targeted.
+ timer->SetNow(timer->Now() + base::TimeDelta::FromMilliseconds(4));
+ timer->SetActive(true);
+ EXPECT_EQ(12, task_runner->NextPendingTaskDelay().InMilliseconds());
+}
+
+TEST(DelayBasedTimeSource, TestDeactivateAndReactivateAfterNextTickTime) {
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner =
+ new base::TestSimpleTaskRunner;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer =
+ FakeDelayBasedTimeSource::Create(Interval(), task_runner.get());
+ timer->SetClient(&client);
+
+ // Should run the activate task, and pick up a new timebase.
+ timer->SetActive(true);
+ task_runner->RunPendingTasks();
+
+ // Stop the timer.
+ timer->SetActive(false);
+
+ // Task will be pending anyway, run it.
+ task_runner->RunPendingTasks();
+
+ // Start the timer again, but before the next tick time the timer previously
+ // planned on using. That same tick time should still be targeted.
+ timer->SetNow(timer->Now() + base::TimeDelta::FromMilliseconds(20));
+ timer->SetActive(true);
+ EXPECT_EQ(13, task_runner->NextPendingTaskDelay().InMilliseconds());
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/scheduler/frame_rate_controller.cc b/chromium/cc/scheduler/frame_rate_controller.cc
new file mode 100644
index 00000000000..54c1bdaf48f
--- /dev/null
+++ b/chromium/cc/scheduler/frame_rate_controller.cc
@@ -0,0 +1,172 @@
+// Copyright 2011 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 "cc/scheduler/frame_rate_controller.h"
+
+#include "base/bind.h"
+#include "base/debug/trace_event.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/single_thread_task_runner.h"
+#include "cc/scheduler/delay_based_time_source.h"
+#include "cc/scheduler/time_source.h"
+
+namespace cc {
+
+class FrameRateControllerTimeSourceAdapter : public TimeSourceClient {
+ public:
+ static scoped_ptr<FrameRateControllerTimeSourceAdapter> Create(
+ FrameRateController* frame_rate_controller) {
+ return make_scoped_ptr(
+ new FrameRateControllerTimeSourceAdapter(frame_rate_controller));
+ }
+ virtual ~FrameRateControllerTimeSourceAdapter() {}
+
+ virtual void OnTimerTick() OVERRIDE {
+ frame_rate_controller_->OnTimerTick();
+ }
+
+ private:
+ explicit FrameRateControllerTimeSourceAdapter(
+ FrameRateController* frame_rate_controller)
+ : frame_rate_controller_(frame_rate_controller) {}
+
+ FrameRateController* frame_rate_controller_;
+};
+
+FrameRateController::FrameRateController(scoped_refptr<TimeSource> timer)
+ : client_(NULL),
+ num_frames_pending_(0),
+ max_swaps_pending_(0),
+ interval_(BeginFrameArgs::DefaultInterval()),
+ time_source_(timer),
+ active_(false),
+ is_time_source_throttling_(true),
+ weak_factory_(this),
+ task_runner_(NULL) {
+ time_source_client_adapter_ =
+ FrameRateControllerTimeSourceAdapter::Create(this);
+ time_source_->SetClient(time_source_client_adapter_.get());
+}
+
+FrameRateController::FrameRateController(
+ base::SingleThreadTaskRunner* task_runner)
+ : client_(NULL),
+ num_frames_pending_(0),
+ max_swaps_pending_(0),
+ interval_(BeginFrameArgs::DefaultInterval()),
+ active_(false),
+ is_time_source_throttling_(false),
+ weak_factory_(this),
+ task_runner_(task_runner) {}
+
+FrameRateController::~FrameRateController() {
+ if (is_time_source_throttling_)
+ time_source_->SetActive(false);
+}
+
+BeginFrameArgs FrameRateController::SetActive(bool active) {
+ if (active_ == active)
+ return BeginFrameArgs();
+ TRACE_EVENT1("cc", "FrameRateController::SetActive", "active", active);
+ active_ = active;
+
+ if (is_time_source_throttling_) {
+ base::TimeTicks missed_tick_time = time_source_->SetActive(active);
+ if (!missed_tick_time.is_null()) {
+ base::TimeTicks deadline = NextTickTime();
+ return BeginFrameArgs::Create(missed_tick_time,
+ deadline,
+ interval_);
+ }
+ } else {
+ if (active)
+ PostManualTick();
+ else
+ weak_factory_.InvalidateWeakPtrs();
+ }
+
+ return BeginFrameArgs();
+}
+
+void FrameRateController::SetMaxSwapsPending(int max_swaps_pending) {
+ DCHECK_GE(max_swaps_pending, 0);
+ max_swaps_pending_ = max_swaps_pending;
+}
+
+void FrameRateController::SetTimebaseAndInterval(base::TimeTicks timebase,
+ base::TimeDelta interval) {
+ interval_ = interval;
+ if (is_time_source_throttling_)
+ time_source_->SetTimebaseAndInterval(timebase, interval);
+}
+
+void FrameRateController::SetDeadlineAdjustment(base::TimeDelta delta) {
+ deadline_adjustment_ = delta;
+}
+
+void FrameRateController::OnTimerTick() {
+ TRACE_EVENT0("cc", "FrameRateController::OnTimerTick");
+ DCHECK(active_);
+
+ // Check if we have too many frames in flight.
+ bool throttled =
+ max_swaps_pending_ && num_frames_pending_ >= max_swaps_pending_;
+ TRACE_COUNTER_ID1("cc", "ThrottledCompositor", task_runner_, throttled);
+
+ if (client_) {
+ // TODO(brianderson): Use an adaptive parent compositor deadline.
+ base::TimeTicks frame_time = LastTickTime();
+ base::TimeTicks deadline = NextTickTime() + deadline_adjustment_;
+ client_->FrameRateControllerTick(
+ throttled,
+ BeginFrameArgs::Create(frame_time, deadline, interval_));
+ }
+
+ if (!is_time_source_throttling_ && !throttled)
+ PostManualTick();
+}
+
+void FrameRateController::PostManualTick() {
+ if (active_) {
+ task_runner_->PostTask(FROM_HERE,
+ base::Bind(&FrameRateController::ManualTick,
+ weak_factory_.GetWeakPtr()));
+ }
+}
+
+void FrameRateController::ManualTick() {
+ OnTimerTick();
+}
+
+void FrameRateController::DidSwapBuffers() {
+ num_frames_pending_++;
+}
+
+void FrameRateController::DidSwapBuffersComplete() {
+ DCHECK_GT(num_frames_pending_, 0);
+ num_frames_pending_--;
+ if (!is_time_source_throttling_)
+ PostManualTick();
+}
+
+void FrameRateController::DidAbortAllPendingFrames() {
+ num_frames_pending_ = 0;
+}
+
+base::TimeTicks FrameRateController::NextTickTime() {
+ if (is_time_source_throttling_)
+ return time_source_->NextTickTime();
+
+ return base::TimeTicks();
+}
+
+base::TimeTicks FrameRateController::LastTickTime() {
+ if (is_time_source_throttling_)
+ return time_source_->LastTickTime();
+
+ return base::TimeTicks::Now();
+}
+
+} // namespace cc
diff --git a/chromium/cc/scheduler/frame_rate_controller.h b/chromium/cc/scheduler/frame_rate_controller.h
new file mode 100644
index 00000000000..b68c73db0c8
--- /dev/null
+++ b/chromium/cc/scheduler/frame_rate_controller.h
@@ -0,0 +1,100 @@
+// Copyright 2011 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.
+
+#ifndef CC_SCHEDULER_FRAME_RATE_CONTROLLER_H_
+#define CC_SCHEDULER_FRAME_RATE_CONTROLLER_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "cc/base/cc_export.h"
+#include "cc/output/begin_frame_args.h"
+
+namespace base { class SingleThreadTaskRunner; }
+
+namespace cc {
+
+class TimeSource;
+class FrameRateController;
+
+class CC_EXPORT FrameRateControllerClient {
+ protected:
+ virtual ~FrameRateControllerClient() {}
+
+ public:
+ // Throttled is true when we have a maximum number of frames pending.
+ virtual void FrameRateControllerTick(bool throttled,
+ const BeginFrameArgs& args) = 0;
+};
+
+class FrameRateControllerTimeSourceAdapter;
+
+// The FrameRateController is used in cases where we self-tick (i.e. BeginFrame
+// is not sent by a parent compositor.
+class CC_EXPORT FrameRateController {
+ public:
+ explicit FrameRateController(scoped_refptr<TimeSource> timer);
+ // Alternate form of FrameRateController with unthrottled frame-rate.
+ explicit FrameRateController(base::SingleThreadTaskRunner* task_runner);
+ virtual ~FrameRateController();
+
+ void SetClient(FrameRateControllerClient* client) { client_ = client; }
+
+ // Returns a valid BeginFrame on activation to potentially be used
+ // retroactively.
+ BeginFrameArgs SetActive(bool active);
+
+ bool IsActive() { return active_; }
+
+ // Use the following methods to adjust target frame rate.
+ //
+ // Multiple frames can be in-progress, but for every DidSwapBuffers, a
+ // DidFinishFrame should be posted.
+ //
+ // If the rendering pipeline crashes, call DidAbortAllPendingFrames.
+ void DidSwapBuffers();
+ void DidSwapBuffersComplete();
+ void DidAbortAllPendingFrames();
+ void SetMaxSwapsPending(int max_swaps_pending); // 0 for unlimited.
+ int MaxSwapsPending() const { return max_swaps_pending_; }
+ int NumSwapsPendingForTesting() const { return num_frames_pending_; }
+
+ void SetTimebaseAndInterval(base::TimeTicks timebase,
+ base::TimeDelta interval);
+ void SetDeadlineAdjustment(base::TimeDelta delta);
+
+ protected:
+ friend class FrameRateControllerTimeSourceAdapter;
+ void OnTimerTick();
+
+ void PostManualTick();
+ void ManualTick();
+
+ // This returns null for unthrottled frame-rate.
+ base::TimeTicks NextTickTime();
+ // This returns now for unthrottled frame-rate.
+ base::TimeTicks LastTickTime();
+
+ FrameRateControllerClient* client_;
+ int num_frames_pending_;
+ int max_swaps_pending_;
+ base::TimeDelta interval_;
+ base::TimeDelta deadline_adjustment_;
+ scoped_refptr<TimeSource> time_source_;
+ scoped_ptr<FrameRateControllerTimeSourceAdapter> time_source_client_adapter_;
+ bool active_;
+
+ // Members for unthrottled frame-rate.
+ bool is_time_source_throttling_;
+ base::WeakPtrFactory<FrameRateController> weak_factory_;
+ base::SingleThreadTaskRunner* task_runner_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FrameRateController);
+};
+
+} // namespace cc
+
+#endif // CC_SCHEDULER_FRAME_RATE_CONTROLLER_H_
diff --git a/chromium/cc/scheduler/frame_rate_controller_unittest.cc b/chromium/cc/scheduler/frame_rate_controller_unittest.cc
new file mode 100644
index 00000000000..353d9844caf
--- /dev/null
+++ b/chromium/cc/scheduler/frame_rate_controller_unittest.cc
@@ -0,0 +1,186 @@
+// Copyright 2011 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 "cc/scheduler/frame_rate_controller.h"
+
+#include "base/test/test_simple_task_runner.h"
+#include "cc/test/scheduler_test_common.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+class FakeFrameRateControllerClient : public cc::FrameRateControllerClient {
+ public:
+ FakeFrameRateControllerClient() { Reset(); }
+
+ void Reset() { began_frame_ = false; }
+ bool BeganFrame() const { return began_frame_; }
+
+ virtual void FrameRateControllerTick(
+ bool throttled, const BeginFrameArgs& args) OVERRIDE {
+ began_frame_ = !throttled;
+ }
+
+ protected:
+ bool began_frame_;
+};
+
+TEST(FrameRateControllerTest, TestFrameThrottling_ImmediateAck) {
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner =
+ new base::TestSimpleTaskRunner;
+ FakeFrameRateControllerClient client;
+ base::TimeDelta interval = base::TimeDelta::FromMicroseconds(
+ base::Time::kMicrosecondsPerSecond / 60);
+ scoped_refptr<FakeDelayBasedTimeSource> time_source =
+ FakeDelayBasedTimeSource::Create(interval, task_runner.get());
+ FrameRateController controller(time_source);
+
+ controller.SetClient(&client);
+ controller.SetActive(true);
+
+ base::TimeTicks elapsed; // Muck around with time a bit
+
+ // Trigger one frame, make sure the BeginFrame callback is called
+ elapsed += task_runner->NextPendingTaskDelay();
+ time_source->SetNow(elapsed);
+ task_runner->RunPendingTasks();
+ EXPECT_TRUE(client.BeganFrame());
+ client.Reset();
+
+ // Tell the controller we drew
+ controller.DidSwapBuffers();
+
+ // Tell the controller the frame ended 5ms later
+ time_source->SetNow(time_source->Now() +
+ base::TimeDelta::FromMilliseconds(5));
+ controller.DidSwapBuffersComplete();
+
+ // Trigger another frame, make sure BeginFrame runs again
+ elapsed += task_runner->NextPendingTaskDelay();
+ // Sanity check that previous code didn't move time backward.
+ EXPECT_GE(elapsed, time_source->Now());
+ time_source->SetNow(elapsed);
+ task_runner->RunPendingTasks();
+ EXPECT_TRUE(client.BeganFrame());
+}
+
+TEST(FrameRateControllerTest, TestFrameThrottling_TwoFramesInFlight) {
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner =
+ new base::TestSimpleTaskRunner;
+ FakeFrameRateControllerClient client;
+ base::TimeDelta interval = base::TimeDelta::FromMicroseconds(
+ base::Time::kMicrosecondsPerSecond / 60);
+ scoped_refptr<FakeDelayBasedTimeSource> time_source =
+ FakeDelayBasedTimeSource::Create(interval, task_runner.get());
+ FrameRateController controller(time_source);
+
+ controller.SetClient(&client);
+ controller.SetActive(true);
+ controller.SetMaxSwapsPending(2);
+
+ base::TimeTicks elapsed; // Muck around with time a bit
+
+ // Trigger one frame, make sure the BeginFrame callback is called
+ elapsed += task_runner->NextPendingTaskDelay();
+ time_source->SetNow(elapsed);
+ task_runner->RunPendingTasks();
+ EXPECT_TRUE(client.BeganFrame());
+ client.Reset();
+
+ // Tell the controller we drew
+ controller.DidSwapBuffers();
+
+ // Trigger another frame, make sure BeginFrame callback runs again
+ elapsed += task_runner->NextPendingTaskDelay();
+ // Sanity check that previous code didn't move time backward.
+ EXPECT_GE(elapsed, time_source->Now());
+ time_source->SetNow(elapsed);
+ task_runner->RunPendingTasks();
+ EXPECT_TRUE(client.BeganFrame());
+ client.Reset();
+
+ // Tell the controller we drew, again.
+ controller.DidSwapBuffers();
+
+ // Trigger another frame. Since two frames are pending, we should not draw.
+ elapsed += task_runner->NextPendingTaskDelay();
+ // Sanity check that previous code didn't move time backward.
+ EXPECT_GE(elapsed, time_source->Now());
+ time_source->SetNow(elapsed);
+ task_runner->RunPendingTasks();
+ EXPECT_FALSE(client.BeganFrame());
+
+ // Tell the controller the first frame ended 5ms later
+ time_source->SetNow(time_source->Now() +
+ base::TimeDelta::FromMilliseconds(5));
+ controller.DidSwapBuffersComplete();
+
+ // Tick should not have been called
+ EXPECT_FALSE(client.BeganFrame());
+
+ // Trigger yet another frame. Since one frames is pending, another
+ // BeginFrame callback should run.
+ elapsed += task_runner->NextPendingTaskDelay();
+ // Sanity check that previous code didn't move time backward.
+ EXPECT_GE(elapsed, time_source->Now());
+ time_source->SetNow(elapsed);
+ task_runner->RunPendingTasks();
+ EXPECT_TRUE(client.BeganFrame());
+}
+
+TEST(FrameRateControllerTest, TestFrameThrottling_Unthrottled) {
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner =
+ new base::TestSimpleTaskRunner;
+ FakeFrameRateControllerClient client;
+ FrameRateController controller(task_runner.get());
+
+ controller.SetClient(&client);
+ controller.SetMaxSwapsPending(2);
+
+ // SetActive triggers 1st frame, make sure the BeginFrame callback
+ // is called
+ controller.SetActive(true);
+ task_runner->RunPendingTasks();
+ EXPECT_TRUE(client.BeganFrame());
+ client.Reset();
+
+ // Even if we don't call DidSwapBuffers, FrameRateController should
+ // still attempt to tick multiple times until it does result in
+ // a DidSwapBuffers.
+ task_runner->RunPendingTasks();
+ EXPECT_TRUE(client.BeganFrame());
+ client.Reset();
+
+ task_runner->RunPendingTasks();
+ EXPECT_TRUE(client.BeganFrame());
+ client.Reset();
+
+ // DidSwapBuffers triggers 2nd frame, make sure the BeginFrame callback is
+ // called
+ controller.DidSwapBuffers();
+ task_runner->RunPendingTasks();
+ EXPECT_TRUE(client.BeganFrame());
+ client.Reset();
+
+ // DidSwapBuffers triggers 3rd frame (> max_frames_pending),
+ // make sure the BeginFrame callback is NOT called
+ controller.DidSwapBuffers();
+ task_runner->RunPendingTasks();
+ EXPECT_FALSE(client.BeganFrame());
+ client.Reset();
+
+ // Make sure there is no pending task since we can't do anything until we
+ // receive a DidSwapBuffersComplete anyway.
+ EXPECT_FALSE(task_runner->HasPendingTask());
+
+ // DidSwapBuffersComplete triggers a frame, make sure the BeginFrame
+ // callback is called
+ controller.DidSwapBuffersComplete();
+ task_runner->RunPendingTasks();
+ EXPECT_TRUE(client.BeganFrame());
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/scheduler/rate_limiter.cc b/chromium/cc/scheduler/rate_limiter.cc
new file mode 100644
index 00000000000..d89d95b8cf2
--- /dev/null
+++ b/chromium/cc/scheduler/rate_limiter.cc
@@ -0,0 +1,61 @@
+// Copyright 2011 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 "cc/scheduler/rate_limiter.h"
+
+#include "base/bind.h"
+#include "base/debug/trace_event.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/single_thread_task_runner.h"
+#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h"
+
+namespace cc {
+
+scoped_refptr<RateLimiter> RateLimiter::Create(
+ WebKit::WebGraphicsContext3D* context,
+ RateLimiterClient* client,
+ base::SingleThreadTaskRunner* task_runner) {
+ return make_scoped_refptr(new RateLimiter(context, client, task_runner));
+}
+
+RateLimiter::RateLimiter(WebKit::WebGraphicsContext3D* context,
+ RateLimiterClient* client,
+ base::SingleThreadTaskRunner* task_runner)
+ : context_(context),
+ active_(false),
+ client_(client),
+ task_runner_(task_runner) {
+ DCHECK(context);
+}
+
+RateLimiter::~RateLimiter() {}
+
+void RateLimiter::Start() {
+ if (active_)
+ return;
+
+ TRACE_EVENT0("cc", "RateLimiter::Start");
+ active_ = true;
+ task_runner_->PostTask(FROM_HERE,
+ base::Bind(&RateLimiter::RateLimitContext, this));
+}
+
+void RateLimiter::Stop() {
+ TRACE_EVENT0("cc", "RateLimiter::Stop");
+ client_ = NULL;
+}
+
+void RateLimiter::RateLimitContext() {
+ if (!client_)
+ return;
+
+ TRACE_EVENT0("cc", "RateLimiter::RateLimitContext");
+
+ active_ = false;
+ client_->RateLimit();
+ context_->rateLimitOffscreenContextCHROMIUM();
+}
+
+} // namespace cc
diff --git a/chromium/cc/scheduler/rate_limiter.h b/chromium/cc/scheduler/rate_limiter.h
new file mode 100644
index 00000000000..04b90772e36
--- /dev/null
+++ b/chromium/cc/scheduler/rate_limiter.h
@@ -0,0 +1,61 @@
+// Copyright 2011 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.
+
+#ifndef CC_SCHEDULER_RATE_LIMITER_H_
+#define CC_SCHEDULER_RATE_LIMITER_H_
+
+#include "base/memory/ref_counted.h"
+
+namespace base { class SingleThreadTaskRunner; }
+
+namespace WebKit { class WebGraphicsContext3D; }
+
+namespace cc {
+
+class RateLimiterClient {
+ public:
+ virtual void RateLimit() = 0;
+
+ protected:
+ virtual ~RateLimiterClient() {}
+};
+
+// A RateLimiter can be used to make sure that a single context does not
+// dominate all execution time. To use, construct a RateLimiter class around
+// the context and call Start() whenever calls are made on the context outside
+// of normal flow control. RateLimiter will block if the context is too far
+// ahead of the compositor.
+class RateLimiter : public base::RefCounted<RateLimiter> {
+ public:
+ static scoped_refptr<RateLimiter> Create(
+ WebKit::WebGraphicsContext3D* context,
+ RateLimiterClient* client,
+ base::SingleThreadTaskRunner* task_runner);
+
+ void Start();
+
+ // Context and client will not be accessed after Stop().
+ void Stop();
+
+ private:
+ friend class base::RefCounted<RateLimiter>;
+
+ RateLimiter(WebKit::WebGraphicsContext3D* context,
+ RateLimiterClient* client,
+ base::SingleThreadTaskRunner* task_runner);
+ ~RateLimiter();
+
+ void RateLimitContext();
+
+ WebKit::WebGraphicsContext3D* context_;
+ bool active_;
+ RateLimiterClient* client_;
+ base::SingleThreadTaskRunner* task_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(RateLimiter);
+};
+
+} // namespace cc
+
+#endif // CC_SCHEDULER_RATE_LIMITER_H_
diff --git a/chromium/cc/scheduler/rolling_time_delta_history.cc b/chromium/cc/scheduler/rolling_time_delta_history.cc
new file mode 100644
index 00000000000..8d8bba5032f
--- /dev/null
+++ b/chromium/cc/scheduler/rolling_time_delta_history.cc
@@ -0,0 +1,65 @@
+// Copyright 2013 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 <cmath>
+
+#include "cc/scheduler/rolling_time_delta_history.h"
+
+namespace cc {
+
+RollingTimeDeltaHistory::RollingTimeDeltaHistory(size_t max_size)
+ : max_size_(max_size) {
+}
+
+RollingTimeDeltaHistory::~RollingTimeDeltaHistory() {
+}
+
+void RollingTimeDeltaHistory::InsertSample(base::TimeDelta time) {
+ if (max_size_ == 0)
+ return;
+
+ if (sample_set_.size() == max_size_) {
+ sample_set_.erase(chronological_sample_deque_.front());
+ chronological_sample_deque_.pop_front();
+ }
+
+ TimeDeltaMultiset::iterator it = sample_set_.insert(time);
+ chronological_sample_deque_.push_back(it);
+}
+
+void RollingTimeDeltaHistory::Clear() {
+ chronological_sample_deque_.clear();
+ sample_set_.clear();
+}
+
+base::TimeDelta RollingTimeDeltaHistory::Percentile(double percent) const {
+ if (sample_set_.size() == 0)
+ return base::TimeDelta();
+
+ double fraction = percent / 100.0;
+
+ if (fraction <= 0.0)
+ return *(sample_set_.begin());
+
+ if (fraction >= 1.0)
+ return *(sample_set_.rbegin());
+
+ size_t num_smaller_samples =
+ static_cast<size_t>(std::ceil(fraction * sample_set_.size())) - 1;
+
+ if (num_smaller_samples > sample_set_.size() / 2) {
+ size_t num_larger_samples = sample_set_.size() - num_smaller_samples - 1;
+ TimeDeltaMultiset::const_reverse_iterator it = sample_set_.rbegin();
+ for (size_t i = 0; i < num_larger_samples; i++)
+ it++;
+ return *it;
+ }
+
+ TimeDeltaMultiset::const_iterator it = sample_set_.begin();
+ for (size_t i = 0; i < num_smaller_samples; i++)
+ it++;
+ return *it;
+}
+
+} // namespace cc
diff --git a/chromium/cc/scheduler/rolling_time_delta_history.h b/chromium/cc/scheduler/rolling_time_delta_history.h
new file mode 100644
index 00000000000..b79862d6dae
--- /dev/null
+++ b/chromium/cc/scheduler/rolling_time_delta_history.h
@@ -0,0 +1,44 @@
+// Copyright 2013 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.
+
+#ifndef CC_SCHEDULER_ROLLING_TIME_DELTA_HISTORY_H_
+#define CC_SCHEDULER_ROLLING_TIME_DELTA_HISTORY_H_
+
+#include <deque>
+#include <set>
+
+#include "base/time/time.h"
+#include "cc/base/cc_export.h"
+
+namespace cc {
+
+// Stores a limited number of samples. When the maximum size is reached, each
+// insertion results in the deletion of the oldest remaining sample.
+class CC_EXPORT RollingTimeDeltaHistory {
+ public:
+ explicit RollingTimeDeltaHistory(size_t max_size);
+
+ ~RollingTimeDeltaHistory();
+
+ void InsertSample(base::TimeDelta time);
+
+ void Clear();
+
+ // Returns the smallest sample that is greater than or equal to the specified
+ // percent of samples. If there aren't any samples, returns base::TimeDelta().
+ base::TimeDelta Percentile(double percent) const;
+
+ private:
+ typedef std::multiset<base::TimeDelta> TimeDeltaMultiset;
+
+ TimeDeltaMultiset sample_set_;
+ std::deque<TimeDeltaMultiset::iterator> chronological_sample_deque_;
+ size_t max_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(RollingTimeDeltaHistory);
+};
+
+} // namespace cc
+
+#endif // CC_SCHEDULER_ROLLING_TIME_DELTA_HISTORY_H_
diff --git a/chromium/cc/scheduler/rolling_time_delta_history_unittest.cc b/chromium/cc/scheduler/rolling_time_delta_history_unittest.cc
new file mode 100644
index 00000000000..072c98ab386
--- /dev/null
+++ b/chromium/cc/scheduler/rolling_time_delta_history_unittest.cc
@@ -0,0 +1,109 @@
+// Copyright 2013 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 "cc/scheduler/rolling_time_delta_history.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+TEST(RollingTimeDeltaHistoryTest, EmptyHistory) {
+ RollingTimeDeltaHistory empty_history(0);
+
+ EXPECT_EQ(base::TimeDelta(), empty_history.Percentile(0.0));
+ EXPECT_EQ(base::TimeDelta(), empty_history.Percentile(50.0));
+ EXPECT_EQ(base::TimeDelta(), empty_history.Percentile(100.0));
+
+ empty_history.InsertSample(base::TimeDelta::FromMilliseconds(10));
+ empty_history.InsertSample(base::TimeDelta::FromMilliseconds(15));
+ empty_history.InsertSample(base::TimeDelta::FromMilliseconds(20));
+
+ EXPECT_EQ(base::TimeDelta(), empty_history.Percentile(0.0));
+ EXPECT_EQ(base::TimeDelta(), empty_history.Percentile(50.0));
+ EXPECT_EQ(base::TimeDelta(), empty_history.Percentile(100.0));
+
+ empty_history.Clear();
+ EXPECT_EQ(base::TimeDelta(), empty_history.Percentile(0.0));
+ EXPECT_EQ(base::TimeDelta(), empty_history.Percentile(50.0));
+ EXPECT_EQ(base::TimeDelta(), empty_history.Percentile(100.0));
+}
+
+TEST(RollingTimeDeltaHistoryTest, SizeOneHistory) {
+ RollingTimeDeltaHistory size_one_history(1);
+ base::TimeDelta sample1 = base::TimeDelta::FromMilliseconds(10);
+ base::TimeDelta sample2 = base::TimeDelta::FromMilliseconds(20);
+
+ EXPECT_EQ(base::TimeDelta(), size_one_history.Percentile(0.0));
+ EXPECT_EQ(base::TimeDelta(), size_one_history.Percentile(50.0));
+ EXPECT_EQ(base::TimeDelta(), size_one_history.Percentile(100.0));
+
+ size_one_history.InsertSample(sample1);
+ EXPECT_EQ(sample1, size_one_history.Percentile(0.0));
+ EXPECT_EQ(sample1, size_one_history.Percentile(50.0));
+ EXPECT_EQ(sample1, size_one_history.Percentile(100.0));
+
+ size_one_history.InsertSample(sample2);
+ EXPECT_EQ(sample2, size_one_history.Percentile(0.0));
+ EXPECT_EQ(sample2, size_one_history.Percentile(50.0));
+ EXPECT_EQ(sample2, size_one_history.Percentile(100.0));
+
+ size_one_history.Clear();
+ EXPECT_EQ(base::TimeDelta(), size_one_history.Percentile(0.0));
+ EXPECT_EQ(base::TimeDelta(), size_one_history.Percentile(50.0));
+ EXPECT_EQ(base::TimeDelta(), size_one_history.Percentile(100.0));
+}
+
+TEST(RollingTimeDeltaHistoryTest, LargeHistory) {
+ RollingTimeDeltaHistory large_history(100);
+ base::TimeDelta sample1 = base::TimeDelta::FromMilliseconds(150);
+ base::TimeDelta sample2 = base::TimeDelta::FromMilliseconds(250);
+ base::TimeDelta sample3 = base::TimeDelta::FromMilliseconds(200);
+
+ large_history.InsertSample(sample1);
+ large_history.InsertSample(sample2);
+
+ EXPECT_EQ(sample1, large_history.Percentile(0.0));
+ EXPECT_EQ(sample1, large_history.Percentile(25.0));
+ EXPECT_EQ(sample2, large_history.Percentile(75.0));
+ EXPECT_EQ(sample2, large_history.Percentile(100.0));
+
+ large_history.InsertSample(sample3);
+ EXPECT_EQ(sample1, large_history.Percentile(0.0));
+ EXPECT_EQ(sample1, large_history.Percentile(25.0));
+ EXPECT_EQ(sample3, large_history.Percentile(50.0));
+ EXPECT_EQ(sample2, large_history.Percentile(100.0));
+
+ // Fill the history.
+ for (int i = 1; i <= 97; i++)
+ large_history.InsertSample(base::TimeDelta::FromMilliseconds(i));
+
+ EXPECT_EQ(base::TimeDelta::FromMilliseconds(1),
+ large_history.Percentile(0.0));
+ for (int i = 1; i <= 97; i++) {
+ EXPECT_EQ(base::TimeDelta::FromMilliseconds(i),
+ large_history.Percentile(i - 0.5));
+ }
+ EXPECT_EQ(sample1, large_history.Percentile(97.5));
+ EXPECT_EQ(sample3, large_history.Percentile(98.5));
+ EXPECT_EQ(sample2, large_history.Percentile(99.5));
+
+ // Continue inserting samples, causing the oldest samples to be discarded.
+ base::TimeDelta sample4 = base::TimeDelta::FromMilliseconds(100);
+ base::TimeDelta sample5 = base::TimeDelta::FromMilliseconds(102);
+ base::TimeDelta sample6 = base::TimeDelta::FromMilliseconds(104);
+ large_history.InsertSample(sample4);
+ large_history.InsertSample(sample5);
+ large_history.InsertSample(sample6);
+ EXPECT_EQ(sample4, large_history.Percentile(97.5));
+ EXPECT_EQ(sample5, large_history.Percentile(98.5));
+ EXPECT_EQ(sample6, large_history.Percentile(99.5));
+
+ large_history.Clear();
+ EXPECT_EQ(base::TimeDelta(), large_history.Percentile(0.0));
+ EXPECT_EQ(base::TimeDelta(), large_history.Percentile(50.0));
+ EXPECT_EQ(base::TimeDelta(), large_history.Percentile(100.0));
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/scheduler/scheduler.cc b/chromium/cc/scheduler/scheduler.cc
new file mode 100644
index 00000000000..010cc990b47
--- /dev/null
+++ b/chromium/cc/scheduler/scheduler.cc
@@ -0,0 +1,244 @@
+// Copyright 2011 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 "cc/scheduler/scheduler.h"
+
+#include "base/auto_reset.h"
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+
+namespace cc {
+
+Scheduler::Scheduler(SchedulerClient* client,
+ const SchedulerSettings& scheduler_settings)
+ : settings_(scheduler_settings),
+ client_(client),
+ weak_factory_(this),
+ last_set_needs_begin_frame_(false),
+ has_pending_begin_frame_(false),
+ safe_to_expect_begin_frame_(false),
+ state_machine_(scheduler_settings),
+ inside_process_scheduled_actions_(false) {
+ DCHECK(client_);
+ DCHECK(!state_machine_.BeginFrameNeededToDrawByImplThread());
+}
+
+Scheduler::~Scheduler() {
+ client_->SetNeedsBeginFrameOnImplThread(false);
+}
+
+void Scheduler::SetCanStart() {
+ state_machine_.SetCanStart();
+ ProcessScheduledActions();
+}
+
+void Scheduler::SetVisible(bool visible) {
+ state_machine_.SetVisible(visible);
+ ProcessScheduledActions();
+}
+
+void Scheduler::SetCanDraw(bool can_draw) {
+ state_machine_.SetCanDraw(can_draw);
+ ProcessScheduledActions();
+}
+
+void Scheduler::SetHasPendingTree(bool has_pending_tree) {
+ state_machine_.SetHasPendingTree(has_pending_tree);
+ ProcessScheduledActions();
+}
+
+void Scheduler::SetNeedsCommit() {
+ state_machine_.SetNeedsCommit();
+ ProcessScheduledActions();
+}
+
+void Scheduler::SetNeedsForcedCommit() {
+ state_machine_.SetNeedsCommit();
+ state_machine_.SetNeedsForcedCommit();
+ ProcessScheduledActions();
+}
+
+void Scheduler::SetNeedsRedraw() {
+ state_machine_.SetNeedsRedraw();
+ ProcessScheduledActions();
+}
+
+void Scheduler::DidSwapUseIncompleteTile() {
+ state_machine_.DidSwapUseIncompleteTile();
+ ProcessScheduledActions();
+}
+
+void Scheduler::SetNeedsForcedRedraw() {
+ state_machine_.SetNeedsForcedRedraw();
+ ProcessScheduledActions();
+}
+
+void Scheduler::SetMainThreadNeedsLayerTextures() {
+ state_machine_.SetMainThreadNeedsLayerTextures();
+ ProcessScheduledActions();
+}
+
+void Scheduler::FinishCommit() {
+ TRACE_EVENT0("cc", "Scheduler::FinishCommit");
+ state_machine_.FinishCommit();
+ ProcessScheduledActions();
+}
+
+void Scheduler::BeginFrameAbortedByMainThread(bool did_handle) {
+ TRACE_EVENT0("cc", "Scheduler::BeginFrameAbortedByMainThread");
+ state_machine_.BeginFrameAbortedByMainThread(did_handle);
+ ProcessScheduledActions();
+}
+
+void Scheduler::DidLoseOutputSurface() {
+ TRACE_EVENT0("cc", "Scheduler::DidLoseOutputSurface");
+ state_machine_.DidLoseOutputSurface();
+ ProcessScheduledActions();
+}
+
+void Scheduler::DidCreateAndInitializeOutputSurface() {
+ TRACE_EVENT0("cc", "Scheduler::DidCreateAndInitializeOutputSurface");
+ state_machine_.DidCreateAndInitializeOutputSurface();
+ has_pending_begin_frame_ = false;
+ last_set_needs_begin_frame_ = false;
+ safe_to_expect_begin_frame_ = false;
+ ProcessScheduledActions();
+}
+
+base::TimeTicks Scheduler::AnticipatedDrawTime() {
+ TRACE_EVENT0("cc", "Scheduler::AnticipatedDrawTime");
+
+ if (!last_set_needs_begin_frame_ ||
+ last_begin_frame_args_.interval <= base::TimeDelta())
+ return base::TimeTicks();
+
+ // TODO(brianderson): Express this in terms of the deadline.
+ base::TimeTicks now = base::TimeTicks::Now();
+ int64 intervals = 1 + ((now - last_begin_frame_args_.frame_time) /
+ last_begin_frame_args_.interval);
+ return last_begin_frame_args_.frame_time +
+ (last_begin_frame_args_.interval * intervals);
+}
+
+base::TimeTicks Scheduler::LastBeginFrameOnImplThreadTime() {
+ return last_begin_frame_args_.frame_time;
+}
+
+void Scheduler::SetupNextBeginFrameIfNeeded() {
+ bool needs_begin_frame_to_draw =
+ state_machine_.BeginFrameNeededToDrawByImplThread();
+ // We want to avoid proactive begin frames with the synchronous compositor
+ // because every SetNeedsBeginFrame will force a redraw.
+ bool proactive_begin_frame_wanted =
+ state_machine_.ProactiveBeginFrameWantedByImplThread() &&
+ !settings_.using_synchronous_renderer_compositor &&
+ settings_.throttle_frame_production;
+ bool needs_begin_frame = needs_begin_frame_to_draw ||
+ proactive_begin_frame_wanted;
+ bool immediate_disables_needed =
+ settings_.using_synchronous_renderer_compositor;
+
+ if (needs_begin_frame_to_draw)
+ safe_to_expect_begin_frame_ = true;
+
+ // Determine if we need BeginFrame notifications.
+ // If we do, always request the BeginFrame immediately.
+ // If not, only disable on the next BeginFrame to avoid unnecessary toggles.
+ // The synchronous renderer compositor requires immediate disables though.
+ if ((needs_begin_frame ||
+ state_machine_.inside_begin_frame() ||
+ immediate_disables_needed) &&
+ (needs_begin_frame != last_set_needs_begin_frame_)) {
+ has_pending_begin_frame_ = false;
+ client_->SetNeedsBeginFrameOnImplThread(needs_begin_frame);
+ if (safe_to_expect_begin_frame_)
+ last_set_needs_begin_frame_ = needs_begin_frame;
+ }
+
+ // Request another BeginFrame if we haven't drawn for now until we have
+ // deadlines implemented.
+ if (state_machine_.inside_begin_frame() && has_pending_begin_frame_) {
+ has_pending_begin_frame_ = false;
+ client_->SetNeedsBeginFrameOnImplThread(true);
+ return;
+ }
+}
+
+void Scheduler::BeginFrame(const BeginFrameArgs& args) {
+ TRACE_EVENT0("cc", "Scheduler::BeginFrame");
+ DCHECK(!has_pending_begin_frame_);
+ has_pending_begin_frame_ = true;
+ safe_to_expect_begin_frame_ = true;
+ last_begin_frame_args_ = args;
+ state_machine_.DidEnterBeginFrame(args);
+ ProcessScheduledActions();
+ state_machine_.DidLeaveBeginFrame();
+}
+
+void Scheduler::DrawAndSwapIfPossible() {
+ ScheduledActionDrawAndSwapResult result =
+ client_->ScheduledActionDrawAndSwapIfPossible();
+ state_machine_.DidDrawIfPossibleCompleted(result.did_draw);
+ if (result.did_swap)
+ has_pending_begin_frame_ = false;
+}
+
+void Scheduler::DrawAndSwapForced() {
+ ScheduledActionDrawAndSwapResult result =
+ client_->ScheduledActionDrawAndSwapForced();
+ if (result.did_swap)
+ has_pending_begin_frame_ = false;
+}
+
+void Scheduler::ProcessScheduledActions() {
+ // We do not allow ProcessScheduledActions to be recursive.
+ // The top-level call will iteratively execute the next action for us anyway.
+ if (inside_process_scheduled_actions_)
+ return;
+
+ base::AutoReset<bool> mark_inside(&inside_process_scheduled_actions_, true);
+
+ SchedulerStateMachine::Action action = state_machine_.NextAction();
+ while (action != SchedulerStateMachine::ACTION_NONE) {
+ state_machine_.UpdateState(action);
+ switch (action) {
+ case SchedulerStateMachine::ACTION_NONE:
+ break;
+ case SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD:
+ client_->ScheduledActionSendBeginFrameToMainThread();
+ break;
+ case SchedulerStateMachine::ACTION_COMMIT:
+ client_->ScheduledActionCommit();
+ break;
+ case SchedulerStateMachine::ACTION_UPDATE_VISIBLE_TILES:
+ client_->ScheduledActionUpdateVisibleTiles();
+ break;
+ case SchedulerStateMachine::ACTION_ACTIVATE_PENDING_TREE_IF_NEEDED:
+ client_->ScheduledActionActivatePendingTreeIfNeeded();
+ break;
+ case SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE:
+ DrawAndSwapIfPossible();
+ break;
+ case SchedulerStateMachine::ACTION_DRAW_FORCED:
+ DrawAndSwapForced();
+ break;
+ case SchedulerStateMachine::ACTION_BEGIN_OUTPUT_SURFACE_CREATION:
+ client_->ScheduledActionBeginOutputSurfaceCreation();
+ break;
+ case SchedulerStateMachine::ACTION_ACQUIRE_LAYER_TEXTURES_FOR_MAIN_THREAD:
+ client_->ScheduledActionAcquireLayerTexturesForMainThread();
+ break;
+ }
+ action = state_machine_.NextAction();
+ }
+
+ SetupNextBeginFrameIfNeeded();
+ client_->DidAnticipatedDrawTimeChange(AnticipatedDrawTime());
+}
+
+bool Scheduler::WillDrawIfNeeded() const {
+ return !state_machine_.DrawSuspendedUntilCommit();
+}
+
+} // namespace cc
diff --git a/chromium/cc/scheduler/scheduler.h b/chromium/cc/scheduler/scheduler.h
new file mode 100644
index 00000000000..230f20d0ec9
--- /dev/null
+++ b/chromium/cc/scheduler/scheduler.h
@@ -0,0 +1,138 @@
+// Copyright 2011 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.
+
+#ifndef CC_SCHEDULER_SCHEDULER_H_
+#define CC_SCHEDULER_SCHEDULER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "cc/base/cc_export.h"
+#include "cc/output/begin_frame_args.h"
+#include "cc/scheduler/scheduler_settings.h"
+#include "cc/scheduler/scheduler_state_machine.h"
+#include "cc/trees/layer_tree_host.h"
+
+namespace cc {
+
+class Thread;
+
+struct ScheduledActionDrawAndSwapResult {
+ ScheduledActionDrawAndSwapResult()
+ : did_draw(false),
+ did_swap(false) {}
+ ScheduledActionDrawAndSwapResult(bool did_draw, bool did_swap)
+ : did_draw(did_draw),
+ did_swap(did_swap) {}
+ bool did_draw;
+ bool did_swap;
+};
+
+class SchedulerClient {
+ public:
+ virtual void SetNeedsBeginFrameOnImplThread(bool enable) = 0;
+ virtual void ScheduledActionSendBeginFrameToMainThread() = 0;
+ virtual ScheduledActionDrawAndSwapResult
+ ScheduledActionDrawAndSwapIfPossible() = 0;
+ virtual ScheduledActionDrawAndSwapResult
+ ScheduledActionDrawAndSwapForced() = 0;
+ virtual void ScheduledActionCommit() = 0;
+ virtual void ScheduledActionUpdateVisibleTiles() = 0;
+ virtual void ScheduledActionActivatePendingTreeIfNeeded() = 0;
+ virtual void ScheduledActionBeginOutputSurfaceCreation() = 0;
+ virtual void ScheduledActionAcquireLayerTexturesForMainThread() = 0;
+ virtual void DidAnticipatedDrawTimeChange(base::TimeTicks time) = 0;
+ virtual base::TimeDelta DrawDurationEstimate() = 0;
+ virtual base::TimeDelta BeginFrameToCommitDurationEstimate() = 0;
+ virtual base::TimeDelta CommitToActivateDurationEstimate() = 0;
+
+ protected:
+ virtual ~SchedulerClient() {}
+};
+
+class CC_EXPORT Scheduler {
+ public:
+ static scoped_ptr<Scheduler> Create(
+ SchedulerClient* client,
+ const SchedulerSettings& scheduler_settings) {
+ return make_scoped_ptr(new Scheduler(client, scheduler_settings));
+ }
+
+ virtual ~Scheduler();
+
+ void SetCanStart();
+
+ void SetVisible(bool visible);
+ void SetCanDraw(bool can_draw);
+ void SetHasPendingTree(bool has_pending_tree);
+
+ void SetNeedsCommit();
+
+ // Like SetNeedsCommit(), but ensures a commit will definitely happen even if
+ // we are not visible.
+ void SetNeedsForcedCommit();
+
+ void SetNeedsRedraw();
+
+ void SetMainThreadNeedsLayerTextures();
+
+ // Like SetNeedsRedraw(), but ensures the draw will definitely happen even if
+ // we are not visible.
+ void SetNeedsForcedRedraw();
+
+ void DidSwapUseIncompleteTile();
+
+ void FinishCommit();
+ void BeginFrameAbortedByMainThread(bool did_handle);
+
+ void DidLoseOutputSurface();
+ void DidCreateAndInitializeOutputSurface();
+ bool HasInitializedOutputSurface() const {
+ return state_machine_.HasInitializedOutputSurface();
+ }
+
+ bool CommitPending() const { return state_machine_.CommitPending(); }
+ bool RedrawPending() const { return state_machine_.RedrawPending(); }
+
+ bool WillDrawIfNeeded() const;
+
+ base::TimeTicks AnticipatedDrawTime();
+
+ base::TimeTicks LastBeginFrameOnImplThreadTime();
+
+ void BeginFrame(const BeginFrameArgs& args);
+
+ std::string StateAsStringForTesting() { return state_machine_.ToString(); }
+
+ private:
+ Scheduler(SchedulerClient* client,
+ const SchedulerSettings& scheduler_settings);
+
+ void SetupNextBeginFrameIfNeeded();
+ void DrawAndSwapIfPossible();
+ void DrawAndSwapForced();
+ void ProcessScheduledActions();
+
+ const SchedulerSettings settings_;
+ SchedulerClient* client_;
+
+ base::WeakPtrFactory<Scheduler> weak_factory_;
+ bool last_set_needs_begin_frame_;
+ bool has_pending_begin_frame_;
+ // TODO(brianderson): crbug.com/249806 : Remove safe_to_expect_begin_frame_
+ // workaround.
+ bool safe_to_expect_begin_frame_;
+ BeginFrameArgs last_begin_frame_args_;
+
+ SchedulerStateMachine state_machine_;
+ bool inside_process_scheduled_actions_;
+
+ DISALLOW_COPY_AND_ASSIGN(Scheduler);
+};
+
+} // namespace cc
+
+#endif // CC_SCHEDULER_SCHEDULER_H_
diff --git a/chromium/cc/scheduler/scheduler_settings.cc b/chromium/cc/scheduler/scheduler_settings.cc
new file mode 100644
index 00000000000..90e9e89bbdc
--- /dev/null
+++ b/chromium/cc/scheduler/scheduler_settings.cc
@@ -0,0 +1,17 @@
+// Copyright 2013 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 "cc/scheduler/scheduler_settings.h"
+
+namespace cc {
+
+SchedulerSettings::SchedulerSettings()
+ : impl_side_painting(false),
+ timeout_and_draw_when_animation_checkerboards(true),
+ using_synchronous_renderer_compositor(false),
+ throttle_frame_production(true) {}
+
+SchedulerSettings::~SchedulerSettings() {}
+
+} // namespace cc
diff --git a/chromium/cc/scheduler/scheduler_settings.h b/chromium/cc/scheduler/scheduler_settings.h
new file mode 100644
index 00000000000..ff00fc32b36
--- /dev/null
+++ b/chromium/cc/scheduler/scheduler_settings.h
@@ -0,0 +1,25 @@
+// Copyright 2013 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.
+
+#ifndef CC_SCHEDULER_SCHEDULER_SETTINGS_H_
+#define CC_SCHEDULER_SCHEDULER_SETTINGS_H_
+
+#include "cc/base/cc_export.h"
+
+namespace cc {
+
+class CC_EXPORT SchedulerSettings {
+ public:
+ SchedulerSettings();
+ ~SchedulerSettings();
+
+ bool impl_side_painting;
+ bool timeout_and_draw_when_animation_checkerboards;
+ bool using_synchronous_renderer_compositor;
+ bool throttle_frame_production;
+};
+
+} // namespace cc
+
+#endif // CC_SCHEDULER_SCHEDULER_SETTINGS_H_
diff --git a/chromium/cc/scheduler/scheduler_state_machine.cc b/chromium/cc/scheduler/scheduler_state_machine.cc
new file mode 100644
index 00000000000..ed9c15f43f9
--- /dev/null
+++ b/chromium/cc/scheduler/scheduler_state_machine.cc
@@ -0,0 +1,500 @@
+// Copyright 2011 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 "cc/scheduler/scheduler_state_machine.h"
+
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+
+namespace cc {
+
+SchedulerStateMachine::SchedulerStateMachine(const SchedulerSettings& settings)
+ : settings_(settings),
+ commit_state_(COMMIT_STATE_IDLE),
+ commit_count_(0),
+ current_frame_number_(0),
+ last_frame_number_where_begin_frame_sent_to_main_thread_(-1),
+ last_frame_number_where_draw_was_called_(-1),
+ last_frame_number_where_tree_activation_attempted_(-1),
+ last_frame_number_where_update_visible_tiles_was_called_(-1),
+ consecutive_failed_draws_(0),
+ maximum_number_of_failed_draws_before_draw_is_forced_(3),
+ needs_redraw_(false),
+ swap_used_incomplete_tile_(false),
+ needs_forced_redraw_(false),
+ needs_forced_redraw_after_next_commit_(false),
+ needs_redraw_after_next_commit_(false),
+ needs_commit_(false),
+ needs_forced_commit_(false),
+ expect_immediate_begin_frame_for_main_thread_(false),
+ main_thread_needs_layer_textures_(false),
+ inside_begin_frame_(false),
+ visible_(false),
+ can_start_(false),
+ can_draw_(false),
+ has_pending_tree_(false),
+ draw_if_possible_failed_(false),
+ texture_state_(LAYER_TEXTURE_STATE_UNLOCKED),
+ output_surface_state_(OUTPUT_SURFACE_LOST),
+ did_create_and_initialize_first_output_surface_(false) {}
+
+std::string SchedulerStateMachine::ToString() {
+ std::string str;
+ base::StringAppendF(&str,
+ "settings_.impl_side_painting = %d; ",
+ settings_.impl_side_painting);
+ base::StringAppendF(&str, "commit_state_ = %d; ", commit_state_);
+ base::StringAppendF(&str, "commit_count_ = %d; ", commit_count_);
+ base::StringAppendF(
+ &str, "current_frame_number_ = %d; ", current_frame_number_);
+ base::StringAppendF(&str,
+ "last_frame_number_where_draw_was_called_ = %d; ",
+ last_frame_number_where_draw_was_called_);
+ base::StringAppendF(
+ &str,
+ "last_frame_number_where_tree_activation_attempted_ = %d; ",
+ last_frame_number_where_tree_activation_attempted_);
+ base::StringAppendF(
+ &str,
+ "last_frame_number_where_update_visible_tiles_was_called_ = %d; ",
+ last_frame_number_where_update_visible_tiles_was_called_);
+ base::StringAppendF(
+ &str, "consecutive_failed_draws_ = %d; ", consecutive_failed_draws_);
+ base::StringAppendF(
+ &str,
+ "maximum_number_of_failed_draws_before_draw_is_forced_ = %d; ",
+ maximum_number_of_failed_draws_before_draw_is_forced_);
+ base::StringAppendF(&str, "needs_redraw_ = %d; ", needs_redraw_);
+ base::StringAppendF(
+ &str, "swap_used_incomplete_tile_ = %d; ", swap_used_incomplete_tile_);
+ base::StringAppendF(
+ &str, "needs_forced_redraw_ = %d; ", needs_forced_redraw_);
+ base::StringAppendF(&str,
+ "needs_forced_redraw_after_next_commit_ = %d; ",
+ needs_forced_redraw_after_next_commit_);
+ base::StringAppendF(&str, "needs_commit_ = %d; ", needs_commit_);
+ base::StringAppendF(
+ &str, "needs_forced_commit_ = %d; ", needs_forced_commit_);
+ base::StringAppendF(&str,
+ "expect_immediate_begin_frame_for_main_thread_ = %d; ",
+ expect_immediate_begin_frame_for_main_thread_);
+ base::StringAppendF(&str,
+ "main_thread_needs_layer_textures_ = %d; ",
+ main_thread_needs_layer_textures_);
+ base::StringAppendF(&str, "inside_begin_frame_ = %d; ",
+ inside_begin_frame_);
+ base::StringAppendF(&str, "last_frame_time_ = %" PRId64 "; ",
+ (last_begin_frame_args_.frame_time - base::TimeTicks())
+ .InMilliseconds());
+ base::StringAppendF(&str, "last_deadline_ = %" PRId64 "; ",
+ (last_begin_frame_args_.deadline - base::TimeTicks()).InMilliseconds());
+ base::StringAppendF(&str, "last_interval_ = %" PRId64 "; ",
+ last_begin_frame_args_.interval.InMilliseconds());
+ base::StringAppendF(&str, "visible_ = %d; ", visible_);
+ base::StringAppendF(&str, "can_start_ = %d; ", can_start_);
+ base::StringAppendF(&str, "can_draw_ = %d; ", can_draw_);
+ base::StringAppendF(
+ &str, "draw_if_possible_failed_ = %d; ", draw_if_possible_failed_);
+ base::StringAppendF(&str, "has_pending_tree_ = %d; ", has_pending_tree_);
+ base::StringAppendF(&str, "texture_state_ = %d; ", texture_state_);
+ base::StringAppendF(
+ &str, "output_surface_state_ = %d; ", output_surface_state_);
+ return str;
+}
+
+bool SchedulerStateMachine::HasDrawnThisFrame() const {
+ return current_frame_number_ == last_frame_number_where_draw_was_called_;
+}
+
+bool SchedulerStateMachine::HasAttemptedTreeActivationThisFrame() const {
+ return current_frame_number_ ==
+ last_frame_number_where_tree_activation_attempted_;
+}
+
+bool SchedulerStateMachine::HasUpdatedVisibleTilesThisFrame() const {
+ return current_frame_number_ ==
+ last_frame_number_where_update_visible_tiles_was_called_;
+}
+
+void SchedulerStateMachine::SetPostCommitFlags() {
+ // This post-commit work is common to both completed and aborted commits.
+ if (needs_forced_redraw_after_next_commit_) {
+ needs_forced_redraw_after_next_commit_ = false;
+ needs_forced_redraw_ = true;
+ }
+ if (needs_redraw_after_next_commit_) {
+ needs_redraw_after_next_commit_ = false;
+ needs_redraw_ = true;
+ }
+ texture_state_ = LAYER_TEXTURE_STATE_ACQUIRED_BY_IMPL_THREAD;
+}
+
+bool SchedulerStateMachine::DrawSuspendedUntilCommit() const {
+ if (!can_draw_)
+ return true;
+ if (!visible_)
+ return true;
+ if (texture_state_ == LAYER_TEXTURE_STATE_ACQUIRED_BY_MAIN_THREAD)
+ return true;
+ return false;
+}
+
+bool SchedulerStateMachine::ScheduledToDraw() const {
+ if (!needs_redraw_)
+ return false;
+ if (DrawSuspendedUntilCommit())
+ return false;
+ return true;
+}
+
+bool SchedulerStateMachine::ShouldDraw() const {
+ if (needs_forced_redraw_)
+ return true;
+
+ if (!ScheduledToDraw())
+ return false;
+ if (!inside_begin_frame_)
+ return false;
+ if (HasDrawnThisFrame())
+ return false;
+ if (output_surface_state_ != OUTPUT_SURFACE_ACTIVE)
+ return false;
+ return true;
+}
+
+bool SchedulerStateMachine::ShouldAttemptTreeActivation() const {
+ return has_pending_tree_ && inside_begin_frame_ &&
+ !HasAttemptedTreeActivationThisFrame();
+}
+
+bool SchedulerStateMachine::ShouldUpdateVisibleTiles() const {
+ if (!settings_.impl_side_painting)
+ return false;
+ if (HasUpdatedVisibleTilesThisFrame())
+ return false;
+
+ return ShouldAttemptTreeActivation() || ShouldDraw() ||
+ swap_used_incomplete_tile_;
+}
+
+bool SchedulerStateMachine::ShouldAcquireLayerTexturesForMainThread() const {
+ if (!main_thread_needs_layer_textures_)
+ return false;
+ if (texture_state_ == LAYER_TEXTURE_STATE_UNLOCKED)
+ return true;
+ DCHECK_EQ(texture_state_, LAYER_TEXTURE_STATE_ACQUIRED_BY_IMPL_THREAD);
+ // Transfer the lock from impl thread to main thread immediately if the
+ // impl thread is not even scheduled to draw. Guards against deadlocking.
+ if (!ScheduledToDraw())
+ return true;
+ if (!BeginFrameNeededToDrawByImplThread())
+ return true;
+ return false;
+}
+
+SchedulerStateMachine::Action SchedulerStateMachine::NextAction() const {
+ if (ShouldAcquireLayerTexturesForMainThread())
+ return ACTION_ACQUIRE_LAYER_TEXTURES_FOR_MAIN_THREAD;
+
+ switch (commit_state_) {
+ case COMMIT_STATE_IDLE: {
+ if (output_surface_state_ != OUTPUT_SURFACE_ACTIVE &&
+ needs_forced_redraw_)
+ return ACTION_DRAW_FORCED;
+ if (output_surface_state_ != OUTPUT_SURFACE_ACTIVE &&
+ needs_forced_commit_)
+ // TODO(enne): Should probably drop the active tree on force commit.
+ return has_pending_tree_ ? ACTION_NONE
+ : ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD;
+ if (output_surface_state_ == OUTPUT_SURFACE_LOST && can_start_)
+ return ACTION_BEGIN_OUTPUT_SURFACE_CREATION;
+ if (output_surface_state_ == OUTPUT_SURFACE_CREATING)
+ return ACTION_NONE;
+ if (ShouldUpdateVisibleTiles())
+ return ACTION_UPDATE_VISIBLE_TILES;
+ if (ShouldAttemptTreeActivation())
+ return ACTION_ACTIVATE_PENDING_TREE_IF_NEEDED;
+ if (ShouldDraw()) {
+ return needs_forced_redraw_ ? ACTION_DRAW_FORCED
+ : ACTION_DRAW_IF_POSSIBLE;
+ }
+ bool can_commit_this_frame =
+ visible_ &&
+ current_frame_number_ >
+ last_frame_number_where_begin_frame_sent_to_main_thread_;
+ if (needs_commit_ && ((can_commit_this_frame &&
+ output_surface_state_ == OUTPUT_SURFACE_ACTIVE) ||
+ needs_forced_commit_))
+ // TODO(enne): Should probably drop the active tree on force commit.
+ return has_pending_tree_ ? ACTION_NONE
+ : ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD;
+ return ACTION_NONE;
+ }
+ case COMMIT_STATE_FRAME_IN_PROGRESS:
+ if (ShouldUpdateVisibleTiles())
+ return ACTION_UPDATE_VISIBLE_TILES;
+ if (ShouldAttemptTreeActivation())
+ return ACTION_ACTIVATE_PENDING_TREE_IF_NEEDED;
+ if (ShouldDraw()) {
+ return needs_forced_redraw_ ? ACTION_DRAW_FORCED
+ : ACTION_DRAW_IF_POSSIBLE;
+ }
+ return ACTION_NONE;
+
+ case COMMIT_STATE_READY_TO_COMMIT:
+ return ACTION_COMMIT;
+
+ case COMMIT_STATE_WAITING_FOR_FIRST_DRAW: {
+ if (ShouldUpdateVisibleTiles())
+ return ACTION_UPDATE_VISIBLE_TILES;
+ if (ShouldAttemptTreeActivation())
+ return ACTION_ACTIVATE_PENDING_TREE_IF_NEEDED;
+ if (ShouldDraw() || output_surface_state_ == OUTPUT_SURFACE_LOST) {
+ return needs_forced_redraw_ ? ACTION_DRAW_FORCED
+ : ACTION_DRAW_IF_POSSIBLE;
+ }
+ // COMMIT_STATE_WAITING_FOR_FIRST_DRAW wants to enforce a draw. If
+ // can_draw_ is false or textures are not available, proceed to the next
+ // step (similar as in COMMIT_STATE_IDLE).
+ bool can_commit =
+ needs_forced_commit_ ||
+ (visible_ &&
+ current_frame_number_ >
+ last_frame_number_where_begin_frame_sent_to_main_thread_);
+ if (needs_commit_ && can_commit && DrawSuspendedUntilCommit())
+ return has_pending_tree_ ? ACTION_NONE
+ : ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD;
+ return ACTION_NONE;
+ }
+
+ case COMMIT_STATE_WAITING_FOR_FIRST_FORCED_DRAW:
+ if (ShouldUpdateVisibleTiles())
+ return ACTION_UPDATE_VISIBLE_TILES;
+ if (ShouldAttemptTreeActivation())
+ return ACTION_ACTIVATE_PENDING_TREE_IF_NEEDED;
+ if (needs_forced_redraw_)
+ return ACTION_DRAW_FORCED;
+ return ACTION_NONE;
+ }
+ NOTREACHED();
+ return ACTION_NONE;
+}
+
+void SchedulerStateMachine::UpdateState(Action action) {
+ switch (action) {
+ case ACTION_NONE:
+ return;
+
+ case ACTION_UPDATE_VISIBLE_TILES:
+ last_frame_number_where_update_visible_tiles_was_called_ =
+ current_frame_number_;
+ return;
+
+ case ACTION_ACTIVATE_PENDING_TREE_IF_NEEDED:
+ last_frame_number_where_tree_activation_attempted_ =
+ current_frame_number_;
+ return;
+
+ case ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD:
+ DCHECK(!has_pending_tree_);
+ if (!needs_forced_commit_) {
+ DCHECK(visible_);
+ DCHECK_GT(current_frame_number_,
+ last_frame_number_where_begin_frame_sent_to_main_thread_);
+ }
+ commit_state_ = COMMIT_STATE_FRAME_IN_PROGRESS;
+ needs_commit_ = false;
+ needs_forced_commit_ = false;
+ last_frame_number_where_begin_frame_sent_to_main_thread_ =
+ current_frame_number_;
+ return;
+
+ case ACTION_COMMIT:
+ commit_count_++;
+ if (expect_immediate_begin_frame_for_main_thread_)
+ commit_state_ = COMMIT_STATE_WAITING_FOR_FIRST_FORCED_DRAW;
+ else
+ commit_state_ = COMMIT_STATE_WAITING_FOR_FIRST_DRAW;
+ // When impl-side painting, we draw on activation instead of on commit.
+ if (!settings_.impl_side_painting)
+ needs_redraw_ = true;
+ if (draw_if_possible_failed_)
+ last_frame_number_where_draw_was_called_ = -1;
+ SetPostCommitFlags();
+ return;
+
+ case ACTION_DRAW_FORCED:
+ case ACTION_DRAW_IF_POSSIBLE:
+ needs_redraw_ = false;
+ needs_forced_redraw_ = false;
+ draw_if_possible_failed_ = false;
+ swap_used_incomplete_tile_ = false;
+ if (inside_begin_frame_)
+ last_frame_number_where_draw_was_called_ = current_frame_number_;
+ if (commit_state_ == COMMIT_STATE_WAITING_FOR_FIRST_FORCED_DRAW) {
+ DCHECK(expect_immediate_begin_frame_for_main_thread_);
+ commit_state_ = COMMIT_STATE_FRAME_IN_PROGRESS;
+ expect_immediate_begin_frame_for_main_thread_ = false;
+ } else if (commit_state_ == COMMIT_STATE_WAITING_FOR_FIRST_DRAW) {
+ commit_state_ = COMMIT_STATE_IDLE;
+ }
+ if (texture_state_ == LAYER_TEXTURE_STATE_ACQUIRED_BY_IMPL_THREAD)
+ texture_state_ = LAYER_TEXTURE_STATE_UNLOCKED;
+ return;
+
+ case ACTION_BEGIN_OUTPUT_SURFACE_CREATION:
+ DCHECK_EQ(commit_state_, COMMIT_STATE_IDLE);
+ DCHECK_EQ(output_surface_state_, OUTPUT_SURFACE_LOST);
+ output_surface_state_ = OUTPUT_SURFACE_CREATING;
+ return;
+
+ case ACTION_ACQUIRE_LAYER_TEXTURES_FOR_MAIN_THREAD:
+ texture_state_ = LAYER_TEXTURE_STATE_ACQUIRED_BY_MAIN_THREAD;
+ main_thread_needs_layer_textures_ = false;
+ return;
+ }
+}
+
+void SchedulerStateMachine::SetMainThreadNeedsLayerTextures() {
+ DCHECK(!main_thread_needs_layer_textures_);
+ DCHECK_NE(texture_state_, LAYER_TEXTURE_STATE_ACQUIRED_BY_MAIN_THREAD);
+ main_thread_needs_layer_textures_ = true;
+}
+
+bool SchedulerStateMachine::BeginFrameNeededToDrawByImplThread() const {
+ // If we can't draw, don't tick until we are notified that we can draw again.
+ if (!can_draw_)
+ return false;
+
+ if (needs_forced_redraw_)
+ return true;
+
+ if (visible_ && swap_used_incomplete_tile_)
+ return true;
+
+ return needs_redraw_ && visible_ &&
+ output_surface_state_ == OUTPUT_SURFACE_ACTIVE;
+}
+
+bool SchedulerStateMachine::ProactiveBeginFrameWantedByImplThread() const {
+ // Do not be proactive when invisible.
+ if (!visible_ || output_surface_state_ != OUTPUT_SURFACE_ACTIVE)
+ return false;
+
+ // We should proactively request a BeginFrame if a commit or a tree activation
+ // is pending.
+ return (needs_commit_ || needs_forced_commit_ ||
+ commit_state_ != COMMIT_STATE_IDLE || has_pending_tree_);
+}
+
+void SchedulerStateMachine::DidEnterBeginFrame(const BeginFrameArgs& args) {
+ current_frame_number_++;
+ inside_begin_frame_ = true;
+ last_begin_frame_args_ = args;
+}
+
+void SchedulerStateMachine::DidLeaveBeginFrame() {
+ inside_begin_frame_ = false;
+}
+
+void SchedulerStateMachine::SetVisible(bool visible) { visible_ = visible; }
+
+void SchedulerStateMachine::SetNeedsRedraw() { needs_redraw_ = true; }
+
+void SchedulerStateMachine::DidSwapUseIncompleteTile() {
+ swap_used_incomplete_tile_ = true;
+}
+
+void SchedulerStateMachine::SetNeedsForcedRedraw() {
+ needs_forced_redraw_ = true;
+}
+
+void SchedulerStateMachine::DidDrawIfPossibleCompleted(bool success) {
+ draw_if_possible_failed_ = !success;
+ if (draw_if_possible_failed_) {
+ needs_redraw_ = true;
+ needs_commit_ = true;
+ consecutive_failed_draws_++;
+ if (settings_.timeout_and_draw_when_animation_checkerboards &&
+ consecutive_failed_draws_ >=
+ maximum_number_of_failed_draws_before_draw_is_forced_) {
+ consecutive_failed_draws_ = 0;
+ // We need to force a draw, but it doesn't make sense to do this until
+ // we've committed and have new textures.
+ needs_forced_redraw_after_next_commit_ = true;
+ }
+ } else {
+ consecutive_failed_draws_ = 0;
+ }
+}
+
+void SchedulerStateMachine::SetNeedsCommit() { needs_commit_ = true; }
+
+void SchedulerStateMachine::SetNeedsForcedCommit() {
+ needs_forced_commit_ = true;
+ expect_immediate_begin_frame_for_main_thread_ = true;
+}
+
+void SchedulerStateMachine::FinishCommit() {
+ DCHECK(commit_state_ == COMMIT_STATE_FRAME_IN_PROGRESS ||
+ (expect_immediate_begin_frame_for_main_thread_ &&
+ commit_state_ != COMMIT_STATE_IDLE))
+ << ToString();
+ commit_state_ = COMMIT_STATE_READY_TO_COMMIT;
+}
+
+void SchedulerStateMachine::BeginFrameAbortedByMainThread(bool did_handle) {
+ DCHECK_EQ(commit_state_, COMMIT_STATE_FRAME_IN_PROGRESS);
+ if (expect_immediate_begin_frame_for_main_thread_) {
+ expect_immediate_begin_frame_for_main_thread_ = false;
+ } else if (did_handle) {
+ commit_state_ = COMMIT_STATE_IDLE;
+ SetPostCommitFlags();
+ } else {
+ commit_state_ = COMMIT_STATE_IDLE;
+ SetNeedsCommit();
+ }
+}
+
+void SchedulerStateMachine::DidLoseOutputSurface() {
+ if (output_surface_state_ == OUTPUT_SURFACE_LOST ||
+ output_surface_state_ == OUTPUT_SURFACE_CREATING)
+ return;
+ output_surface_state_ = OUTPUT_SURFACE_LOST;
+}
+
+void SchedulerStateMachine::SetHasPendingTree(bool has_pending_tree) {
+ has_pending_tree_ = has_pending_tree;
+}
+
+void SchedulerStateMachine::SetCanDraw(bool can) { can_draw_ = can; }
+
+void SchedulerStateMachine::DidCreateAndInitializeOutputSurface() {
+ DCHECK_EQ(output_surface_state_, OUTPUT_SURFACE_CREATING);
+ output_surface_state_ = OUTPUT_SURFACE_ACTIVE;
+
+ if (did_create_and_initialize_first_output_surface_) {
+ // TODO(boliu): See if we can remove this when impl-side painting is always
+ // on. Does anything on the main thread need to update after recreate?
+ needs_commit_ = true;
+ // If anything has requested a redraw, we don't want to actually draw
+ // when the output surface is restored until things have a chance to
+ // sort themselves out with a commit.
+ needs_redraw_ = false;
+ }
+ needs_redraw_after_next_commit_ = true;
+ did_create_and_initialize_first_output_surface_ = true;
+}
+
+bool SchedulerStateMachine::HasInitializedOutputSurface() const {
+ return output_surface_state_ == OUTPUT_SURFACE_ACTIVE;
+}
+
+void SchedulerStateMachine::SetMaximumNumberOfFailedDrawsBeforeDrawIsForced(
+ int num_draws) {
+ maximum_number_of_failed_draws_before_draw_is_forced_ = num_draws;
+}
+
+} // namespace cc
diff --git a/chromium/cc/scheduler/scheduler_state_machine.h b/chromium/cc/scheduler/scheduler_state_machine.h
new file mode 100644
index 00000000000..5c7d5b5b8a7
--- /dev/null
+++ b/chromium/cc/scheduler/scheduler_state_machine.h
@@ -0,0 +1,212 @@
+// Copyright 2011 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.
+
+#ifndef CC_SCHEDULER_SCHEDULER_STATE_MACHINE_H_
+#define CC_SCHEDULER_SCHEDULER_STATE_MACHINE_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/time/time.h"
+#include "cc/base/cc_export.h"
+#include "cc/output/begin_frame_args.h"
+#include "cc/scheduler/scheduler_settings.h"
+
+namespace cc {
+
+// The SchedulerStateMachine decides how to coordinate main thread activites
+// like painting/running javascript with rendering and input activities on the
+// impl thread.
+//
+// The state machine tracks internal state but is also influenced by external
+// state. Internal state includes things like whether a frame has been
+// requested, while external state includes things like the current time being
+// near to the vblank time.
+//
+// The scheduler seperates "what to do next" from the updating of its internal
+// state to make testing cleaner.
+class CC_EXPORT SchedulerStateMachine {
+ public:
+ // settings must be valid for the lifetime of this class.
+ explicit SchedulerStateMachine(const SchedulerSettings& settings);
+
+ enum CommitState {
+ COMMIT_STATE_IDLE,
+ COMMIT_STATE_FRAME_IN_PROGRESS,
+ COMMIT_STATE_READY_TO_COMMIT,
+ COMMIT_STATE_WAITING_FOR_FIRST_DRAW,
+ COMMIT_STATE_WAITING_FOR_FIRST_FORCED_DRAW,
+ };
+
+ enum TextureState {
+ LAYER_TEXTURE_STATE_UNLOCKED,
+ LAYER_TEXTURE_STATE_ACQUIRED_BY_MAIN_THREAD,
+ LAYER_TEXTURE_STATE_ACQUIRED_BY_IMPL_THREAD,
+ };
+
+ enum OutputSurfaceState {
+ OUTPUT_SURFACE_ACTIVE,
+ OUTPUT_SURFACE_LOST,
+ OUTPUT_SURFACE_CREATING,
+ };
+
+ bool CommitPending() const {
+ return commit_state_ == COMMIT_STATE_FRAME_IN_PROGRESS ||
+ commit_state_ == COMMIT_STATE_READY_TO_COMMIT;
+ }
+
+ bool RedrawPending() const { return needs_redraw_; }
+
+ enum Action {
+ ACTION_NONE,
+ ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ ACTION_COMMIT,
+ ACTION_UPDATE_VISIBLE_TILES,
+ ACTION_ACTIVATE_PENDING_TREE_IF_NEEDED,
+ ACTION_DRAW_IF_POSSIBLE,
+ ACTION_DRAW_FORCED,
+ ACTION_BEGIN_OUTPUT_SURFACE_CREATION,
+ ACTION_ACQUIRE_LAYER_TEXTURES_FOR_MAIN_THREAD,
+ };
+ Action NextAction() const;
+ void UpdateState(Action action);
+
+ // Indicates whether the main thread needs a begin frame callback in order to
+ // make progress.
+ bool BeginFrameNeededToDrawByImplThread() const;
+ bool ProactiveBeginFrameWantedByImplThread() const;
+
+ // Indicates that the system has entered and left a BeginFrame callback.
+ // The scheduler will not draw more than once in a given BeginFrame
+ // callback nor send more than one BeginFrame message.
+ void DidEnterBeginFrame(const BeginFrameArgs& args);
+ void DidLeaveBeginFrame();
+ bool inside_begin_frame() const { return inside_begin_frame_; }
+
+ // Indicates whether the LayerTreeHostImpl is visible.
+ void SetVisible(bool visible);
+
+ // Indicates that a redraw is required, either due to the impl tree changing
+ // or the screen being damaged and simply needing redisplay.
+ void SetNeedsRedraw();
+
+ // As SetNeedsRedraw(), but ensures the draw will definitely happen even if
+ // we are not visible.
+ void SetNeedsForcedRedraw();
+
+ // Indicates that a redraw is required because we are currently rendering
+ // with a low resolution or checkerboarded tile.
+ void DidSwapUseIncompleteTile();
+
+ // Indicates whether ACTION_DRAW_IF_POSSIBLE drew to the screen or not.
+ void DidDrawIfPossibleCompleted(bool success);
+
+ // Indicates that a new commit flow needs to be performed, either to pull
+ // updates from the main thread to the impl, or to push deltas from the impl
+ // thread to main.
+ void SetNeedsCommit();
+
+ // As SetNeedsCommit(), but ensures the begin frame will be sent to the main
+ // thread even if we are not visible. After this call we expect to go through
+ // the forced commit flow and then return to waiting for a non-forced
+ // begin frame to finish.
+ void SetNeedsForcedCommit();
+
+ // Call this only in response to receiving an
+ // ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD from NextAction.
+ // Indicates that all painting is complete.
+ void FinishCommit();
+
+ // Call this only in response to receiving an
+ // ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD from NextAction if the client
+ // rejects the begin frame message. If did_handle is false, then
+ // another commit will be retried soon.
+ void BeginFrameAbortedByMainThread(bool did_handle);
+
+ // Request exclusive access to the textures that back single buffered
+ // layers on behalf of the main thread. Upon acquisition,
+ // ACTION_DRAW_IF_POSSIBLE will not draw until the main thread releases the
+ // textures to the impl thread by committing the layers.
+ void SetMainThreadNeedsLayerTextures();
+
+ // Set that we can create the first OutputSurface and start the scheduler.
+ void SetCanStart() { can_start_ = true; }
+
+ // Indicates whether drawing would, at this time, make sense.
+ // CanDraw can be used to supress flashes or checkerboarding
+ // when such behavior would be undesirable.
+ void SetCanDraw(bool can);
+
+ // Indicates whether or not there is a pending tree. This influences
+ // whether or not we can succesfully commit at this time. If the
+ // last commit is still being processed (but not blocking), it may not
+ // be possible to take another commit yet. This overrides force commit,
+ // as a commit is already still in flight.
+ void SetHasPendingTree(bool has_pending_tree);
+ bool has_pending_tree() const { return has_pending_tree_; }
+
+ void DidLoseOutputSurface();
+ void DidCreateAndInitializeOutputSurface();
+ bool HasInitializedOutputSurface() const;
+
+ // Exposed for testing purposes.
+ void SetMaximumNumberOfFailedDrawsBeforeDrawIsForced(int num_draws);
+
+ // False if drawing is not being prevented, true if drawing won't happen
+ // for some reason, such as not being visible.
+ bool DrawSuspendedUntilCommit() const;
+
+ std::string ToString();
+
+ protected:
+ bool ShouldDrawForced() const;
+ bool ScheduledToDraw() const;
+ bool ShouldDraw() const;
+ bool ShouldAttemptTreeActivation() const;
+ bool ShouldAcquireLayerTexturesForMainThread() const;
+ bool ShouldUpdateVisibleTiles() const;
+ bool HasDrawnThisFrame() const;
+ bool HasAttemptedTreeActivationThisFrame() const;
+ bool HasUpdatedVisibleTilesThisFrame() const;
+ void SetPostCommitFlags();
+
+ const SchedulerSettings settings_;
+
+ CommitState commit_state_;
+ int commit_count_;
+
+ int current_frame_number_;
+ int last_frame_number_where_begin_frame_sent_to_main_thread_;
+ int last_frame_number_where_draw_was_called_;
+ int last_frame_number_where_tree_activation_attempted_;
+ int last_frame_number_where_update_visible_tiles_was_called_;
+ int consecutive_failed_draws_;
+ int maximum_number_of_failed_draws_before_draw_is_forced_;
+ bool needs_redraw_;
+ bool swap_used_incomplete_tile_;
+ bool needs_forced_redraw_;
+ bool needs_forced_redraw_after_next_commit_;
+ bool needs_redraw_after_next_commit_;
+ bool needs_commit_;
+ bool needs_forced_commit_;
+ bool expect_immediate_begin_frame_for_main_thread_;
+ bool main_thread_needs_layer_textures_;
+ bool inside_begin_frame_;
+ BeginFrameArgs last_begin_frame_args_;
+ bool visible_;
+ bool can_start_;
+ bool can_draw_;
+ bool has_pending_tree_;
+ bool draw_if_possible_failed_;
+ TextureState texture_state_;
+ OutputSurfaceState output_surface_state_;
+ bool did_create_and_initialize_first_output_surface_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SchedulerStateMachine);
+};
+
+} // namespace cc
+
+#endif // CC_SCHEDULER_SCHEDULER_STATE_MACHINE_H_
diff --git a/chromium/cc/scheduler/scheduler_state_machine_unittest.cc b/chromium/cc/scheduler/scheduler_state_machine_unittest.cc
new file mode 100644
index 00000000000..c7210eb2a05
--- /dev/null
+++ b/chromium/cc/scheduler/scheduler_state_machine_unittest.cc
@@ -0,0 +1,1391 @@
+// Copyright 2011 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 "cc/scheduler/scheduler_state_machine.h"
+
+#include "cc/scheduler/scheduler.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+
+namespace {
+
+const SchedulerStateMachine::CommitState all_commit_states[] = {
+ SchedulerStateMachine::COMMIT_STATE_IDLE,
+ SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS,
+ SchedulerStateMachine::COMMIT_STATE_READY_TO_COMMIT,
+ SchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_DRAW
+};
+
+// Exposes the protected state fields of the SchedulerStateMachine for testing
+class StateMachine : public SchedulerStateMachine {
+ public:
+ explicit StateMachine(const SchedulerSettings& scheduler_settings)
+ : SchedulerStateMachine(scheduler_settings) {}
+ void SetCommitState(CommitState cs) { commit_state_ = cs; }
+ CommitState CommitState() const { return commit_state_; }
+
+ bool NeedsCommit() const { return needs_commit_; }
+
+ void SetNeedsRedraw(bool b) { needs_redraw_ = b; }
+ bool NeedsRedraw() const { return needs_redraw_; }
+
+ void SetNeedsForcedRedraw(bool b) { needs_forced_redraw_ = b; }
+ bool NeedsForcedRedraw() const { return needs_forced_redraw_; }
+
+ bool CanDraw() const { return can_draw_; }
+ bool Visible() const { return visible_; }
+};
+
+TEST(SchedulerStateMachineTest, TestNextActionBeginsMainFrameIfNeeded) {
+ SchedulerSettings default_scheduler_settings;
+
+ // If no commit needed, do nothing.
+ {
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetCommitState(SchedulerStateMachine::COMMIT_STATE_IDLE);
+ state.SetNeedsRedraw(false);
+ state.SetVisible(true);
+
+ EXPECT_FALSE(state.BeginFrameNeededToDrawByImplThread());
+
+ state.DidLeaveBeginFrame();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ EXPECT_FALSE(state.BeginFrameNeededToDrawByImplThread());
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ }
+
+ // If commit requested but can_start is still false, do nothing.
+ {
+ StateMachine state(default_scheduler_settings);
+ state.SetCommitState(SchedulerStateMachine::COMMIT_STATE_IDLE);
+ state.SetNeedsRedraw(false);
+ state.SetVisible(true);
+
+ EXPECT_FALSE(state.BeginFrameNeededToDrawByImplThread());
+
+ state.DidLeaveBeginFrame();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ EXPECT_FALSE(state.BeginFrameNeededToDrawByImplThread());
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ }
+
+ // If commit requested, begin a main frame.
+ {
+ StateMachine state(default_scheduler_settings);
+ state.SetCommitState(SchedulerStateMachine::COMMIT_STATE_IDLE);
+ state.SetCanStart();
+ state.SetNeedsRedraw(false);
+ state.SetVisible(true);
+ EXPECT_FALSE(state.BeginFrameNeededToDrawByImplThread());
+ }
+
+ // Begin the frame, make sure needs_commit and commit_state update correctly.
+ {
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetVisible(true);
+ state.UpdateState(
+ SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD);
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS,
+ state.CommitState());
+ EXPECT_FALSE(state.NeedsCommit());
+ EXPECT_FALSE(state.BeginFrameNeededToDrawByImplThread());
+ }
+}
+
+TEST(SchedulerStateMachineTest, TestSetForcedRedrawDoesNotSetsNormalRedraw) {
+ SchedulerSettings default_scheduler_settings;
+ SchedulerStateMachine state(default_scheduler_settings);
+ state.SetCanDraw(true);
+ state.SetNeedsForcedRedraw();
+ EXPECT_FALSE(state.RedrawPending());
+ EXPECT_TRUE(state.BeginFrameNeededToDrawByImplThread());
+}
+
+TEST(SchedulerStateMachineTest,
+ TestFailedDrawSetsNeedsCommitAndDoesNotDrawAgain) {
+ SchedulerSettings default_scheduler_settings;
+ SchedulerStateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+ state.SetNeedsRedraw();
+ EXPECT_TRUE(state.RedrawPending());
+ EXPECT_TRUE(state.BeginFrameNeededToDrawByImplThread());
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+
+ // We're drawing now.
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ state.UpdateState(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ EXPECT_FALSE(state.RedrawPending());
+ EXPECT_FALSE(state.CommitPending());
+
+ // Failing the draw makes us require a commit.
+ state.DidDrawIfPossibleCompleted(false);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ state.NextAction());
+ state.UpdateState(
+ SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD);
+ EXPECT_TRUE(state.RedrawPending());
+ EXPECT_TRUE(state.CommitPending());
+}
+
+TEST(SchedulerStateMachineTest,
+ TestsetNeedsRedrawDuringFailedDrawDoesNotRemoveNeedsRedraw) {
+ SchedulerSettings default_scheduler_settings;
+ SchedulerStateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+ state.SetNeedsRedraw();
+ EXPECT_TRUE(state.RedrawPending());
+ EXPECT_TRUE(state.BeginFrameNeededToDrawByImplThread());
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+
+ // We're drawing now.
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ state.UpdateState(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ EXPECT_FALSE(state.RedrawPending());
+ EXPECT_FALSE(state.CommitPending());
+
+ // While still in the same begin frame callback on the main thread,
+ // set needs redraw again. This should not redraw.
+ state.SetNeedsRedraw();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // Failing the draw makes us require a commit.
+ state.DidDrawIfPossibleCompleted(false);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ state.NextAction());
+ EXPECT_TRUE(state.RedrawPending());
+}
+
+TEST(SchedulerStateMachineTest,
+ TestCommitAfterFailedDrawAllowsDrawInSameFrame) {
+ SchedulerSettings default_scheduler_settings;
+ SchedulerStateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ // Start a commit.
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ state.NextAction());
+ state.UpdateState(
+ SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD);
+ EXPECT_TRUE(state.CommitPending());
+
+ // Then initiate a draw.
+ state.SetNeedsRedraw();
+ EXPECT_TRUE(state.BeginFrameNeededToDrawByImplThread());
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ EXPECT_TRUE(state.RedrawPending());
+
+ // Fail the draw.
+ state.UpdateState(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ state.DidDrawIfPossibleCompleted(false);
+ EXPECT_TRUE(state.RedrawPending());
+ // But the commit is ongoing.
+ EXPECT_TRUE(state.CommitPending());
+
+ // Finish the commit.
+ state.FinishCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+ state.UpdateState(SchedulerStateMachine::ACTION_COMMIT);
+ EXPECT_TRUE(state.RedrawPending());
+
+ // And we should be allowed to draw again.
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest,
+ TestCommitAfterFailedAndSuccessfulDrawDoesNotAllowDrawInSameFrame) {
+ SchedulerSettings default_scheduler_settings;
+ SchedulerStateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ // Start a commit.
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ state.NextAction());
+ state.UpdateState(
+ SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD);
+ EXPECT_TRUE(state.CommitPending());
+
+ // Then initiate a draw.
+ state.SetNeedsRedraw();
+ EXPECT_TRUE(state.BeginFrameNeededToDrawByImplThread());
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ EXPECT_TRUE(state.RedrawPending());
+
+ // Fail the draw.
+ state.UpdateState(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ state.DidDrawIfPossibleCompleted(false);
+ EXPECT_TRUE(state.RedrawPending());
+ // But the commit is ongoing.
+ EXPECT_TRUE(state.CommitPending());
+
+ // Force a draw.
+ state.SetNeedsForcedRedraw();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_FORCED, state.NextAction());
+
+ // Do the forced draw.
+ state.UpdateState(SchedulerStateMachine::ACTION_DRAW_FORCED);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ EXPECT_FALSE(state.RedrawPending());
+ // And the commit is still ongoing.
+ EXPECT_TRUE(state.CommitPending());
+
+ // Finish the commit.
+ state.FinishCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+ state.UpdateState(SchedulerStateMachine::ACTION_COMMIT);
+ EXPECT_TRUE(state.RedrawPending());
+
+ // And we should not be allowed to draw again in the same frame..
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest,
+ TestFailedDrawsWillEventuallyForceADrawAfterTheNextCommit) {
+ SchedulerSettings default_scheduler_settings;
+ SchedulerStateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+ state.SetMaximumNumberOfFailedDrawsBeforeDrawIsForced(1);
+
+ // Start a commit.
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ state.NextAction());
+ state.UpdateState(
+ SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD);
+ EXPECT_TRUE(state.CommitPending());
+
+ // Then initiate a draw.
+ state.SetNeedsRedraw();
+ EXPECT_TRUE(state.BeginFrameNeededToDrawByImplThread());
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ EXPECT_TRUE(state.RedrawPending());
+
+ // Fail the draw.
+ state.UpdateState(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ state.DidDrawIfPossibleCompleted(false);
+ EXPECT_TRUE(state.RedrawPending());
+ // But the commit is ongoing.
+ EXPECT_TRUE(state.CommitPending());
+
+ // Finish the commit. Note, we should not yet be forcing a draw, but should
+ // continue the commit as usual.
+ state.FinishCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+ state.UpdateState(SchedulerStateMachine::ACTION_COMMIT);
+ EXPECT_TRUE(state.RedrawPending());
+
+ // The redraw should be forced in this case.
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_FORCED, state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest,
+ TestFailedDrawIsRetriedInNextBeginFrameForImplThread) {
+ SchedulerSettings default_scheduler_settings;
+ SchedulerStateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ // Start a draw.
+ state.SetNeedsRedraw();
+ EXPECT_TRUE(state.BeginFrameNeededToDrawByImplThread());
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ EXPECT_TRUE(state.RedrawPending());
+
+ // Fail the draw.
+ state.UpdateState(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ state.DidDrawIfPossibleCompleted(false);
+ EXPECT_TRUE(state.RedrawPending());
+
+ // We should not be trying to draw again now, but we have a commit pending.
+ EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ state.NextAction());
+
+ state.DidLeaveBeginFrame();
+ EXPECT_TRUE(state.BeginFrameNeededToDrawByImplThread());
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+
+ // We should try to draw again in the next begin frame on the impl thread.
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest, TestDoestDrawTwiceInSameFrame) {
+ SchedulerSettings default_scheduler_settings;
+ SchedulerStateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+ state.SetNeedsRedraw();
+ EXPECT_TRUE(state.BeginFrameNeededToDrawByImplThread());
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ state.UpdateState(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE);
+
+ // While still in the same begin frame for the impl thread, set needs redraw
+ // again. This should not redraw.
+ state.SetNeedsRedraw();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // Move to another frame. This should now draw.
+ state.DidDrawIfPossibleCompleted(true);
+ state.DidLeaveBeginFrame();
+ EXPECT_TRUE(state.BeginFrameNeededToDrawByImplThread());
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ state.UpdateState(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE);
+ state.DidDrawIfPossibleCompleted(true);
+ EXPECT_FALSE(state.BeginFrameNeededToDrawByImplThread());
+}
+
+TEST(SchedulerStateMachineTest, TestNextActionDrawsOnBeginFrame) {
+ SchedulerSettings default_scheduler_settings;
+
+ // When not in BeginFrame, or in BeginFrame but not visible,
+ // don't draw.
+ size_t num_commit_states =
+ sizeof(all_commit_states) / sizeof(SchedulerStateMachine::CommitState);
+ for (size_t i = 0; i < num_commit_states; ++i) {
+ for (size_t j = 0; j < 2; ++j) {
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetCommitState(all_commit_states[i]);
+ bool visible = j;
+ if (!visible) {
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+ state.SetVisible(false);
+ } else {
+ state.SetVisible(true);
+ }
+
+ // Case 1: needs_commit=false
+ EXPECT_NE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE,
+ state.NextAction());
+
+ // Case 2: needs_commit=true
+ state.SetNeedsCommit();
+ EXPECT_NE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE,
+ state.NextAction());
+ }
+ }
+
+ // When in BeginFrame, or not in BeginFrame but needs_forced_dedraw
+ // set, should always draw except if you're ready to commit, in which case
+ // commit.
+ for (size_t i = 0; i < num_commit_states; ++i) {
+ for (size_t j = 0; j < 2; ++j) {
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetCanDraw(true);
+ state.SetCommitState(all_commit_states[i]);
+ bool forced_draw = j;
+ if (!forced_draw) {
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+ state.SetNeedsRedraw(true);
+ state.SetVisible(true);
+ } else {
+ state.SetNeedsForcedRedraw(true);
+ }
+
+ SchedulerStateMachine::Action expected_action;
+ if (all_commit_states[i] !=
+ SchedulerStateMachine::COMMIT_STATE_READY_TO_COMMIT) {
+ expected_action =
+ forced_draw ? SchedulerStateMachine::ACTION_DRAW_FORCED
+ : SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE;
+ } else {
+ expected_action = SchedulerStateMachine::ACTION_COMMIT;
+ }
+
+ // Case 1: needs_commit=false.
+ EXPECT_TRUE(state.BeginFrameNeededToDrawByImplThread());
+ EXPECT_EQ(expected_action, state.NextAction());
+
+ // Case 2: needs_commit=true.
+ state.SetNeedsCommit();
+ EXPECT_TRUE(state.BeginFrameNeededToDrawByImplThread());
+ EXPECT_EQ(expected_action, state.NextAction());
+ }
+ }
+}
+
+TEST(SchedulerStateMachineTest, TestNoCommitStatesRedrawWhenInvisible) {
+ SchedulerSettings default_scheduler_settings;
+
+ size_t num_commit_states =
+ sizeof(all_commit_states) / sizeof(SchedulerStateMachine::CommitState);
+ for (size_t i = 0; i < num_commit_states; ++i) {
+ // There shouldn't be any drawing regardless of BeginFrame.
+ for (size_t j = 0; j < 2; ++j) {
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetCommitState(all_commit_states[i]);
+ state.SetVisible(false);
+ state.SetNeedsRedraw(true);
+ state.SetNeedsForcedRedraw(false);
+ if (j == 1)
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+
+ // Case 1: needs_commit=false.
+ EXPECT_NE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE,
+ state.NextAction());
+
+ // Case 2: needs_commit=true.
+ state.SetNeedsCommit();
+ EXPECT_NE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE,
+ state.NextAction());
+ }
+ }
+}
+
+TEST(SchedulerStateMachineTest, TestCanRedraw_StopsDraw) {
+ SchedulerSettings default_scheduler_settings;
+
+ size_t num_commit_states =
+ sizeof(all_commit_states) / sizeof(SchedulerStateMachine::CommitState);
+ for (size_t i = 0; i < num_commit_states; ++i) {
+ // There shouldn't be any drawing regardless of BeginFrame.
+ for (size_t j = 0; j < 2; ++j) {
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetCommitState(all_commit_states[i]);
+ state.SetVisible(false);
+ state.SetNeedsRedraw(true);
+ state.SetNeedsForcedRedraw(false);
+ if (j == 1)
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+
+ state.SetCanDraw(false);
+ EXPECT_NE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE,
+ state.NextAction());
+ }
+ }
+}
+
+TEST(SchedulerStateMachineTest,
+ TestCanRedrawWithWaitingForFirstDrawMakesProgress) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetCommitState(
+ SchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_DRAW);
+ state.SetNeedsCommit();
+ state.SetNeedsRedraw(true);
+ state.SetVisible(true);
+ state.SetCanDraw(false);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest, TestsetNeedsCommitIsNotLost) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetNeedsCommit();
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ // Begin the frame.
+ EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ state.NextAction());
+ state.UpdateState(state.NextAction());
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS,
+ state.CommitState());
+
+ // Now, while the frame is in progress, set another commit.
+ state.SetNeedsCommit();
+ EXPECT_TRUE(state.NeedsCommit());
+
+ // Let the frame finish.
+ state.FinishCommit();
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_READY_TO_COMMIT,
+ state.CommitState());
+
+ // Expect to commit regardless of BeginFrame state.
+ state.DidLeaveBeginFrame();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+
+ // Commit and make sure we draw on next BeginFrame
+ state.UpdateState(SchedulerStateMachine::ACTION_COMMIT);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_DRAW,
+ state.CommitState());
+ state.UpdateState(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE);
+ state.DidDrawIfPossibleCompleted(true);
+
+ // Verify that another commit will begin.
+ state.DidLeaveBeginFrame();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest, TestFullCycle) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ // Start clean and set commit.
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ state.NextAction());
+
+ // Begin the frame.
+ state.UpdateState(
+ SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD);
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS,
+ state.CommitState());
+ EXPECT_FALSE(state.NeedsCommit());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // Tell the scheduler the frame finished.
+ state.FinishCommit();
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_READY_TO_COMMIT,
+ state.CommitState());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+
+ // Commit.
+ state.UpdateState(SchedulerStateMachine::ACTION_COMMIT);
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_DRAW,
+ state.CommitState());
+ EXPECT_TRUE(state.NeedsRedraw());
+
+ // Expect to do nothing until BeginFrame.
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // At BeginFrame, draw.
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ state.UpdateState(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE);
+ state.DidDrawIfPossibleCompleted(true);
+ state.DidLeaveBeginFrame();
+
+ // Should be synchronized, no draw needed, no action needed.
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_IDLE, state.CommitState());
+ EXPECT_FALSE(state.NeedsRedraw());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest, TestFullCycleWithCommitRequestInbetween) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ // Start clean and set commit.
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ state.NextAction());
+
+ // Begin the frame.
+ state.UpdateState(
+ SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD);
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS,
+ state.CommitState());
+ EXPECT_FALSE(state.NeedsCommit());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // Request another commit while the commit is in flight.
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // Tell the scheduler the frame finished.
+ state.FinishCommit();
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_READY_TO_COMMIT,
+ state.CommitState());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+
+ // Commit.
+ state.UpdateState(SchedulerStateMachine::ACTION_COMMIT);
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_DRAW,
+ state.CommitState());
+ EXPECT_TRUE(state.NeedsRedraw());
+
+ // Expect to do nothing until BeginFrame.
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // At BeginFrame, draw.
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ state.UpdateState(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE);
+ state.DidDrawIfPossibleCompleted(true);
+ state.DidLeaveBeginFrame();
+
+ // Should be synchronized, no draw needed, no action needed.
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_IDLE, state.CommitState());
+ EXPECT_FALSE(state.NeedsRedraw());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest, TestRequestCommitInvisible) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest, TestGoesInvisibleBeforeFinishCommit) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ // Start clean and set commit.
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ state.NextAction());
+
+ // Begin the frame while visible.
+ state.UpdateState(
+ SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD);
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS,
+ state.CommitState());
+ EXPECT_FALSE(state.NeedsCommit());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // Become invisible and abort the main thread's begin frame.
+ state.SetVisible(false);
+ state.BeginFrameAbortedByMainThread(false);
+
+ // We should now be back in the idle state as if we didn't start a frame at
+ // all.
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_IDLE, state.CommitState());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // Become visible again.
+ state.SetVisible(true);
+
+ // Although we have aborted on this frame and haven't cancelled the commit
+ // (i.e. need another), don't send another begin frame yet.
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_IDLE, state.CommitState());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ EXPECT_TRUE(state.NeedsCommit());
+
+ // Start a new frame.
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ state.NextAction());
+
+ // Begin the frame.
+ state.UpdateState(state.NextAction());
+
+ // We should be starting the commit now.
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS,
+ state.CommitState());
+}
+
+TEST(SchedulerStateMachineTest, AbortBeginFrameAndCancelCommit) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ // Get into a begin frame / commit state.
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ state.NextAction());
+ state.UpdateState(
+ SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD);
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS,
+ state.CommitState());
+ EXPECT_FALSE(state.NeedsCommit());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // Abort the commit, cancelling future commits.
+ state.BeginFrameAbortedByMainThread(true);
+
+ // Verify that another commit doesn't start on the same frame.
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_IDLE, state.CommitState());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ EXPECT_FALSE(state.NeedsCommit());
+
+ // Start a new frame; draw because this is the first frame since output
+ // surface init'd.
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ state.DidLeaveBeginFrame();
+
+ // Verify another commit doesn't start on another frame either.
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_IDLE, state.CommitState());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ EXPECT_FALSE(state.NeedsCommit());
+
+ // Verify another commit can start if requested, though.
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_IDLE, state.CommitState());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest, TestFirstContextCreation) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_OUTPUT_SURFACE_CREATION,
+ state.NextAction());
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // Check that the first init does not SetNeedsCommit.
+ state.SetNeedsCommit();
+ EXPECT_NE(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest, TestContextLostWhenCompletelyIdle) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ EXPECT_NE(SchedulerStateMachine::ACTION_BEGIN_OUTPUT_SURFACE_CREATION,
+ state.NextAction());
+ state.DidLoseOutputSurface();
+
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_OUTPUT_SURFACE_CREATION,
+ state.NextAction());
+ state.UpdateState(state.NextAction());
+
+ // Once context recreation begins, nothing should happen.
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // Recreate the context.
+ state.DidCreateAndInitializeOutputSurface();
+
+ // When the context is recreated, we should begin a commit.
+ EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ state.NextAction());
+ state.UpdateState(state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest,
+ TestContextLostWhenIdleAndCommitRequestedWhileRecreating) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ EXPECT_NE(SchedulerStateMachine::ACTION_BEGIN_OUTPUT_SURFACE_CREATION,
+ state.NextAction());
+ state.DidLoseOutputSurface();
+
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_OUTPUT_SURFACE_CREATION,
+ state.NextAction());
+ state.UpdateState(state.NextAction());
+
+ // Once context recreation begins, nothing should happen.
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // While context is recreating, commits shouldn't begin.
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // Recreate the context
+ state.DidCreateAndInitializeOutputSurface();
+ EXPECT_FALSE(state.RedrawPending());
+
+ // When the context is recreated, we should begin a commit
+ EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ state.NextAction());
+ state.UpdateState(state.NextAction());
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS,
+ state.CommitState());
+ state.FinishCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+ state.UpdateState(state.NextAction());
+ // Finishing the first commit after initializing an output surface should
+ // automatically cause a redraw.
+ EXPECT_TRUE(state.RedrawPending());
+
+ // Once the context is recreated, whether we draw should be based on
+ // SetCanDraw.
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ state.SetCanDraw(false);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ state.SetCanDraw(true);
+ state.DidLeaveBeginFrame();
+}
+
+TEST(SchedulerStateMachineTest, TestContextLostWhileCommitInProgress) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ // Get a commit in flight.
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ state.NextAction());
+ state.UpdateState(state.NextAction());
+
+ // Set damage and expect a draw.
+ state.SetNeedsRedraw(true);
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ state.UpdateState(state.NextAction());
+ state.DidLeaveBeginFrame();
+
+ // Cause a lost context while the begin frame is in flight
+ // for the main thread.
+ state.DidLoseOutputSurface();
+
+ // Ask for another draw. Expect nothing happens.
+ state.SetNeedsRedraw(true);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // Finish the frame, and commit.
+ state.FinishCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+ state.UpdateState(state.NextAction());
+
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_DRAW,
+ state.CommitState());
+
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ state.UpdateState(state.NextAction());
+
+ // Expect to be told to begin context recreation, independent of
+ // BeginFrame state.
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_OUTPUT_SURFACE_CREATION,
+ state.NextAction());
+ state.DidLeaveBeginFrame();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_OUTPUT_SURFACE_CREATION,
+ state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest,
+ TestContextLostWhileCommitInProgressAndAnotherCommitRequested) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ // Get a commit in flight.
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ state.NextAction());
+ state.UpdateState(state.NextAction());
+
+ // Set damage and expect a draw.
+ state.SetNeedsRedraw(true);
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ state.UpdateState(state.NextAction());
+ state.DidLeaveBeginFrame();
+
+ // Cause a lost context while the begin frame is in flight
+ // for the main thread.
+ state.DidLoseOutputSurface();
+
+ // Ask for another draw and also set needs commit. Expect nothing happens.
+ state.SetNeedsRedraw(true);
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // Finish the frame, and commit.
+ state.FinishCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+ state.UpdateState(state.NextAction());
+
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_DRAW,
+ state.CommitState());
+
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ state.UpdateState(state.NextAction());
+
+ // Expect to be told to begin context recreation, independent of
+ // BeginFrame state
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_OUTPUT_SURFACE_CREATION,
+ state.NextAction());
+ state.DidLeaveBeginFrame();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_OUTPUT_SURFACE_CREATION,
+ state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest, TestFinishAllRenderingWhileContextLost) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ // Cause a lost context lost.
+ state.DidLoseOutputSurface();
+
+ // Ask a forced redraw and verify it ocurrs.
+ state.SetNeedsForcedRedraw(true);
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_FORCED, state.NextAction());
+ state.DidLeaveBeginFrame();
+
+ // Clear the forced redraw bit.
+ state.SetNeedsForcedRedraw(false);
+
+ // Expect to be told to begin context recreation, independent of
+ // BeginFrame state
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_OUTPUT_SURFACE_CREATION,
+ state.NextAction());
+ state.UpdateState(state.NextAction());
+
+ // Ask a forced redraw and verify it ocurrs.
+ state.SetNeedsForcedRedraw(true);
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_FORCED, state.NextAction());
+ state.DidLeaveBeginFrame();
+}
+
+TEST(SchedulerStateMachineTest, DontDrawBeforeCommitAfterLostOutputSurface) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ state.SetNeedsRedraw(true);
+
+ // Cause a lost output surface, and restore it.
+ state.DidLoseOutputSurface();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_OUTPUT_SURFACE_CREATION,
+ state.NextAction());
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+
+ EXPECT_FALSE(state.RedrawPending());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest,
+ TestSendBeginFrameToMainThreadWhenInvisibleAndForceCommit) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetVisible(false);
+ state.SetNeedsCommit();
+ state.SetNeedsForcedCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest,
+ TestSendBeginFrameToMainThreadWhenCanStartFalseAndForceCommit) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+ state.SetNeedsCommit();
+ state.SetNeedsForcedCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest, TestFinishCommitWhenCommitInProgress) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetVisible(false);
+ state.SetCommitState(SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS);
+ state.SetNeedsCommit();
+
+ state.FinishCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+ state.UpdateState(state.NextAction());
+
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_DRAW,
+ state.CommitState());
+
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest, TestFinishCommitWhenForcedCommitInProgress) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetVisible(false);
+ state.SetCommitState(SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS);
+ state.SetNeedsCommit();
+ state.SetNeedsForcedCommit();
+
+ state.FinishCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+ state.UpdateState(state.NextAction());
+
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_FORCED_DRAW,
+ state.CommitState());
+
+ // If we are waiting for forced draw then we know a begin frame is already
+ // in flight for the main thread.
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest, TestSendBeginFrameToMainThreadWhenContextLost) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+ state.SetNeedsCommit();
+ state.SetNeedsForcedCommit();
+ state.DidLoseOutputSurface();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest, TestImmediateFinishCommit) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ // Schedule a forced frame, commit it, draw it.
+ state.SetNeedsCommit();
+ state.SetNeedsForcedCommit();
+ state.UpdateState(state.NextAction());
+ state.FinishCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_READY_TO_COMMIT,
+ state.CommitState());
+ state.UpdateState(state.NextAction());
+
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_FORCED_DRAW,
+ state.CommitState());
+
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ state.SetNeedsForcedRedraw(true);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_FORCED, state.NextAction());
+ state.UpdateState(state.NextAction());
+ state.DidDrawIfPossibleCompleted(true);
+ state.DidLeaveBeginFrame();
+
+ // Should be waiting for the normal begin frame from the main thread.
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS,
+ state.CommitState());
+}
+
+TEST(SchedulerStateMachineTest, TestImmediateFinishCommitDuringCommit) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ // Start a normal commit.
+ state.SetNeedsCommit();
+ state.UpdateState(state.NextAction());
+
+ // Schedule a forced frame, commit it, draw it.
+ state.SetNeedsCommit();
+ state.SetNeedsForcedCommit();
+ state.UpdateState(state.NextAction());
+ state.FinishCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_READY_TO_COMMIT,
+ state.CommitState());
+ state.UpdateState(state.NextAction());
+
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_FORCED_DRAW,
+ state.CommitState());
+
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ state.SetNeedsForcedRedraw(true);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_FORCED, state.NextAction());
+ state.UpdateState(state.NextAction());
+ state.DidDrawIfPossibleCompleted(true);
+ state.DidLeaveBeginFrame();
+
+ // Should be waiting for the normal begin frame from the main thread.
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS,
+ state.CommitState()) << state.ToString();
+}
+
+TEST(SchedulerStateMachineTest,
+ ImmediateBeginFrameAbortedByMainThreadWhileInvisible) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ state.SetNeedsCommit();
+ state.UpdateState(state.NextAction());
+
+ state.SetNeedsCommit();
+ state.SetNeedsForcedCommit();
+ state.UpdateState(state.NextAction());
+ state.FinishCommit();
+
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_READY_TO_COMMIT,
+ state.CommitState());
+ state.UpdateState(state.NextAction());
+
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_FORCED_DRAW,
+ state.CommitState());
+
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ state.SetNeedsForcedRedraw(true);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_FORCED, state.NextAction());
+ state.UpdateState(state.NextAction());
+ state.DidDrawIfPossibleCompleted(true);
+ state.DidLeaveBeginFrame();
+
+ // Should be waiting for the main thread's begin frame.
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS,
+ state.CommitState()) << state.ToString();
+
+ // Become invisible and abort the main thread's begin frame.
+ state.SetVisible(false);
+ state.BeginFrameAbortedByMainThread(false);
+
+ // Should be back in the idle state, but needing a commit.
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_IDLE, state.CommitState());
+ EXPECT_TRUE(state.NeedsCommit());
+}
+
+TEST(SchedulerStateMachineTest, ImmediateFinishCommitWhileCantDraw) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetVisible(true);
+ state.SetCanDraw(false);
+
+ state.SetNeedsCommit();
+ state.UpdateState(state.NextAction());
+
+ state.SetNeedsCommit();
+ state.SetNeedsForcedCommit();
+ state.UpdateState(state.NextAction());
+ state.FinishCommit();
+
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_READY_TO_COMMIT,
+ state.CommitState());
+ state.UpdateState(state.NextAction());
+
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_FORCED_DRAW,
+ state.CommitState());
+
+ state.DidEnterBeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ state.SetNeedsForcedRedraw(true);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_FORCED, state.NextAction());
+ state.UpdateState(state.NextAction());
+ state.DidDrawIfPossibleCompleted(true);
+ state.DidLeaveBeginFrame();
+}
+
+TEST(SchedulerStateMachineTest, ReportIfNotDrawing) {
+ SchedulerSettings default_scheduler_settings;
+ SchedulerStateMachine state(default_scheduler_settings);
+
+ state.SetCanDraw(true);
+ state.SetVisible(true);
+ EXPECT_FALSE(state.DrawSuspendedUntilCommit());
+
+ state.SetCanDraw(false);
+ state.SetVisible(true);
+ EXPECT_TRUE(state.DrawSuspendedUntilCommit());
+
+ state.SetCanDraw(true);
+ state.SetVisible(false);
+ EXPECT_TRUE(state.DrawSuspendedUntilCommit());
+
+ state.SetCanDraw(false);
+ state.SetVisible(false);
+ EXPECT_TRUE(state.DrawSuspendedUntilCommit());
+
+ state.SetCanDraw(true);
+ state.SetVisible(true);
+ EXPECT_FALSE(state.DrawSuspendedUntilCommit());
+}
+
+TEST(SchedulerStateMachineTest, ReportIfNotDrawingFromAcquiredTextures) {
+ SchedulerSettings default_scheduler_settings;
+ SchedulerStateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetCanDraw(true);
+ state.SetVisible(true);
+ EXPECT_FALSE(state.DrawSuspendedUntilCommit());
+
+ state.SetMainThreadNeedsLayerTextures();
+ EXPECT_EQ(
+ SchedulerStateMachine::ACTION_ACQUIRE_LAYER_TEXTURES_FOR_MAIN_THREAD,
+ state.NextAction());
+ state.UpdateState(state.NextAction());
+ EXPECT_TRUE(state.DrawSuspendedUntilCommit());
+
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ state.NextAction());
+
+ state.UpdateState(state.NextAction());
+ EXPECT_TRUE(state.DrawSuspendedUntilCommit());
+
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ state.FinishCommit();
+ EXPECT_TRUE(state.DrawSuspendedUntilCommit());
+
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+
+ state.UpdateState(state.NextAction());
+ EXPECT_FALSE(state.DrawSuspendedUntilCommit());
+}
+
+TEST(SchedulerStateMachineTest, AcquireTexturesWithAbort) {
+ SchedulerSettings default_scheduler_settings;
+ SchedulerStateMachine state(default_scheduler_settings);
+ state.SetCanStart();
+ state.UpdateState(state.NextAction());
+ state.DidCreateAndInitializeOutputSurface();
+ state.SetCanDraw(true);
+ state.SetVisible(true);
+
+ state.SetMainThreadNeedsLayerTextures();
+ EXPECT_EQ(
+ SchedulerStateMachine::ACTION_ACQUIRE_LAYER_TEXTURES_FOR_MAIN_THREAD,
+ state.NextAction());
+ state.UpdateState(state.NextAction());
+ EXPECT_TRUE(state.DrawSuspendedUntilCommit());
+
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_SEND_BEGIN_FRAME_TO_MAIN_THREAD,
+ state.NextAction());
+ state.UpdateState(state.NextAction());
+ EXPECT_TRUE(state.DrawSuspendedUntilCommit());
+
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ state.BeginFrameAbortedByMainThread(true);
+
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ EXPECT_FALSE(state.DrawSuspendedUntilCommit());
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/scheduler/scheduler_unittest.cc b/chromium/cc/scheduler/scheduler_unittest.cc
new file mode 100644
index 00000000000..930111c1b2a
--- /dev/null
+++ b/chromium/cc/scheduler/scheduler_unittest.cc
@@ -0,0 +1,627 @@
+// Copyright 2011 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 "cc/scheduler/scheduler.h"
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "cc/test/scheduler_test_common.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#define EXPECT_ACTION(action, client, action_index, expected_num_actions) \
+ EXPECT_EQ(expected_num_actions, client.num_actions_()); \
+ ASSERT_LT(action_index, client.num_actions_()); \
+ do { \
+ EXPECT_STREQ(action, client.Action(action_index)); \
+ for (int i = expected_num_actions; i < client.num_actions_(); ++i) \
+ ADD_FAILURE() << "Unexpected action: " << client.Action(i) << \
+ " with state:\n" << client.StateForAction(action_index); \
+ } while (false)
+
+#define EXPECT_SINGLE_ACTION(action, client) \
+ EXPECT_ACTION(action, client, 0, 1)
+
+namespace cc {
+namespace {
+
+class FakeSchedulerClient : public SchedulerClient {
+ public:
+ FakeSchedulerClient()
+ : needs_begin_frame_(false) {
+ Reset();
+ }
+
+ void Reset() {
+ actions_.clear();
+ states_.clear();
+ draw_will_happen_ = true;
+ swap_will_happen_if_draw_happens_ = true;
+ num_draws_ = 0;
+ }
+
+ Scheduler* CreateScheduler(const SchedulerSettings& settings) {
+ scheduler_ = Scheduler::Create(this, settings);
+ return scheduler_.get();
+ }
+
+ bool needs_begin_frame() { return needs_begin_frame_; }
+ int num_draws() const { return num_draws_; }
+ int num_actions_() const { return static_cast<int>(actions_.size()); }
+ const char* Action(int i) const { return actions_[i]; }
+ std::string StateForAction(int i) const { return states_[i]; }
+
+ bool HasAction(const char* action) const {
+ for (size_t i = 0; i < actions_.size(); i++)
+ if (!strcmp(actions_[i], action))
+ return true;
+ return false;
+ }
+
+ void SetDrawWillHappen(bool draw_will_happen) {
+ draw_will_happen_ = draw_will_happen;
+ }
+ void SetSwapWillHappenIfDrawHappens(bool swap_will_happen_if_draw_happens) {
+ swap_will_happen_if_draw_happens_ = swap_will_happen_if_draw_happens;
+ }
+
+ // Scheduler Implementation.
+ virtual void SetNeedsBeginFrameOnImplThread(bool enable) OVERRIDE {
+ actions_.push_back("SetNeedsBeginFrameOnImplThread");
+ states_.push_back(scheduler_->StateAsStringForTesting());
+ needs_begin_frame_ = enable;
+ }
+ virtual void ScheduledActionSendBeginFrameToMainThread() OVERRIDE {
+ actions_.push_back("ScheduledActionSendBeginFrameToMainThread");
+ states_.push_back(scheduler_->StateAsStringForTesting());
+ }
+ virtual ScheduledActionDrawAndSwapResult
+ ScheduledActionDrawAndSwapIfPossible() OVERRIDE {
+ actions_.push_back("ScheduledActionDrawAndSwapIfPossible");
+ states_.push_back(scheduler_->StateAsStringForTesting());
+ num_draws_++;
+ return ScheduledActionDrawAndSwapResult(draw_will_happen_,
+ draw_will_happen_ &&
+ swap_will_happen_if_draw_happens_);
+ }
+ virtual ScheduledActionDrawAndSwapResult ScheduledActionDrawAndSwapForced()
+ OVERRIDE {
+ actions_.push_back("ScheduledActionDrawAndSwapForced");
+ states_.push_back(scheduler_->StateAsStringForTesting());
+ return ScheduledActionDrawAndSwapResult(true,
+ swap_will_happen_if_draw_happens_);
+ }
+ virtual void ScheduledActionCommit() OVERRIDE {
+ actions_.push_back("ScheduledActionCommit");
+ states_.push_back(scheduler_->StateAsStringForTesting());
+ }
+ virtual void ScheduledActionUpdateVisibleTiles() OVERRIDE {
+ actions_.push_back("ScheduledActionUpdateVisibleTiles");
+ states_.push_back(scheduler_->StateAsStringForTesting());
+ }
+ virtual void ScheduledActionActivatePendingTreeIfNeeded() OVERRIDE {
+ actions_.push_back("ScheduledActionActivatePendingTreeIfNeeded");
+ states_.push_back(scheduler_->StateAsStringForTesting());
+ }
+ virtual void ScheduledActionBeginOutputSurfaceCreation() OVERRIDE {
+ actions_.push_back("ScheduledActionBeginOutputSurfaceCreation");
+ states_.push_back(scheduler_->StateAsStringForTesting());
+ }
+ virtual void ScheduledActionAcquireLayerTexturesForMainThread() OVERRIDE {
+ actions_.push_back("ScheduledActionAcquireLayerTexturesForMainThread");
+ states_.push_back(scheduler_->StateAsStringForTesting());
+ }
+ virtual void DidAnticipatedDrawTimeChange(base::TimeTicks) OVERRIDE {}
+ virtual base::TimeDelta DrawDurationEstimate() OVERRIDE {
+ return base::TimeDelta();
+ }
+ virtual base::TimeDelta BeginFrameToCommitDurationEstimate() OVERRIDE {
+ return base::TimeDelta();
+ }
+ virtual base::TimeDelta CommitToActivateDurationEstimate() OVERRIDE {
+ return base::TimeDelta();
+ }
+
+ protected:
+ bool needs_begin_frame_;
+ bool draw_will_happen_;
+ bool swap_will_happen_if_draw_happens_;
+ int num_draws_;
+ std::vector<const char*> actions_;
+ std::vector<std::string> states_;
+ scoped_ptr<Scheduler> scheduler_;
+};
+
+TEST(SchedulerTest, InitializeOutputSurfaceDoesNotBeginFrame) {
+ FakeSchedulerClient client;
+ SchedulerSettings default_scheduler_settings;
+ Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings);
+ scheduler->SetCanStart();
+ scheduler->SetVisible(true);
+ scheduler->SetCanDraw(true);
+
+ EXPECT_SINGLE_ACTION("ScheduledActionBeginOutputSurfaceCreation", client);
+ client.Reset();
+ scheduler->DidCreateAndInitializeOutputSurface();
+ EXPECT_EQ(0, client.num_actions_());
+}
+
+TEST(SchedulerTest, RequestCommit) {
+ FakeSchedulerClient client;
+ SchedulerSettings default_scheduler_settings;
+ Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings);
+ scheduler->SetCanStart();
+ scheduler->SetVisible(true);
+ scheduler->SetCanDraw(true);
+
+ EXPECT_SINGLE_ACTION("ScheduledActionBeginOutputSurfaceCreation", client);
+ client.Reset();
+ scheduler->DidCreateAndInitializeOutputSurface();
+
+ // SetNeedsCommit should begin the frame.
+ scheduler->SetNeedsCommit();
+ EXPECT_ACTION("ScheduledActionSendBeginFrameToMainThread", client, 0, 2);
+ EXPECT_ACTION("SetNeedsBeginFrameOnImplThread", client, 1, 2);
+ EXPECT_TRUE(client.needs_begin_frame());
+ client.Reset();
+
+ // FinishCommit should commit
+ scheduler->FinishCommit();
+ EXPECT_ACTION("ScheduledActionCommit", client, 0, 2);
+ EXPECT_ACTION("SetNeedsBeginFrameOnImplThread", client, 1, 2);
+ EXPECT_TRUE(client.needs_begin_frame());
+ client.Reset();
+
+ // BeginFrame should draw.
+ scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 0, 2);
+ EXPECT_ACTION("SetNeedsBeginFrameOnImplThread", client, 1, 2);
+ EXPECT_FALSE(client.needs_begin_frame());
+ client.Reset();
+}
+
+TEST(SchedulerTest, RequestCommitAfterBeginFrameSentToMainThread) {
+ FakeSchedulerClient client;
+ SchedulerSettings default_scheduler_settings;
+ Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings);
+ scheduler->SetCanStart();
+ scheduler->SetVisible(true);
+ scheduler->SetCanDraw(true);
+
+ EXPECT_SINGLE_ACTION("ScheduledActionBeginOutputSurfaceCreation", client);
+ client.Reset();
+ scheduler->DidCreateAndInitializeOutputSurface();
+
+ // SetNedsCommit should begin the frame.
+ scheduler->SetNeedsCommit();
+ EXPECT_ACTION("ScheduledActionSendBeginFrameToMainThread", client, 0, 2);
+ EXPECT_ACTION("SetNeedsBeginFrameOnImplThread", client, 1, 2);
+ client.Reset();
+
+ // Now SetNeedsCommit again. Calling here means we need a second frame.
+ scheduler->SetNeedsCommit();
+ EXPECT_ACTION("SetNeedsBeginFrameOnImplThread", client, 0, 1);
+ client.Reset();
+
+ // Since another commit is needed, FinishCommit should commit,
+ // then begin another frame.
+ scheduler->FinishCommit();
+ EXPECT_ACTION("ScheduledActionCommit", client, 0, 2);
+ EXPECT_ACTION("SetNeedsBeginFrameOnImplThread", client, 1, 2);
+ client.Reset();
+
+ // Tick should draw but then begin another frame.
+ scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_TRUE(client.needs_begin_frame());
+ EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 0, 2);
+ EXPECT_ACTION("ScheduledActionSendBeginFrameToMainThread", client, 1, 2);
+ client.Reset();
+
+ // Go back to quiescent state and verify we no longer request BeginFrames.
+ scheduler->FinishCommit();
+ scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_FALSE(client.needs_begin_frame());
+}
+
+TEST(SchedulerTest, TextureAcquisitionCausesCommitInsteadOfDraw) {
+ FakeSchedulerClient client;
+ SchedulerSettings default_scheduler_settings;
+ Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings);
+ scheduler->SetCanStart();
+ scheduler->SetVisible(true);
+ scheduler->SetCanDraw(true);
+ EXPECT_SINGLE_ACTION("ScheduledActionBeginOutputSurfaceCreation", client);
+
+ client.Reset();
+ scheduler->DidCreateAndInitializeOutputSurface();
+ scheduler->SetNeedsRedraw();
+ EXPECT_TRUE(scheduler->RedrawPending());
+ EXPECT_SINGLE_ACTION("SetNeedsBeginFrameOnImplThread", client);
+ EXPECT_TRUE(client.needs_begin_frame());
+
+ client.Reset();
+ scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 0, 2);
+ EXPECT_ACTION("SetNeedsBeginFrameOnImplThread", client, 1, 2);
+ EXPECT_FALSE(scheduler->RedrawPending());
+ EXPECT_FALSE(client.needs_begin_frame());
+
+ client.Reset();
+ scheduler->SetMainThreadNeedsLayerTextures();
+ EXPECT_SINGLE_ACTION("ScheduledActionAcquireLayerTexturesForMainThread",
+ client);
+
+ // We should request a BeginFrame in anticipation of a draw.
+ client.Reset();
+ scheduler->SetNeedsRedraw();
+ EXPECT_TRUE(scheduler->RedrawPending());
+ EXPECT_SINGLE_ACTION("SetNeedsBeginFrameOnImplThread", client);
+ EXPECT_TRUE(client.needs_begin_frame());
+
+ // No draw happens since the textures are acquired by the main thread.
+ client.Reset();
+ scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_SINGLE_ACTION("SetNeedsBeginFrameOnImplThread", client);
+ EXPECT_TRUE(scheduler->RedrawPending());
+ EXPECT_TRUE(client.needs_begin_frame());
+
+ scheduler->SetNeedsCommit();
+ EXPECT_ACTION("SetNeedsBeginFrameOnImplThread", client, 0, 2);
+ EXPECT_ACTION("ScheduledActionSendBeginFrameToMainThread", client, 1, 2);
+ EXPECT_TRUE(client.needs_begin_frame());
+
+ // Commit will release the texture.
+ client.Reset();
+ scheduler->FinishCommit();
+ EXPECT_SINGLE_ACTION("ScheduledActionCommit", client);
+ EXPECT_TRUE(scheduler->RedrawPending());
+ EXPECT_TRUE(client.needs_begin_frame());
+
+ // Now we can draw again after the commit happens.
+ client.Reset();
+ scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 0, 2);
+ EXPECT_ACTION("SetNeedsBeginFrameOnImplThread", client, 1, 2);
+ EXPECT_FALSE(scheduler->RedrawPending());
+ EXPECT_FALSE(client.needs_begin_frame());
+ client.Reset();
+}
+
+TEST(SchedulerTest, TextureAcquisitionCollision) {
+ FakeSchedulerClient client;
+ SchedulerSettings default_scheduler_settings;
+ Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings);
+ scheduler->SetCanStart();
+ scheduler->SetVisible(true);
+ scheduler->SetCanDraw(true);
+
+ EXPECT_SINGLE_ACTION("ScheduledActionBeginOutputSurfaceCreation", client);
+ client.Reset();
+ scheduler->DidCreateAndInitializeOutputSurface();
+
+ scheduler->SetNeedsCommit();
+ scheduler->SetMainThreadNeedsLayerTextures();
+ EXPECT_ACTION("ScheduledActionSendBeginFrameToMainThread", client, 0, 4);
+ EXPECT_ACTION("SetNeedsBeginFrameOnImplThread", client, 1, 4);
+ EXPECT_ACTION("ScheduledActionAcquireLayerTexturesForMainThread",
+ client,
+ 2,
+ 4);
+ EXPECT_ACTION("SetNeedsBeginFrameOnImplThread", client, 3, 4);
+ client.Reset();
+
+ // Although the compositor cannot draw because textures are locked by main
+ // thread, we continue requesting SetNeedsBeginFrame in anticipation of the
+ // unlock.
+ EXPECT_TRUE(client.needs_begin_frame());
+
+ // Trigger the commit
+ scheduler->FinishCommit();
+ EXPECT_TRUE(client.needs_begin_frame());
+ client.Reset();
+
+ // Between commit and draw, texture acquisition for main thread delayed,
+ // and main thread blocks.
+ scheduler->SetMainThreadNeedsLayerTextures();
+ EXPECT_EQ(0, client.num_actions_());
+ client.Reset();
+
+ // No implicit commit is expected.
+ scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 0, 3);
+ EXPECT_ACTION("ScheduledActionAcquireLayerTexturesForMainThread",
+ client,
+ 1,
+ 3);
+ EXPECT_ACTION("SetNeedsBeginFrameOnImplThread", client, 2, 3);
+ client.Reset();
+
+ // Compositor not scheduled to draw because textures are locked by main
+ // thread.
+ EXPECT_FALSE(client.needs_begin_frame());
+
+ // Needs an explicit commit from the main thread.
+ scheduler->SetNeedsCommit();
+ EXPECT_ACTION("ScheduledActionSendBeginFrameToMainThread", client, 0, 2);
+ EXPECT_ACTION("SetNeedsBeginFrameOnImplThread", client, 1, 2);
+ client.Reset();
+
+ // Trigger the commit
+ scheduler->FinishCommit();
+ EXPECT_TRUE(client.needs_begin_frame());
+}
+
+TEST(SchedulerTest, VisibilitySwitchWithTextureAcquisition) {
+ FakeSchedulerClient client;
+ SchedulerSettings default_scheduler_settings;
+ Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings);
+ scheduler->SetCanStart();
+ scheduler->SetVisible(true);
+ scheduler->SetCanDraw(true);
+
+ EXPECT_SINGLE_ACTION("ScheduledActionBeginOutputSurfaceCreation", client);
+ client.Reset();
+ scheduler->DidCreateAndInitializeOutputSurface();
+
+ scheduler->SetNeedsCommit();
+ scheduler->FinishCommit();
+ scheduler->SetMainThreadNeedsLayerTextures();
+ scheduler->SetNeedsCommit();
+ client.Reset();
+ // Verify that pending texture acquisition fires when visibility
+ // is lost in order to avoid a deadlock.
+ scheduler->SetVisible(false);
+ EXPECT_SINGLE_ACTION("ScheduledActionAcquireLayerTexturesForMainThread",
+ client);
+ client.Reset();
+
+ // Already sent a begin frame on this current frame, so wait.
+ scheduler->SetVisible(true);
+ EXPECT_EQ(0, client.num_actions_());
+ client.Reset();
+
+ // Regaining visibility with textures acquired by main thread while
+ // compositor is waiting for first draw should result in a request
+ // for a new frame in order to escape a deadlock.
+ scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_ACTION("ScheduledActionSendBeginFrameToMainThread", client, 0, 2);
+ EXPECT_ACTION("SetNeedsBeginFrameOnImplThread", client, 1, 2);
+}
+
+class SchedulerClientThatsetNeedsDrawInsideDraw : public FakeSchedulerClient {
+ public:
+ virtual void ScheduledActionSendBeginFrameToMainThread() OVERRIDE {}
+ virtual ScheduledActionDrawAndSwapResult
+ ScheduledActionDrawAndSwapIfPossible() OVERRIDE {
+ // Only SetNeedsRedraw the first time this is called
+ if (!num_draws_)
+ scheduler_->SetNeedsRedraw();
+ return FakeSchedulerClient::ScheduledActionDrawAndSwapIfPossible();
+ }
+
+ virtual ScheduledActionDrawAndSwapResult ScheduledActionDrawAndSwapForced()
+ OVERRIDE {
+ NOTREACHED();
+ return ScheduledActionDrawAndSwapResult(true, true);
+ }
+
+ virtual void ScheduledActionCommit() OVERRIDE {}
+ virtual void ScheduledActionBeginOutputSurfaceCreation() OVERRIDE {}
+ virtual void DidAnticipatedDrawTimeChange(base::TimeTicks) OVERRIDE {}
+};
+
+// Tests for two different situations:
+// 1. the scheduler dropping SetNeedsRedraw requests that happen inside
+// a ScheduledActionDrawAndSwap
+// 2. the scheduler drawing twice inside a single tick
+TEST(SchedulerTest, RequestRedrawInsideDraw) {
+ SchedulerClientThatsetNeedsDrawInsideDraw client;
+ SchedulerSettings default_scheduler_settings;
+ Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings);
+ scheduler->SetCanStart();
+ scheduler->SetVisible(true);
+ scheduler->SetCanDraw(true);
+ scheduler->DidCreateAndInitializeOutputSurface();
+
+ scheduler->SetNeedsRedraw();
+ EXPECT_TRUE(scheduler->RedrawPending());
+ EXPECT_TRUE(client.needs_begin_frame());
+ EXPECT_EQ(0, client.num_draws());
+
+ scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(1, client.num_draws());
+ EXPECT_TRUE(scheduler->RedrawPending());
+ EXPECT_TRUE(client.needs_begin_frame());
+
+ scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(2, client.num_draws());
+ EXPECT_FALSE(scheduler->RedrawPending());
+ EXPECT_FALSE(client.needs_begin_frame());
+}
+
+// Test that requesting redraw inside a failed draw doesn't lose the request.
+TEST(SchedulerTest, RequestRedrawInsideFailedDraw) {
+ SchedulerClientThatsetNeedsDrawInsideDraw client;
+ SchedulerSettings default_scheduler_settings;
+ Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings);
+ scheduler->SetCanStart();
+ scheduler->SetVisible(true);
+ scheduler->SetCanDraw(true);
+ scheduler->DidCreateAndInitializeOutputSurface();
+
+ client.SetDrawWillHappen(false);
+
+ scheduler->SetNeedsRedraw();
+ EXPECT_TRUE(scheduler->RedrawPending());
+ EXPECT_TRUE(client.needs_begin_frame());
+ EXPECT_EQ(0, client.num_draws());
+
+ // Fail the draw.
+ scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(1, client.num_draws());
+
+ // We have a commit pending and the draw failed, and we didn't lose the redraw
+ // request.
+ EXPECT_TRUE(scheduler->CommitPending());
+ EXPECT_TRUE(scheduler->RedrawPending());
+ EXPECT_TRUE(client.needs_begin_frame());
+
+ // Fail the draw again.
+ scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(2, client.num_draws());
+ EXPECT_TRUE(scheduler->CommitPending());
+ EXPECT_TRUE(scheduler->RedrawPending());
+ EXPECT_TRUE(client.needs_begin_frame());
+
+ // Draw successfully.
+ client.SetDrawWillHappen(true);
+ scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(3, client.num_draws());
+ EXPECT_TRUE(scheduler->CommitPending());
+ EXPECT_FALSE(scheduler->RedrawPending());
+ EXPECT_TRUE(client.needs_begin_frame());
+}
+
+class SchedulerClientThatsetNeedsCommitInsideDraw : public FakeSchedulerClient {
+ public:
+ virtual void ScheduledActionSendBeginFrameToMainThread() OVERRIDE {}
+ virtual ScheduledActionDrawAndSwapResult
+ ScheduledActionDrawAndSwapIfPossible() OVERRIDE {
+ // Only SetNeedsCommit the first time this is called
+ if (!num_draws_)
+ scheduler_->SetNeedsCommit();
+ return FakeSchedulerClient::ScheduledActionDrawAndSwapIfPossible();
+ }
+
+ virtual ScheduledActionDrawAndSwapResult ScheduledActionDrawAndSwapForced()
+ OVERRIDE {
+ NOTREACHED();
+ return ScheduledActionDrawAndSwapResult(true, true);
+ }
+
+ virtual void ScheduledActionCommit() OVERRIDE {}
+ virtual void ScheduledActionBeginOutputSurfaceCreation() OVERRIDE {}
+ virtual void DidAnticipatedDrawTimeChange(base::TimeTicks) OVERRIDE {}
+};
+
+// Tests for the scheduler infinite-looping on SetNeedsCommit requests that
+// happen inside a ScheduledActionDrawAndSwap
+TEST(SchedulerTest, RequestCommitInsideDraw) {
+ SchedulerClientThatsetNeedsCommitInsideDraw client;
+ SchedulerSettings default_scheduler_settings;
+ Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings);
+ scheduler->SetCanStart();
+ scheduler->SetVisible(true);
+ scheduler->SetCanDraw(true);
+ scheduler->DidCreateAndInitializeOutputSurface();
+
+ scheduler->SetNeedsRedraw();
+ EXPECT_TRUE(scheduler->RedrawPending());
+ EXPECT_EQ(0, client.num_draws());
+ EXPECT_TRUE(client.needs_begin_frame());
+
+ scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(1, client.num_draws());
+ EXPECT_TRUE(scheduler->CommitPending());
+ EXPECT_TRUE(client.needs_begin_frame());
+ scheduler->FinishCommit();
+
+ scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(2, client.num_draws());;
+ EXPECT_FALSE(scheduler->RedrawPending());
+ EXPECT_FALSE(scheduler->CommitPending());
+ EXPECT_FALSE(client.needs_begin_frame());
+}
+
+// Tests that when a draw fails then the pending commit should not be dropped.
+TEST(SchedulerTest, RequestCommitInsideFailedDraw) {
+ SchedulerClientThatsetNeedsDrawInsideDraw client;
+ SchedulerSettings default_scheduler_settings;
+ Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings);
+ scheduler->SetCanStart();
+ scheduler->SetVisible(true);
+ scheduler->SetCanDraw(true);
+ scheduler->DidCreateAndInitializeOutputSurface();
+
+ client.SetDrawWillHappen(false);
+
+ scheduler->SetNeedsRedraw();
+ EXPECT_TRUE(scheduler->RedrawPending());
+ EXPECT_TRUE(client.needs_begin_frame());
+ EXPECT_EQ(0, client.num_draws());
+
+ // Fail the draw.
+ scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(1, client.num_draws());
+
+ // We have a commit pending and the draw failed, and we didn't lose the commit
+ // request.
+ EXPECT_TRUE(scheduler->CommitPending());
+ EXPECT_TRUE(scheduler->RedrawPending());
+ EXPECT_TRUE(client.needs_begin_frame());
+
+ // Fail the draw again.
+ scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(2, client.num_draws());
+ EXPECT_TRUE(scheduler->CommitPending());
+ EXPECT_TRUE(scheduler->RedrawPending());
+ EXPECT_TRUE(client.needs_begin_frame());
+
+ // Draw successfully.
+ client.SetDrawWillHappen(true);
+ scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(3, client.num_draws());
+ EXPECT_TRUE(scheduler->CommitPending());
+ EXPECT_FALSE(scheduler->RedrawPending());
+ EXPECT_TRUE(client.needs_begin_frame());
+}
+
+TEST(SchedulerTest, NoSwapWhenDrawFails) {
+ SchedulerClientThatsetNeedsCommitInsideDraw client;
+ SchedulerSettings default_scheduler_settings;
+ Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings);
+ scheduler->SetCanStart();
+ scheduler->SetVisible(true);
+ scheduler->SetCanDraw(true);
+ scheduler->DidCreateAndInitializeOutputSurface();
+
+ scheduler->SetNeedsRedraw();
+ EXPECT_TRUE(scheduler->RedrawPending());
+ EXPECT_TRUE(client.needs_begin_frame());
+ EXPECT_EQ(0, client.num_draws());
+
+ // Draw successfully, this starts a new frame.
+ scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(1, client.num_draws());
+
+ scheduler->SetNeedsRedraw();
+ EXPECT_TRUE(scheduler->RedrawPending());
+ EXPECT_TRUE(client.needs_begin_frame());
+
+ // Fail to draw, this should not start a frame.
+ client.SetDrawWillHappen(false);
+ scheduler->BeginFrame(BeginFrameArgs::CreateForTesting());
+ EXPECT_EQ(2, client.num_draws());
+}
+
+TEST(SchedulerTest, NoSwapWhenSwapFailsDuringForcedCommit) {
+ FakeSchedulerClient client;
+ SchedulerSettings default_scheduler_settings;
+ Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings);
+
+ // Tell the client that it will fail to swap.
+ client.SetDrawWillHappen(true);
+ client.SetSwapWillHappenIfDrawHappens(false);
+
+ // Get the compositor to do a ScheduledActionDrawAndSwapForced.
+ scheduler->SetCanDraw(true);
+ scheduler->SetNeedsRedraw();
+ scheduler->SetNeedsForcedRedraw();
+ EXPECT_TRUE(client.HasAction("ScheduledActionDrawAndSwapForced"));
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/scheduler/texture_uploader.cc b/chromium/cc/scheduler/texture_uploader.cc
new file mode 100644
index 00000000000..920c38e6692
--- /dev/null
+++ b/chromium/cc/scheduler/texture_uploader.cc
@@ -0,0 +1,350 @@
+// 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 "cc/scheduler/texture_uploader.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/debug/alias.h"
+#include "base/debug/trace_event.h"
+#include "base/metrics/histogram.h"
+#include "cc/base/util.h"
+#include "cc/resources/prioritized_resource.h"
+#include "cc/resources/resource.h"
+#include "gpu/GLES2/gl2extchromium.h"
+#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "third_party/khronos/GLES2/gl2ext.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/vector2d.h"
+
+namespace {
+
+// How many previous uploads to use when predicting future throughput.
+static const size_t kUploadHistorySizeMax = 1000;
+static const size_t kUploadHistorySizeInitial = 100;
+
+// Global estimated number of textures per second to maintain estimates across
+// subsequent instances of TextureUploader.
+// More than one thread will not access this variable, so we do not need to
+// synchronize access.
+static const double kDefaultEstimatedTexturesPerSecond = 48.0 * 60.0;
+
+// Flush interval when performing texture uploads.
+static const size_t kTextureUploadFlushPeriod = 4;
+
+} // anonymous namespace
+
+namespace cc {
+
+TextureUploader::Query::Query(WebKit::WebGraphicsContext3D* context)
+ : context_(context),
+ query_id_(0),
+ value_(0),
+ has_value_(false),
+ is_non_blocking_(false) {
+ query_id_ = context_->createQueryEXT();
+}
+
+TextureUploader::Query::~Query() { context_->deleteQueryEXT(query_id_); }
+
+void TextureUploader::Query::Begin() {
+ has_value_ = false;
+ is_non_blocking_ = false;
+ context_->beginQueryEXT(GL_COMMANDS_ISSUED_CHROMIUM, query_id_);
+}
+
+void TextureUploader::Query::End() {
+ context_->endQueryEXT(GL_COMMANDS_ISSUED_CHROMIUM);
+}
+
+bool TextureUploader::Query::IsPending() {
+ unsigned available = 1;
+ context_->getQueryObjectuivEXT(
+ query_id_, GL_QUERY_RESULT_AVAILABLE_EXT, &available);
+ return !available;
+}
+
+unsigned TextureUploader::Query::Value() {
+ if (!has_value_) {
+ context_->getQueryObjectuivEXT(query_id_, GL_QUERY_RESULT_EXT, &value_);
+ has_value_ = true;
+ }
+ return value_;
+}
+
+TextureUploader::TextureUploader(WebKit::WebGraphicsContext3D* context,
+ bool use_map_tex_sub_image,
+ bool use_shallow_flush)
+ : context_(context),
+ num_blocking_texture_uploads_(0),
+ use_map_tex_sub_image_(use_map_tex_sub_image),
+ sub_image_size_(0),
+ use_shallow_flush_(use_shallow_flush),
+ num_texture_uploads_since_last_flush_(0) {
+ for (size_t i = kUploadHistorySizeInitial; i > 0; i--)
+ textures_per_second_history_.insert(kDefaultEstimatedTexturesPerSecond);
+}
+
+TextureUploader::~TextureUploader() {}
+
+size_t TextureUploader::NumBlockingUploads() {
+ ProcessQueries();
+ return num_blocking_texture_uploads_;
+}
+
+void TextureUploader::MarkPendingUploadsAsNonBlocking() {
+ for (ScopedPtrDeque<Query>::iterator it = pending_queries_.begin();
+ it != pending_queries_.end();
+ ++it) {
+ if ((*it)->is_non_blocking())
+ continue;
+
+ num_blocking_texture_uploads_--;
+ (*it)->mark_as_non_blocking();
+ }
+
+ DCHECK(!num_blocking_texture_uploads_);
+}
+
+double TextureUploader::EstimatedTexturesPerSecond() {
+ ProcessQueries();
+
+ // Use the median as our estimate.
+ std::multiset<double>::iterator median = textures_per_second_history_.begin();
+ std::advance(median, textures_per_second_history_.size() / 2);
+ return *median;
+}
+
+void TextureUploader::BeginQuery() {
+ if (available_queries_.empty())
+ available_queries_.push_back(Query::Create(context_));
+
+ available_queries_.front()->Begin();
+}
+
+void TextureUploader::EndQuery() {
+ available_queries_.front()->End();
+ pending_queries_.push_back(available_queries_.take_front());
+ num_blocking_texture_uploads_++;
+}
+
+void TextureUploader::Upload(const uint8* image,
+ gfx::Rect image_rect,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset,
+ GLenum format,
+ gfx::Size size) {
+ CHECK(image_rect.Contains(source_rect));
+
+ bool is_full_upload = dest_offset.IsZero() && source_rect.size() == size;
+
+ if (is_full_upload)
+ BeginQuery();
+
+ if (use_map_tex_sub_image_) {
+ UploadWithMapTexSubImage(
+ image, image_rect, source_rect, dest_offset, format);
+ } else {
+ UploadWithTexSubImage(image, image_rect, source_rect, dest_offset, format);
+ }
+
+ if (is_full_upload)
+ EndQuery();
+
+ num_texture_uploads_since_last_flush_++;
+ if (num_texture_uploads_since_last_flush_ >= kTextureUploadFlushPeriod)
+ Flush();
+}
+
+void TextureUploader::Flush() {
+ if (!num_texture_uploads_since_last_flush_)
+ return;
+
+ if (use_shallow_flush_)
+ context_->shallowFlushCHROMIUM();
+
+ num_texture_uploads_since_last_flush_ = 0;
+}
+
+void TextureUploader::ReleaseCachedQueries() {
+ ProcessQueries();
+ available_queries_.clear();
+}
+
+void TextureUploader::UploadWithTexSubImage(const uint8* image,
+ gfx::Rect image_rect,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset,
+ GLenum format) {
+ // Instrumentation to debug issue 156107
+ int source_rect_x = source_rect.x();
+ int source_rect_y = source_rect.y();
+ int source_rect_width = source_rect.width();
+ int source_rect_height = source_rect.height();
+ int image_rect_x = image_rect.x();
+ int image_rect_y = image_rect.y();
+ int image_rect_width = image_rect.width();
+ int image_rect_height = image_rect.height();
+ int dest_offset_x = dest_offset.x();
+ int dest_offset_y = dest_offset.y();
+ base::debug::Alias(&image);
+ base::debug::Alias(&source_rect_x);
+ base::debug::Alias(&source_rect_y);
+ base::debug::Alias(&source_rect_width);
+ base::debug::Alias(&source_rect_height);
+ base::debug::Alias(&image_rect_x);
+ base::debug::Alias(&image_rect_y);
+ base::debug::Alias(&image_rect_width);
+ base::debug::Alias(&image_rect_height);
+ base::debug::Alias(&dest_offset_x);
+ base::debug::Alias(&dest_offset_y);
+ TRACE_EVENT0("cc", "TextureUploader::UploadWithTexSubImage");
+
+ // Offset from image-rect to source-rect.
+ gfx::Vector2d offset(source_rect.origin() - image_rect.origin());
+
+ const uint8* pixel_source;
+ unsigned int bytes_per_pixel = Resource::BytesPerPixel(format);
+ // Use 4-byte row alignment (OpenGL default) for upload performance.
+ // Assuming that GL_UNPACK_ALIGNMENT has not changed from default.
+ unsigned int upload_image_stride =
+ RoundUp(bytes_per_pixel * source_rect.width(), 4u);
+
+ if (upload_image_stride == image_rect.width() * bytes_per_pixel &&
+ !offset.x()) {
+ pixel_source = &image[image_rect.width() * bytes_per_pixel * offset.y()];
+ } else {
+ size_t needed_size = upload_image_stride * source_rect.height();
+ if (sub_image_size_ < needed_size) {
+ sub_image_.reset(new uint8[needed_size]);
+ sub_image_size_ = needed_size;
+ }
+ // Strides not equal, so do a row-by-row memcpy from the
+ // paint results into a temp buffer for uploading.
+ for (int row = 0; row < source_rect.height(); ++row)
+ memcpy(&sub_image_[upload_image_stride * row],
+ &image[bytes_per_pixel *
+ (offset.x() + (offset.y() + row) * image_rect.width())],
+ source_rect.width() * bytes_per_pixel);
+
+ pixel_source = &sub_image_[0];
+ }
+
+ context_->texSubImage2D(GL_TEXTURE_2D,
+ 0,
+ dest_offset.x(),
+ dest_offset.y(),
+ source_rect.width(),
+ source_rect.height(),
+ format,
+ GL_UNSIGNED_BYTE,
+ pixel_source);
+}
+
+void TextureUploader::UploadWithMapTexSubImage(const uint8* image,
+ gfx::Rect image_rect,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset,
+ GLenum format) {
+ // Instrumentation to debug issue 156107
+ int source_rect_x = source_rect.x();
+ int source_rect_y = source_rect.y();
+ int source_rect_width = source_rect.width();
+ int source_rect_height = source_rect.height();
+ int image_rect_x = image_rect.x();
+ int image_rect_y = image_rect.y();
+ int image_rect_width = image_rect.width();
+ int image_rect_height = image_rect.height();
+ int dest_offset_x = dest_offset.x();
+ int dest_offset_y = dest_offset.y();
+ base::debug::Alias(&image);
+ base::debug::Alias(&source_rect_x);
+ base::debug::Alias(&source_rect_y);
+ base::debug::Alias(&source_rect_width);
+ base::debug::Alias(&source_rect_height);
+ base::debug::Alias(&image_rect_x);
+ base::debug::Alias(&image_rect_y);
+ base::debug::Alias(&image_rect_width);
+ base::debug::Alias(&image_rect_height);
+ base::debug::Alias(&dest_offset_x);
+ base::debug::Alias(&dest_offset_y);
+
+ TRACE_EVENT0("cc", "TextureUploader::UploadWithMapTexSubImage");
+
+ // Offset from image-rect to source-rect.
+ gfx::Vector2d offset(source_rect.origin() - image_rect.origin());
+
+ unsigned int bytes_per_pixel = Resource::BytesPerPixel(format);
+ // Use 4-byte row alignment (OpenGL default) for upload performance.
+ // Assuming that GL_UNPACK_ALIGNMENT has not changed from default.
+ unsigned int upload_image_stride =
+ RoundUp(bytes_per_pixel * source_rect.width(), 4u);
+
+ // Upload tile data via a mapped transfer buffer
+ uint8* pixel_dest = static_cast<uint8*>(
+ context_->mapTexSubImage2DCHROMIUM(GL_TEXTURE_2D,
+ 0,
+ dest_offset.x(),
+ dest_offset.y(),
+ source_rect.width(),
+ source_rect.height(),
+ format,
+ GL_UNSIGNED_BYTE,
+ GL_WRITE_ONLY));
+
+ if (!pixel_dest) {
+ UploadWithTexSubImage(image, image_rect, source_rect, dest_offset, format);
+ return;
+ }
+
+ if (upload_image_stride == image_rect.width() * bytes_per_pixel &&
+ !offset.x()) {
+ memcpy(pixel_dest,
+ &image[image_rect.width() * bytes_per_pixel * offset.y()],
+ source_rect.height() * image_rect.width() * bytes_per_pixel);
+ } else {
+ // Strides not equal, so do a row-by-row memcpy from the
+ // paint results into the pixel_dest.
+ for (int row = 0; row < source_rect.height(); ++row) {
+ memcpy(&pixel_dest[upload_image_stride * row],
+ &image[bytes_per_pixel *
+ (offset.x() + (offset.y() + row) * image_rect.width())],
+ source_rect.width() * bytes_per_pixel);
+ }
+ }
+
+ context_->unmapTexSubImage2DCHROMIUM(pixel_dest);
+}
+
+void TextureUploader::ProcessQueries() {
+ while (!pending_queries_.empty()) {
+ if (pending_queries_.front()->IsPending())
+ break;
+
+ unsigned us_elapsed = pending_queries_.front()->Value();
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Renderer4.TextureGpuUploadTimeUS", us_elapsed, 0, 100000, 50);
+
+ // Clamp the queries to saner values in case the queries fail.
+ us_elapsed = std::max(1u, us_elapsed);
+ us_elapsed = std::min(15000u, us_elapsed);
+
+ if (!pending_queries_.front()->is_non_blocking())
+ num_blocking_texture_uploads_--;
+
+ // Remove the min and max value from our history and insert the new one.
+ double textures_per_second = 1.0 / (us_elapsed * 1e-6);
+ if (textures_per_second_history_.size() >= kUploadHistorySizeMax) {
+ textures_per_second_history_.erase(textures_per_second_history_.begin());
+ textures_per_second_history_.erase(--textures_per_second_history_.end());
+ }
+ textures_per_second_history_.insert(textures_per_second);
+
+ available_queries_.push_back(pending_queries_.take_front());
+ }
+}
+
+} // namespace cc
diff --git a/chromium/cc/scheduler/texture_uploader.h b/chromium/cc/scheduler/texture_uploader.h
new file mode 100644
index 00000000000..1457bedb727
--- /dev/null
+++ b/chromium/cc/scheduler/texture_uploader.h
@@ -0,0 +1,125 @@
+// 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.
+
+#ifndef CC_SCHEDULER_TEXTURE_UPLOADER_H_
+#define CC_SCHEDULER_TEXTURE_UPLOADER_H_
+
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/base/scoped_ptr_deque.h"
+#include "third_party/khronos/GLES2/gl2.h"
+
+namespace WebKit { class WebGraphicsContext3D; }
+
+namespace gfx {
+class Rect;
+class Size;
+class Vector2d;
+}
+
+namespace cc {
+
+class CC_EXPORT TextureUploader {
+ public:
+ static scoped_ptr<TextureUploader> Create(
+ WebKit::WebGraphicsContext3D* context,
+ bool use_map_tex_sub_image,
+ bool use_shallow_flush) {
+ return make_scoped_ptr(
+ new TextureUploader(context, use_map_tex_sub_image, use_shallow_flush));
+ }
+ ~TextureUploader();
+
+ size_t NumBlockingUploads();
+ void MarkPendingUploadsAsNonBlocking();
+ double EstimatedTexturesPerSecond();
+
+ // Let content_rect be a rectangle, and let content_rect be a sub-rectangle of
+ // content_rect, expressed in the same coordinate system as content_rect. Let
+ // image be a buffer for content_rect. This function will copy the region
+ // corresponding to source_rect to dest_offset in this sub-image.
+ void Upload(const uint8* image,
+ gfx::Rect content_rect,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset,
+ GLenum format,
+ gfx::Size size);
+
+ void Flush();
+ void ReleaseCachedQueries();
+
+ private:
+ class Query {
+ public:
+ static scoped_ptr<Query> Create(WebKit::WebGraphicsContext3D* context) {
+ return make_scoped_ptr(new Query(context));
+ }
+
+ virtual ~Query();
+
+ void Begin();
+ void End();
+ bool IsPending();
+ unsigned Value();
+ size_t TexturesUploaded();
+ void mark_as_non_blocking() {
+ is_non_blocking_ = true;
+ }
+ bool is_non_blocking() const {
+ return is_non_blocking_;
+ }
+
+ private:
+ explicit Query(WebKit::WebGraphicsContext3D* context);
+
+ WebKit::WebGraphicsContext3D* context_;
+ unsigned query_id_;
+ unsigned value_;
+ bool has_value_;
+ bool is_non_blocking_;
+
+ DISALLOW_COPY_AND_ASSIGN(Query);
+ };
+
+ TextureUploader(WebKit::WebGraphicsContext3D* context,
+ bool use_map_tex_sub_image,
+ bool use_shallow_flush);
+
+ void BeginQuery();
+ void EndQuery();
+ void ProcessQueries();
+
+ void UploadWithTexSubImage(const uint8* image,
+ gfx::Rect image_rect,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset,
+ GLenum format);
+ void UploadWithMapTexSubImage(const uint8* image,
+ gfx::Rect image_rect,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset,
+ GLenum format);
+
+ WebKit::WebGraphicsContext3D* context_;
+ ScopedPtrDeque<Query> pending_queries_;
+ ScopedPtrDeque<Query> available_queries_;
+ std::multiset<double> textures_per_second_history_;
+ size_t num_blocking_texture_uploads_;
+
+ bool use_map_tex_sub_image_;
+ size_t sub_image_size_;
+ scoped_ptr<uint8[]> sub_image_;
+
+ bool use_shallow_flush_;
+ size_t num_texture_uploads_since_last_flush_;
+
+ DISALLOW_COPY_AND_ASSIGN(TextureUploader);
+};
+
+} // namespace cc
+
+#endif // CC_SCHEDULER_TEXTURE_UPLOADER_H_
diff --git a/chromium/cc/scheduler/texture_uploader_unittest.cc b/chromium/cc/scheduler/texture_uploader_unittest.cc
new file mode 100644
index 00000000000..05959172f50
--- /dev/null
+++ b/chromium/cc/scheduler/texture_uploader_unittest.cc
@@ -0,0 +1,247 @@
+// 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 "cc/scheduler/texture_uploader.h"
+
+#include "cc/base/util.h"
+#include "cc/debug/test_web_graphics_context_3d.h"
+#include "cc/resources/prioritized_resource.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "third_party/khronos/GLES2/gl2ext.h"
+
+using WebKit::WGC3Denum;
+using WebKit::WGC3Dint;
+using WebKit::WGC3Dsizei;
+using WebKit::WebGLId;
+using WebKit::WGC3Duint;
+
+namespace cc {
+namespace {
+
+class TestWebGraphicsContext3DTextureUpload : public TestWebGraphicsContext3D {
+ public:
+ TestWebGraphicsContext3DTextureUpload()
+ : result_available_(0),
+ unpack_alignment_(4) {}
+
+ virtual void pixelStorei(WGC3Denum pname, WGC3Dint param) OVERRIDE {
+ switch (pname) {
+ case GL_UNPACK_ALIGNMENT:
+ // Param should be a power of two <= 8.
+ EXPECT_EQ(0, param & (param - 1));
+ EXPECT_GE(8, param);
+ switch (param) {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ unpack_alignment_ = param;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ virtual void getQueryObjectuivEXT(WebGLId, WGC3Denum type, WGC3Duint* value)
+ OVERRIDE {
+ switch (type) {
+ case GL_QUERY_RESULT_AVAILABLE_EXT:
+ *value = result_available_;
+ break;
+ default:
+ *value = 0;
+ break;
+ }
+ }
+
+ virtual void texSubImage2D(WGC3Denum target,
+ WGC3Dint level,
+ WGC3Dint xoffset,
+ WGC3Dint yoffset,
+ WGC3Dsizei width,
+ WGC3Dsizei height,
+ WGC3Denum format,
+ WGC3Denum type,
+ const void* pixels) OVERRIDE {
+ EXPECT_EQ(static_cast<unsigned>(GL_TEXTURE_2D), target);
+ EXPECT_EQ(0, level);
+ EXPECT_LE(0, width);
+ EXPECT_LE(0, height);
+ EXPECT_LE(0, xoffset);
+ EXPECT_LE(0, yoffset);
+ EXPECT_LE(0, width);
+ EXPECT_LE(0, height);
+
+ // Check for allowed format/type combination.
+ unsigned int bytes_per_pixel = 0;
+ switch (format) {
+ case GL_ALPHA:
+ EXPECT_EQ(static_cast<unsigned>(GL_UNSIGNED_BYTE), type);
+ bytes_per_pixel = 1;
+ break;
+ case GL_RGB:
+ EXPECT_NE(static_cast<unsigned>(GL_UNSIGNED_SHORT_4_4_4_4), type);
+ EXPECT_NE(static_cast<unsigned>(GL_UNSIGNED_SHORT_5_5_5_1), type);
+ switch (type) {
+ case GL_UNSIGNED_BYTE:
+ bytes_per_pixel = 3;
+ break;
+ case GL_UNSIGNED_SHORT_5_6_5:
+ bytes_per_pixel = 2;
+ break;
+ }
+ break;
+ case GL_RGBA:
+ EXPECT_NE(static_cast<unsigned>(GL_UNSIGNED_SHORT_5_6_5), type);
+ switch (type) {
+ case GL_UNSIGNED_BYTE:
+ bytes_per_pixel = 4;
+ break;
+ case GL_UNSIGNED_SHORT_4_4_4_4:
+ bytes_per_pixel = 2;
+ break;
+ case GL_UNSIGNED_SHORT_5_5_5_1:
+ bytes_per_pixel = 2;
+ break;
+ }
+ break;
+ case GL_LUMINANCE:
+ EXPECT_EQ(static_cast<unsigned>(GL_UNSIGNED_BYTE), type);
+ bytes_per_pixel = 1;
+ break;
+ case GL_LUMINANCE_ALPHA:
+ EXPECT_EQ(static_cast<unsigned>(GL_UNSIGNED_BYTE), type);
+ bytes_per_pixel = 2;
+ break;
+ }
+
+ // If NULL, we aren't checking texture contents.
+ if (pixels == NULL)
+ return;
+
+ const uint8* bytes = static_cast<const uint8*>(pixels);
+ // We'll expect the first byte of every row to be 0x1, and the last byte to
+ // be 0x2.
+ const unsigned int stride =
+ RoundUp(bytes_per_pixel * width, unpack_alignment_);
+ for (WGC3Dsizei row = 0; row < height; ++row) {
+ const uint8* row_bytes =
+ bytes + (xoffset * bytes_per_pixel + (yoffset + row) * stride);
+ EXPECT_EQ(0x1, row_bytes[0]);
+ EXPECT_EQ(0x2, row_bytes[width * bytes_per_pixel - 1]);
+ }
+ }
+
+ void SetResultAvailable(unsigned result_available) {
+ result_available_ = result_available;
+ }
+
+ private:
+ unsigned result_available_;
+ unsigned unpack_alignment_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestWebGraphicsContext3DTextureUpload);
+};
+
+void UploadTexture(TextureUploader* uploader,
+ WGC3Denum format,
+ gfx::Size size,
+ const uint8* data) {
+ uploader->Upload(data,
+ gfx::Rect(size),
+ gfx::Rect(size),
+ gfx::Vector2d(),
+ format,
+ size);
+}
+
+TEST(TextureUploaderTest, NumBlockingUploads) {
+ scoped_ptr<TestWebGraphicsContext3DTextureUpload> fake_context(
+ new TestWebGraphicsContext3DTextureUpload);
+ scoped_ptr<TextureUploader> uploader =
+ TextureUploader::Create(fake_context.get(), false, false);
+
+ fake_context->SetResultAvailable(0);
+ EXPECT_EQ(0u, uploader->NumBlockingUploads());
+ UploadTexture(uploader.get(), GL_RGBA, gfx::Size(), NULL);
+ EXPECT_EQ(1u, uploader->NumBlockingUploads());
+ UploadTexture(uploader.get(), GL_RGBA, gfx::Size(), NULL);
+ EXPECT_EQ(2u, uploader->NumBlockingUploads());
+
+ fake_context->SetResultAvailable(1);
+ EXPECT_EQ(0u, uploader->NumBlockingUploads());
+ UploadTexture(uploader.get(), GL_RGBA, gfx::Size(), NULL);
+ EXPECT_EQ(0u, uploader->NumBlockingUploads());
+ UploadTexture(uploader.get(), GL_RGBA, gfx::Size(), NULL);
+ UploadTexture(uploader.get(), GL_RGBA, gfx::Size(), NULL);
+ EXPECT_EQ(0u, uploader->NumBlockingUploads());
+}
+
+TEST(TextureUploaderTest, MarkPendingUploadsAsNonBlocking) {
+ scoped_ptr<TestWebGraphicsContext3DTextureUpload> fake_context(
+ new TestWebGraphicsContext3DTextureUpload);
+ scoped_ptr<TextureUploader> uploader =
+ TextureUploader::Create(fake_context.get(), false, false);
+
+ fake_context->SetResultAvailable(0);
+ EXPECT_EQ(0u, uploader->NumBlockingUploads());
+ UploadTexture(uploader.get(), GL_RGBA, gfx::Size(), NULL);
+ UploadTexture(uploader.get(), GL_RGBA, gfx::Size(), NULL);
+ EXPECT_EQ(2u, uploader->NumBlockingUploads());
+
+ uploader->MarkPendingUploadsAsNonBlocking();
+ EXPECT_EQ(0u, uploader->NumBlockingUploads());
+ UploadTexture(uploader.get(), GL_RGBA, gfx::Size(), NULL);
+ EXPECT_EQ(1u, uploader->NumBlockingUploads());
+
+ fake_context->SetResultAvailable(1);
+ EXPECT_EQ(0u, uploader->NumBlockingUploads());
+ UploadTexture(uploader.get(), GL_RGBA, gfx::Size(), NULL);
+ uploader->MarkPendingUploadsAsNonBlocking();
+ EXPECT_EQ(0u, uploader->NumBlockingUploads());
+}
+
+TEST(TextureUploaderTest, UploadContentsTest) {
+ scoped_ptr<TestWebGraphicsContext3DTextureUpload> fake_context(
+ new TestWebGraphicsContext3DTextureUpload);
+ scoped_ptr<TextureUploader> uploader =
+ TextureUploader::Create(fake_context.get(), false, false);
+ uint8 buffer[256 * 256 * 4];
+
+ // Upload a tightly packed 256x256 RGBA texture.
+ memset(buffer, 0, sizeof(buffer));
+ for (int i = 0; i < 256; ++i) {
+ // Mark the beginning and end of each row, for the test.
+ buffer[i * 4 * 256] = 0x1;
+ buffer[(i + 1) * 4 * 256 - 1] = 0x2;
+ }
+ UploadTexture(uploader.get(), GL_RGBA, gfx::Size(256, 256), buffer);
+
+ // Upload a tightly packed 41x43 RGBA texture.
+ memset(buffer, 0, sizeof(buffer));
+ for (int i = 0; i < 43; ++i) {
+ // Mark the beginning and end of each row, for the test.
+ buffer[i * 4 * 41] = 0x1;
+ buffer[(i + 1) * 4 * 41 - 1] = 0x2;
+ }
+ UploadTexture(uploader.get(), GL_RGBA, gfx::Size(41, 43), buffer);
+
+ // Upload a tightly packed 82x86 LUMINANCE texture.
+ memset(buffer, 0, sizeof(buffer));
+ for (int i = 0; i < 86; ++i) {
+ // Mark the beginning and end of each row, for the test.
+ buffer[i * 1 * 82] = 0x1;
+ buffer[(i + 1) * 82 - 1] = 0x2;
+ }
+ UploadTexture(uploader.get(), GL_LUMINANCE, gfx::Size(82, 86), buffer);
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/scheduler/time_source.h b/chromium/cc/scheduler/time_source.h
new file mode 100644
index 00000000000..e45ffbb95aa
--- /dev/null
+++ b/chromium/cc/scheduler/time_source.h
@@ -0,0 +1,51 @@
+// Copyright 2011 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.
+
+#ifndef CC_SCHEDULER_TIME_SOURCE_H_
+#define CC_SCHEDULER_TIME_SOURCE_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/time/time.h"
+#include "cc/base/cc_export.h"
+
+namespace cc {
+
+class TimeSourceClient {
+ public:
+ virtual void OnTimerTick() = 0;
+
+ protected:
+ virtual ~TimeSourceClient() {}
+};
+
+// An generic interface for getting a reliably-ticking timesource of
+// a specified rate.
+//
+// Be sure to call SetActive(false) before releasing your reference to the
+// timer, or it will keep on ticking!
+class CC_EXPORT TimeSource : public base::RefCounted<TimeSource> {
+ public:
+ virtual void SetClient(TimeSourceClient* client) = 0;
+
+ // If transitioning from not active to active, SetActive will return the
+ // timestamp of the most recenly missed tick that did not have OnTimerTick
+ // called.
+ virtual base::TimeTicks SetActive(bool active) = 0;
+
+ virtual bool Active() const = 0;
+ virtual void SetTimebaseAndInterval(base::TimeTicks timebase,
+ base::TimeDelta interval) = 0;
+ virtual base::TimeTicks LastTickTime() = 0;
+ virtual base::TimeTicks NextTickTime() = 0;
+
+ protected:
+ virtual ~TimeSource() {}
+
+ private:
+ friend class base::RefCounted<TimeSource>;
+};
+
+} // namespace cc
+
+#endif // CC_SCHEDULER_TIME_SOURCE_H_
diff --git a/chromium/cc/trees/damage_tracker.cc b/chromium/cc/trees/damage_tracker.cc
new file mode 100644
index 00000000000..64b15867e07
--- /dev/null
+++ b/chromium/cc/trees/damage_tracker.cc
@@ -0,0 +1,400 @@
+// Copyright 2011 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 "cc/trees/damage_tracker.h"
+
+#include <algorithm>
+
+#include "cc/base/math_util.h"
+#include "cc/layers/heads_up_display_layer_impl.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/layers/render_surface_impl.h"
+#include "cc/output/filter_operations.h"
+#include "cc/trees/layer_tree_host_common.h"
+#include "cc/trees/layer_tree_impl.h"
+
+namespace cc {
+
+scoped_ptr<DamageTracker> DamageTracker::Create() {
+ return make_scoped_ptr(new DamageTracker());
+}
+
+DamageTracker::DamageTracker()
+ : current_rect_history_(new RectMap),
+ next_rect_history_(new RectMap) {}
+
+DamageTracker::~DamageTracker() {}
+
+static inline void ExpandRectWithFilters(
+ gfx::RectF* rect, const FilterOperations& filters) {
+ int top, right, bottom, left;
+ filters.GetOutsets(&top, &right, &bottom, &left);
+ rect->Inset(-left, -top, -right, -bottom);
+}
+
+static inline void ExpandDamageRectInsideRectWithFilters(
+ gfx::RectF* damage_rect,
+ const gfx::RectF& pre_filter_rect,
+ const FilterOperations& filters) {
+ gfx::RectF expanded_damage_rect = *damage_rect;
+ ExpandRectWithFilters(&expanded_damage_rect, filters);
+ gfx::RectF filter_rect = pre_filter_rect;
+ ExpandRectWithFilters(&filter_rect, filters);
+
+ expanded_damage_rect.Intersect(filter_rect);
+ damage_rect->Union(expanded_damage_rect);
+}
+
+void DamageTracker::UpdateDamageTrackingState(
+ const LayerImplList& layer_list,
+ int target_surface_layer_id,
+ bool target_surface_property_changed_only_from_descendant,
+ gfx::Rect target_surface_content_rect,
+ LayerImpl* target_surface_mask_layer,
+ const FilterOperations& filters,
+ SkImageFilter* filter) {
+ //
+ // This function computes the "damage rect" of a target surface, and updates
+ // the state that is used to correctly track damage across frames. The damage
+ // rect is the region of the surface that may have changed and needs to be
+ // redrawn. This can be used to scissor what is actually drawn, to save GPU
+ // computation and bandwidth.
+ //
+ // The surface's damage rect is computed as the union of all possible changes
+ // that have happened to the surface since the last frame was drawn. This
+ // includes:
+ // - any changes for existing layers/surfaces that contribute to the target
+ // surface
+ // - layers/surfaces that existed in the previous frame, but no longer exist
+ //
+ // The basic algorithm for computing the damage region is as follows:
+ //
+ // 1. compute damage caused by changes in active/new layers
+ // for each layer in the layer_list:
+ // if the layer is actually a render_surface:
+ // add the surface's damage to our target surface.
+ // else
+ // add the layer's damage to the target surface.
+ //
+ // 2. compute damage caused by the target surface's mask, if it exists.
+ //
+ // 3. compute damage caused by old layers/surfaces that no longer exist
+ // for each leftover layer:
+ // add the old layer/surface bounds to the target surface damage.
+ //
+ // 4. combine all partial damage rects to get the full damage rect.
+ //
+ // Additional important points:
+ //
+ // - This algorithm is implicitly recursive; it assumes that descendant
+ // surfaces have already computed their damage.
+ //
+ // - Changes to layers/surfaces indicate "damage" to the target surface; If a
+ // layer is not changed, it does NOT mean that the layer can skip drawing.
+ // All layers that overlap the damaged region still need to be drawn. For
+ // example, if a layer changed its opacity, then layers underneath must be
+ // re-drawn as well, even if they did not change.
+ //
+ // - If a layer/surface property changed, the old bounds and new bounds may
+ // overlap... i.e. some of the exposed region may not actually be exposing
+ // anything. But this does not artificially inflate the damage rect. If the
+ // layer changed, its entire old bounds would always need to be redrawn,
+ // regardless of how much it overlaps with the layer's new bounds, which
+ // also need to be entirely redrawn.
+ //
+ // - See comments in the rest of the code to see what exactly is considered a
+ // "change" in a layer/surface.
+ //
+ // - To correctly manage exposed rects, two RectMaps are maintained:
+ //
+ // 1. The "current" map contains all the layer bounds that contributed to
+ // the previous frame (even outside the previous damaged area). If a
+ // layer changes or does not exist anymore, those regions are then
+ // exposed and damage the target surface. As the algorithm progresses,
+ // entries are removed from the map until it has only leftover layers
+ // that no longer exist.
+ //
+ // 2. The "next" map starts out empty, and as the algorithm progresses,
+ // every layer/surface that contributes to the surface is added to the
+ // map.
+ //
+ // 3. After the damage rect is computed, the two maps are swapped, so
+ // that the damage tracker is ready for the next frame.
+ //
+
+ // These functions cannot be bypassed with early-exits, even if we know what
+ // the damage will be for this frame, because we need to update the damage
+ // tracker state to correctly track the next frame.
+ gfx::RectF damage_from_active_layers =
+ TrackDamageFromActiveLayers(layer_list, target_surface_layer_id);
+ gfx::RectF damage_from_surface_mask =
+ TrackDamageFromSurfaceMask(target_surface_mask_layer);
+ gfx::RectF damage_from_leftover_rects = TrackDamageFromLeftoverRects();
+
+ gfx::RectF damage_rect_for_this_update;
+
+ if (target_surface_property_changed_only_from_descendant) {
+ damage_rect_for_this_update = target_surface_content_rect;
+ } else {
+ // TODO(shawnsingh): can we clamp this damage to the surface's content rect?
+ // (affects performance, but not correctness)
+ damage_rect_for_this_update = damage_from_active_layers;
+ damage_rect_for_this_update.Union(damage_from_surface_mask);
+ damage_rect_for_this_update.Union(damage_from_leftover_rects);
+
+ if (filters.HasFilterThatMovesPixels()) {
+ ExpandRectWithFilters(&damage_rect_for_this_update, filters);
+ } else if (filter) {
+ // TODO(senorblanco): Once SkImageFilter reports its outsets, use
+ // those here to limit damage.
+ damage_rect_for_this_update = target_surface_content_rect;
+ }
+ }
+
+ // Damage accumulates until we are notified that we actually did draw on that
+ // frame.
+ current_damage_rect_.Union(damage_rect_for_this_update);
+
+ // The next history map becomes the current map for the next frame. Note this
+ // must happen every frame to correctly track changes, even if damage
+ // accumulates over multiple frames before actually being drawn.
+ swap(current_rect_history_, next_rect_history_);
+}
+
+gfx::RectF DamageTracker::RemoveRectFromCurrentFrame(int layer_id,
+ bool* layer_is_new) {
+ RectMap::iterator iter = current_rect_history_->find(layer_id);
+ *layer_is_new = iter == current_rect_history_->end();
+ if (*layer_is_new)
+ return gfx::RectF();
+
+ gfx::RectF ret = iter->second;
+ current_rect_history_->erase(iter);
+ return ret;
+}
+
+void DamageTracker::SaveRectForNextFrame(int layer_id,
+ const gfx::RectF& target_space_rect) {
+ // This layer should not yet exist in next frame's history.
+ DCHECK_GT(layer_id, 0);
+ DCHECK(next_rect_history_->find(layer_id) == next_rect_history_->end());
+ (*next_rect_history_)[layer_id] = target_space_rect;
+}
+
+gfx::RectF DamageTracker::TrackDamageFromActiveLayers(
+ const LayerImplList& layer_list,
+ int target_surface_layer_id) {
+ gfx::RectF damage_rect = gfx::RectF();
+
+ for (size_t layer_index = 0; layer_index < layer_list.size(); ++layer_index) {
+ // Visit layers in back-to-front order.
+ LayerImpl* layer = layer_list[layer_index];
+
+ // We skip damage from the HUD layer because (a) the HUD layer damages the
+ // whole frame and (b) we don't want HUD layer damage to be shown by the
+ // HUD damage rect visualization.
+ if (layer == layer->layer_tree_impl()->hud_layer())
+ continue;
+
+ if (LayerTreeHostCommon::RenderSurfaceContributesToTarget<LayerImpl>(
+ layer, target_surface_layer_id))
+ ExtendDamageForRenderSurface(layer, &damage_rect);
+ else
+ ExtendDamageForLayer(layer, &damage_rect);
+ }
+
+ return damage_rect;
+}
+
+gfx::RectF DamageTracker::TrackDamageFromSurfaceMask(
+ LayerImpl* target_surface_mask_layer) {
+ gfx::RectF damage_rect = gfx::RectF();
+
+ if (!target_surface_mask_layer)
+ return damage_rect;
+
+ // Currently, if there is any change to the mask, we choose to damage the
+ // entire surface. This could potentially be optimized later, but it is not
+ // expected to be a common case.
+ if (target_surface_mask_layer->LayerPropertyChanged() ||
+ !target_surface_mask_layer->update_rect().IsEmpty()) {
+ damage_rect = gfx::RectF(gfx::PointF(),
+ target_surface_mask_layer->bounds());
+ }
+
+ return damage_rect;
+}
+
+gfx::RectF DamageTracker::TrackDamageFromLeftoverRects() {
+ // After computing damage for all active layers, any leftover items in the
+ // current rect history correspond to layers/surfaces that no longer exist.
+ // So, these regions are now exposed on the target surface.
+
+ gfx::RectF damage_rect = gfx::RectF();
+
+ for (RectMap::iterator it = current_rect_history_->begin();
+ it != current_rect_history_->end();
+ ++it)
+ damage_rect.Union(it->second);
+
+ current_rect_history_->clear();
+
+ return damage_rect;
+}
+
+static bool LayerNeedsToRedrawOntoItsTargetSurface(LayerImpl* layer) {
+ // If the layer does NOT own a surface but has SurfacePropertyChanged,
+ // this means that its target surface is affected and needs to be redrawn.
+ // However, if the layer DOES own a surface, then the SurfacePropertyChanged
+ // flag should not be used here, because that flag represents whether the
+ // layer's surface has changed.
+ if (layer->render_surface())
+ return layer->LayerPropertyChanged();
+ return layer->LayerPropertyChanged() || layer->LayerSurfacePropertyChanged();
+}
+
+void DamageTracker::ExtendDamageForLayer(LayerImpl* layer,
+ gfx::RectF* target_damage_rect) {
+ // There are two ways that a layer can damage a region of the target surface:
+ // 1. Property change (e.g. opacity, position, transforms):
+ // - the entire region of the layer itself damages the surface.
+ // - the old layer region also damages the surface, because this region
+ // is now exposed.
+ // - note that in many cases the old and new layer rects may overlap,
+ // which is fine.
+ //
+ // 2. Repaint/update: If a region of the layer that was repainted/updated,
+ // that region damages the surface.
+ //
+ // Property changes take priority over update rects.
+ //
+ // This method is called when we want to consider how a layer contributes to
+ // its target RenderSurface, even if that layer owns the target RenderSurface
+ // itself. To consider how a layer's target surface contributes to the
+ // ancestor surface, ExtendDamageForRenderSurface() must be called instead.
+
+ bool layer_is_new = false;
+ gfx::RectF old_rect_in_target_space =
+ RemoveRectFromCurrentFrame(layer->id(), &layer_is_new);
+
+ gfx::RectF rect_in_target_space = MathUtil::MapClippedRect(
+ layer->draw_transform(),
+ gfx::RectF(gfx::PointF(), layer->content_bounds()));
+ SaveRectForNextFrame(layer->id(), rect_in_target_space);
+
+ if (layer_is_new || LayerNeedsToRedrawOntoItsTargetSurface(layer)) {
+ // If a layer is new or has changed, then its entire layer rect affects the
+ // target surface.
+ target_damage_rect->Union(rect_in_target_space);
+
+ // The layer's old region is now exposed on the target surface, too.
+ // Note old_rect_in_target_space is already in target space.
+ target_damage_rect->Union(old_rect_in_target_space);
+ } else if (!layer->update_rect().IsEmpty()) {
+ // If the layer properties haven't changed, then the the target surface is
+ // only affected by the layer's update area, which could be empty.
+ gfx::RectF update_content_rect =
+ layer->LayerRectToContentRect(layer->update_rect());
+ gfx::RectF update_rect_in_target_space =
+ MathUtil::MapClippedRect(layer->draw_transform(), update_content_rect);
+ target_damage_rect->Union(update_rect_in_target_space);
+ }
+}
+
+void DamageTracker::ExtendDamageForRenderSurface(
+ LayerImpl* layer, gfx::RectF* target_damage_rect) {
+ // There are two ways a "descendant surface" can damage regions of the "target
+ // surface":
+ // 1. Property change:
+ // - a surface's geometry can change because of
+ // - changes to descendants (i.e. the subtree) that affect the
+ // surface's content rect
+ // - changes to ancestor layers that propagate their property
+ // changes to their entire subtree.
+ // - just like layers, both the old surface rect and new surface rect
+ // will damage the target surface in this case.
+ //
+ // 2. Damage rect: This surface may have been damaged by its own layer_list
+ // as well, and that damage should propagate to the target surface.
+ //
+
+ RenderSurfaceImpl* render_surface = layer->render_surface();
+
+ bool surface_is_new = false;
+ gfx::RectF old_surface_rect = RemoveRectFromCurrentFrame(layer->id(),
+ &surface_is_new);
+
+ // The drawableContextRect() already includes the replica if it exists.
+ gfx::RectF surface_rect_in_target_space =
+ render_surface->DrawableContentRect();
+ SaveRectForNextFrame(layer->id(), surface_rect_in_target_space);
+
+ gfx::RectF damage_rect_in_local_space;
+ if (surface_is_new ||
+ render_surface->SurfacePropertyChanged() ||
+ layer->LayerSurfacePropertyChanged()) {
+ // The entire surface contributes damage.
+ damage_rect_in_local_space = render_surface->content_rect();
+
+ // The surface's old region is now exposed on the target surface, too.
+ target_damage_rect->Union(old_surface_rect);
+ } else {
+ // Only the surface's damage_rect will damage the target surface.
+ damage_rect_in_local_space =
+ render_surface->damage_tracker()->current_damage_rect();
+ }
+
+ // If there was damage, transform it to target space, and possibly contribute
+ // its reflection if needed.
+ if (!damage_rect_in_local_space.IsEmpty()) {
+ const gfx::Transform& draw_transform = render_surface->draw_transform();
+ gfx::RectF damage_rect_in_target_space =
+ MathUtil::MapClippedRect(draw_transform, damage_rect_in_local_space);
+ target_damage_rect->Union(damage_rect_in_target_space);
+
+ if (layer->replica_layer()) {
+ const gfx::Transform& replica_draw_transform =
+ render_surface->replica_draw_transform();
+ target_damage_rect->Union(MathUtil::MapClippedRect(
+ replica_draw_transform, damage_rect_in_local_space));
+ }
+ }
+
+ // If there was damage on the replica's mask, then the target surface receives
+ // that damage as well.
+ if (layer->replica_layer() && layer->replica_layer()->mask_layer()) {
+ LayerImpl* replica_mask_layer = layer->replica_layer()->mask_layer();
+
+ bool replica_is_new = false;
+ RemoveRectFromCurrentFrame(replica_mask_layer->id(), &replica_is_new);
+
+ const gfx::Transform& replica_draw_transform =
+ render_surface->replica_draw_transform();
+ gfx::RectF replica_mask_layer_rect = MathUtil::MapClippedRect(
+ replica_draw_transform,
+ gfx::RectF(gfx::PointF(), replica_mask_layer->bounds()));
+ SaveRectForNextFrame(replica_mask_layer->id(), replica_mask_layer_rect);
+
+ // In the current implementation, a change in the replica mask damages the
+ // entire replica region.
+ if (replica_is_new ||
+ replica_mask_layer->LayerPropertyChanged() ||
+ !replica_mask_layer->update_rect().IsEmpty())
+ target_damage_rect->Union(replica_mask_layer_rect);
+ }
+
+ // If the layer has a background filter, this may cause pixels in our surface
+ // to be expanded, so we will need to expand any damage at or below this
+ // layer. We expand the damage from this layer too, as we need to readback
+ // those pixels from the surface with only the contents of layers below this
+ // one in them. This means we need to redraw any pixels in the surface being
+ // used for the blur in this layer this frame.
+ if (layer->background_filters().HasFilterThatMovesPixels()) {
+ ExpandDamageRectInsideRectWithFilters(target_damage_rect,
+ surface_rect_in_target_space,
+ layer->background_filters());
+ }
+}
+
+} // namespace cc
diff --git a/chromium/cc/trees/damage_tracker.h b/chromium/cc/trees/damage_tracker.h
new file mode 100644
index 00000000000..ce72fcc842c
--- /dev/null
+++ b/chromium/cc/trees/damage_tracker.h
@@ -0,0 +1,80 @@
+// Copyright 2011 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.
+
+#ifndef CC_TREES_DAMAGE_TRACKER_H_
+#define CC_TREES_DAMAGE_TRACKER_H_
+
+#include <vector>
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/layers/layer_lists.h"
+#include "ui/gfx/rect_f.h"
+
+class SkImageFilter;
+
+namespace gfx {
+class Rect;
+}
+
+namespace cc {
+
+class FilterOperations;
+class LayerImpl;
+class RenderSurfaceImpl;
+
+// Computes the region where pixels have actually changed on a
+// RenderSurfaceImpl. This region is used to scissor what is actually drawn to
+// the screen to save GPU computation and bandwidth.
+class CC_EXPORT DamageTracker {
+ public:
+ static scoped_ptr<DamageTracker> Create();
+ ~DamageTracker();
+
+ void DidDrawDamagedArea() { current_damage_rect_ = gfx::RectF(); }
+ void AddDamageNextUpdate(gfx::RectF dmg) { current_damage_rect_.Union(dmg); }
+ void UpdateDamageTrackingState(
+ const LayerImplList& layer_list,
+ int target_surface_layer_id,
+ bool target_surface_property_changed_only_from_descendant,
+ gfx::Rect target_surface_content_rect,
+ LayerImpl* target_surface_mask_layer,
+ const FilterOperations& filters,
+ SkImageFilter* filter);
+
+ gfx::RectF current_damage_rect() { return current_damage_rect_; }
+
+ private:
+ DamageTracker();
+
+ gfx::RectF TrackDamageFromActiveLayers(
+ const LayerImplList& layer_list,
+ int target_surface_layer_id);
+ gfx::RectF TrackDamageFromSurfaceMask(LayerImpl* target_surface_mask_layer);
+ gfx::RectF TrackDamageFromLeftoverRects();
+
+ gfx::RectF RemoveRectFromCurrentFrame(int layer_id, bool* layer_is_new);
+ void SaveRectForNextFrame(int layer_id, const gfx::RectF& target_space_rect);
+
+ // These helper functions are used only in TrackDamageFromActiveLayers().
+ void ExtendDamageForLayer(LayerImpl* layer, gfx::RectF* target_damage_rect);
+ void ExtendDamageForRenderSurface(LayerImpl* layer,
+ gfx::RectF* target_damage_rect);
+
+ // To correctly track exposed regions, two hashtables of rects are maintained.
+ // The "current" map is used to compute exposed regions of the current frame,
+ // while the "next" map is used to collect layer rects that are used in the
+ // next frame.
+ typedef base::hash_map<int, gfx::RectF> RectMap;
+ scoped_ptr<RectMap> current_rect_history_;
+ scoped_ptr<RectMap> next_rect_history_;
+
+ gfx::RectF current_damage_rect_;
+
+ DISALLOW_COPY_AND_ASSIGN(DamageTracker);
+};
+
+} // namespace cc
+
+#endif // CC_TREES_DAMAGE_TRACKER_H_
diff --git a/chromium/cc/trees/damage_tracker_unittest.cc b/chromium/cc/trees/damage_tracker_unittest.cc
new file mode 100644
index 00000000000..9e4537974d9
--- /dev/null
+++ b/chromium/cc/trees/damage_tracker_unittest.cc
@@ -0,0 +1,1349 @@
+// Copyright 2011 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 "cc/trees/damage_tracker.h"
+
+#include "cc/base/math_util.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/output/filter_operation.h"
+#include "cc/output/filter_operations.h"
+#include "cc/test/fake_impl_proxy.h"
+#include "cc/test/fake_layer_tree_host_impl.h"
+#include "cc/test/geometry_test_utils.h"
+#include "cc/trees/layer_tree_host_common.h"
+#include "cc/trees/single_thread_proxy.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/effects/SkBlurImageFilter.h"
+#include "ui/gfx/quad_f.h"
+
+namespace cc {
+namespace {
+
+void ExecuteCalculateDrawProperties(LayerImpl* root,
+ LayerImplList& render_surface_layer_list) {
+ // Sanity check: The test itself should create the root layer's render
+ // surface, so that the surface (and its damage tracker) can
+ // persist across multiple calls to this function.
+ ASSERT_TRUE(root->render_surface());
+ ASSERT_FALSE(render_surface_layer_list.size());
+
+ LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
+ root, root->bounds(), &render_surface_layer_list);
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+}
+
+void ClearDamageForAllSurfaces(LayerImpl* layer) {
+ if (layer->render_surface())
+ layer->render_surface()->damage_tracker()->DidDrawDamagedArea();
+
+ // Recursively clear damage for any existing surface.
+ for (size_t i = 0; i < layer->children().size(); ++i)
+ ClearDamageForAllSurfaces(layer->children()[i]);
+}
+
+void EmulateDrawingOneFrame(LayerImpl* root) {
+ // This emulates only steps that are relevant to testing the damage tracker:
+ // 1. computing the render passes and layerlists
+ // 2. updating all damage trackers in the correct order
+ // 3. resetting all update_rects and property_changed flags for all layers
+ // and surfaces.
+
+ LayerImplList render_surface_layer_list;
+ ExecuteCalculateDrawProperties(root, render_surface_layer_list);
+
+ // Iterate back-to-front, so that damage correctly propagates from descendant
+ // surfaces to ancestors.
+ for (int i = render_surface_layer_list.size() - 1; i >= 0; --i) {
+ RenderSurfaceImpl* target_surface =
+ render_surface_layer_list[i]->render_surface();
+ target_surface->damage_tracker()->UpdateDamageTrackingState(
+ target_surface->layer_list(),
+ target_surface->OwningLayerId(),
+ target_surface->SurfacePropertyChangedOnlyFromDescendant(),
+ target_surface->content_rect(),
+ render_surface_layer_list[i]->mask_layer(),
+ render_surface_layer_list[i]->filters(),
+ render_surface_layer_list[i]->filter().get());
+ }
+
+ root->ResetAllChangeTrackingForSubtree();
+}
+
+class DamageTrackerTest : public testing::Test {
+ public:
+ DamageTrackerTest() : host_impl_(&proxy_) {}
+
+ scoped_ptr<LayerImpl> CreateTestTreeWithOneSurface() {
+ scoped_ptr<LayerImpl> root =
+ LayerImpl::Create(host_impl_.active_tree(), 1);
+ scoped_ptr<LayerImpl> child =
+ LayerImpl::Create(host_impl_.active_tree(), 2);
+
+ root->SetPosition(gfx::PointF());
+ root->SetAnchorPoint(gfx::PointF());
+ root->SetBounds(gfx::Size(500, 500));
+ root->SetContentBounds(gfx::Size(500, 500));
+ root->SetDrawsContent(true);
+ root->CreateRenderSurface();
+ root->render_surface()->SetContentRect(gfx::Rect(0, 0, 500, 500));
+
+ child->SetPosition(gfx::PointF(100.f, 100.f));
+ child->SetAnchorPoint(gfx::PointF());
+ child->SetBounds(gfx::Size(30, 30));
+ child->SetContentBounds(gfx::Size(30, 30));
+ child->SetDrawsContent(true);
+ root->AddChild(child.Pass());
+
+ return root.Pass();
+ }
+
+ scoped_ptr<LayerImpl> CreateTestTreeWithTwoSurfaces() {
+ // This test tree has two render surfaces: one for the root, and one for
+ // child1. Additionally, the root has a second child layer, and child1 has
+ // two children of its own.
+
+ scoped_ptr<LayerImpl> root =
+ LayerImpl::Create(host_impl_.active_tree(), 1);
+ scoped_ptr<LayerImpl> child1 =
+ LayerImpl::Create(host_impl_.active_tree(), 2);
+ scoped_ptr<LayerImpl> child2 =
+ LayerImpl::Create(host_impl_.active_tree(), 3);
+ scoped_ptr<LayerImpl> grand_child1 =
+ LayerImpl::Create(host_impl_.active_tree(), 4);
+ scoped_ptr<LayerImpl> grand_child2 =
+ LayerImpl::Create(host_impl_.active_tree(), 5);
+
+ root->SetPosition(gfx::PointF());
+ root->SetAnchorPoint(gfx::PointF());
+ root->SetBounds(gfx::Size(500, 500));
+ root->SetContentBounds(gfx::Size(500, 500));
+ root->SetDrawsContent(true);
+ root->CreateRenderSurface();
+ root->render_surface()->SetContentRect(gfx::Rect(0, 0, 500, 500));
+
+ child1->SetPosition(gfx::PointF(100.f, 100.f));
+ child1->SetAnchorPoint(gfx::PointF());
+ child1->SetBounds(gfx::Size(30, 30));
+ child1->SetContentBounds(gfx::Size(30, 30));
+ // With a child that draws_content, opacity will cause the layer to create
+ // its own RenderSurface. This layer does not draw, but is intended to
+ // create its own RenderSurface. TODO: setting opacity and
+ // ForceRenderSurface may be redundant here.
+ child1->SetOpacity(0.5f);
+ child1->SetDrawsContent(false);
+ child1->SetForceRenderSurface(true);
+
+ child2->SetPosition(gfx::PointF(11.f, 11.f));
+ child2->SetAnchorPoint(gfx::PointF());
+ child2->SetBounds(gfx::Size(18, 18));
+ child2->SetContentBounds(gfx::Size(18, 18));
+ child2->SetDrawsContent(true);
+
+ grand_child1->SetPosition(gfx::PointF(200.f, 200.f));
+ grand_child1->SetAnchorPoint(gfx::PointF());
+ grand_child1->SetBounds(gfx::Size(6, 8));
+ grand_child1->SetContentBounds(gfx::Size(6, 8));
+ grand_child1->SetDrawsContent(true);
+
+ grand_child2->SetPosition(gfx::PointF(190.f, 190.f));
+ grand_child2->SetAnchorPoint(gfx::PointF());
+ grand_child2->SetBounds(gfx::Size(6, 8));
+ grand_child2->SetContentBounds(gfx::Size(6, 8));
+ grand_child2->SetDrawsContent(true);
+
+ child1->AddChild(grand_child1.Pass());
+ child1->AddChild(grand_child2.Pass());
+ root->AddChild(child1.Pass());
+ root->AddChild(child2.Pass());
+
+ return root.Pass();
+ }
+
+ scoped_ptr<LayerImpl> CreateAndSetUpTestTreeWithOneSurface() {
+ scoped_ptr<LayerImpl> root = CreateTestTreeWithOneSurface();
+
+ // Setup includes going past the first frame which always damages
+ // everything, so that we can actually perform specific tests.
+ EmulateDrawingOneFrame(root.get());
+
+ return root.Pass();
+ }
+
+ scoped_ptr<LayerImpl> CreateAndSetUpTestTreeWithTwoSurfaces() {
+ scoped_ptr<LayerImpl> root = CreateTestTreeWithTwoSurfaces();
+
+ // Setup includes going past the first frame which always damages
+ // everything, so that we can actually perform specific tests.
+ EmulateDrawingOneFrame(root.get());
+
+ return root.Pass();
+ }
+
+ protected:
+ FakeImplProxy proxy_;
+ FakeLayerTreeHostImpl host_impl_;
+};
+
+TEST_F(DamageTrackerTest, SanityCheckTestTreeWithOneSurface) {
+ // Sanity check that the simple test tree will actually produce the expected
+ // render surfaces and layer lists.
+
+ scoped_ptr<LayerImpl> root = CreateAndSetUpTestTreeWithOneSurface();
+
+ EXPECT_EQ(2u, root->render_surface()->layer_list().size());
+ EXPECT_EQ(1, root->render_surface()->layer_list()[0]->id());
+ EXPECT_EQ(2, root->render_surface()->layer_list()[1]->id());
+
+ gfx::RectF root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(0.f, 0.f, 500.f, 500.f), root_damage_rect);
+}
+
+TEST_F(DamageTrackerTest, SanityCheckTestTreeWithTwoSurfaces) {
+ // Sanity check that the complex test tree will actually produce the expected
+ // render surfaces and layer lists.
+
+ scoped_ptr<LayerImpl> root = CreateAndSetUpTestTreeWithTwoSurfaces();
+
+ LayerImpl* child1 = root->children()[0];
+ LayerImpl* child2 = root->children()[1];
+ gfx::RectF child_damage_rect =
+ child1->render_surface()->damage_tracker()->current_damage_rect();
+ gfx::RectF root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+
+ ASSERT_TRUE(child1->render_surface());
+ EXPECT_FALSE(child2->render_surface());
+ EXPECT_EQ(3u, root->render_surface()->layer_list().size());
+ EXPECT_EQ(2u, child1->render_surface()->layer_list().size());
+
+ // The render surface for child1 only has a content_rect that encloses
+ // grand_child1 and grand_child2, because child1 does not draw content.
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(190.f, 190.f, 16.f, 18.f), child_damage_rect);
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(0.f, 0.f, 500.f, 500.f), root_damage_rect);
+}
+
+TEST_F(DamageTrackerTest, VerifyDamageForUpdateRects) {
+ scoped_ptr<LayerImpl> root = CreateAndSetUpTestTreeWithOneSurface();
+ LayerImpl* child = root->children()[0];
+
+ // CASE 1: Setting the update rect should cause the corresponding damage to
+ // the surface.
+ ClearDamageForAllSurfaces(root.get());
+ child->set_update_rect(gfx::RectF(10.f, 11.f, 12.f, 13.f));
+ EmulateDrawingOneFrame(root.get());
+
+ // Damage position on the surface should be: position of update_rect (10, 11)
+ // relative to the child (100, 100).
+ gfx::RectF root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(110.f, 111.f, 12.f, 13.f), root_damage_rect);
+
+ // CASE 2: The same update rect twice in a row still produces the same
+ // damage.
+ ClearDamageForAllSurfaces(root.get());
+ child->set_update_rect(gfx::RectF(10.f, 11.f, 12.f, 13.f));
+ EmulateDrawingOneFrame(root.get());
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(110.f, 111.f, 12.f, 13.f), root_damage_rect);
+
+ // CASE 3: Setting a different update rect should cause damage on the new
+ // update region, but no additional exposed old region.
+ ClearDamageForAllSurfaces(root.get());
+ child->set_update_rect(gfx::RectF(20.f, 25.f, 1.f, 2.f));
+ EmulateDrawingOneFrame(root.get());
+
+ // Damage position on the surface should be: position of update_rect (20, 25)
+ // relative to the child (100, 100).
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(120.f, 125.f, 1.f, 2.f), root_damage_rect);
+}
+
+TEST_F(DamageTrackerTest, VerifyDamageForPropertyChanges) {
+ scoped_ptr<LayerImpl> root = CreateAndSetUpTestTreeWithOneSurface();
+ LayerImpl* child = root->children()[0];
+
+ // CASE 1: The layer's property changed flag takes priority over update rect.
+ //
+ ClearDamageForAllSurfaces(root.get());
+ child->set_update_rect(gfx::RectF(10.f, 11.f, 12.f, 13.f));
+ child->SetOpacity(0.5f);
+ EmulateDrawingOneFrame(root.get());
+
+ // Sanity check - we should not have accidentally created a separate render
+ // surface for the translucent layer.
+ ASSERT_FALSE(child->render_surface());
+ ASSERT_EQ(2u, root->render_surface()->layer_list().size());
+
+ // Damage should be the entire child layer in target_surface space.
+ gfx::RectF expected_rect = gfx::RectF(100.f, 100.f, 30.f, 30.f);
+ gfx::RectF root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_FLOAT_RECT_EQ(expected_rect, root_damage_rect);
+
+ // CASE 2: If a layer moves due to property change, it damages both the new
+ // location and the old (exposed) location. The old location is the
+ // entire old layer, not just the update_rect.
+
+ // Cycle one frame of no change, just to sanity check that the next rect is
+ // not because of the old damage state.
+ ClearDamageForAllSurfaces(root.get());
+ EmulateDrawingOneFrame(root.get());
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_TRUE(root_damage_rect.IsEmpty());
+
+ // Then, test the actual layer movement.
+ ClearDamageForAllSurfaces(root.get());
+ child->SetPosition(gfx::PointF(200.f, 230.f));
+ EmulateDrawingOneFrame(root.get());
+
+ // Expect damage to be the combination of the previous one and the new one.
+ expected_rect.Union(gfx::RectF(200, 230, 30, 30));
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_FLOAT_RECT_EQ(expected_rect, root_damage_rect);
+}
+
+TEST_F(DamageTrackerTest, VerifyDamageForTransformedLayer) {
+ // If a layer is transformed, the damage rect should still enclose the entire
+ // transformed layer.
+
+ scoped_ptr<LayerImpl> root = CreateAndSetUpTestTreeWithOneSurface();
+ LayerImpl* child = root->children()[0];
+
+ gfx::Transform rotation;
+ rotation.Rotate(45.0);
+
+ ClearDamageForAllSurfaces(root.get());
+ child->SetAnchorPoint(gfx::PointF(0.5f, 0.5f));
+ child->SetPosition(gfx::PointF(85.f, 85.f));
+ EmulateDrawingOneFrame(root.get());
+
+ // Sanity check that the layer actually moved to (85, 85), damaging its old
+ // location and new location.
+ gfx::RectF root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(85.f, 85.f, 45.f, 45.f), root_damage_rect);
+
+ // With the anchor on the layer's center, now we can test the rotation more
+ // intuitively, since it applies about the layer's anchor.
+ ClearDamageForAllSurfaces(root.get());
+ child->SetTransform(rotation);
+ EmulateDrawingOneFrame(root.get());
+
+ // Since the child layer is square, rotation by 45 degrees about the center
+ // should increase the size of the expected rect by sqrt(2), centered around
+ // (100, 100). The old exposed region should be fully contained in the new
+ // region.
+ double expected_width = 30.0 * sqrt(2.0);
+ double expected_position = 100.0 - 0.5 * expected_width;
+ gfx::RectF expected_rect(expected_position,
+ expected_position,
+ expected_width,
+ expected_width);
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_FLOAT_RECT_EQ(expected_rect, root_damage_rect);
+}
+
+TEST_F(DamageTrackerTest, VerifyDamageForPerspectiveClippedLayer) {
+ // If a layer has a perspective transform that causes w < 0, then not
+ // clipping the layer can cause an invalid damage rect. This test checks that
+ // the w < 0 case is tracked properly.
+ //
+ // The transform is constructed so that if w < 0 clipping is not performed,
+ // the incorrect rect will be very small, specifically: position (500.972504,
+ // 498.544617) and size 0.056610 x 2.910767. Instead, the correctly
+ // transformed rect should actually be very huge (i.e. in theory, -infinity
+ // on the left), and positioned so that the right-most bound rect will be
+ // approximately 501 units in root surface space.
+ //
+
+ scoped_ptr<LayerImpl> root = CreateAndSetUpTestTreeWithOneSurface();
+ LayerImpl* child = root->children()[0];
+
+ gfx::Transform transform;
+ transform.Translate3d(500.0, 500.0, 0.0);
+ transform.ApplyPerspectiveDepth(1.0);
+ transform.RotateAboutYAxis(45.0);
+ transform.Translate3d(-50.0, -50.0, 0.0);
+
+ // Set up the child
+ child->SetPosition(gfx::PointF(0.f, 0.f));
+ child->SetBounds(gfx::Size(100, 100));
+ child->SetContentBounds(gfx::Size(100, 100));
+ child->SetTransform(transform);
+ EmulateDrawingOneFrame(root.get());
+
+ // Sanity check that the child layer's bounds would actually get clipped by
+ // w < 0, otherwise this test is not actually testing the intended scenario.
+ gfx::QuadF test_quad(gfx::RectF(gfx::PointF(), gfx::SizeF(100.f, 100.f)));
+ bool clipped = false;
+ MathUtil::MapQuad(transform, test_quad, &clipped);
+ EXPECT_TRUE(clipped);
+
+ // Damage the child without moving it.
+ ClearDamageForAllSurfaces(root.get());
+ child->SetOpacity(0.5f);
+ EmulateDrawingOneFrame(root.get());
+
+ // The expected damage should cover the entire root surface (500x500), but we
+ // don't care whether the damage rect was clamped or is larger than the
+ // surface for this test.
+ gfx::RectF root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ gfx::RectF damage_we_care_about =
+ gfx::RectF(gfx::PointF(), gfx::SizeF(500.f, 500.f));
+ EXPECT_TRUE(root_damage_rect.Contains(damage_we_care_about));
+}
+
+TEST_F(DamageTrackerTest, VerifyDamageForBlurredSurface) {
+ scoped_ptr<LayerImpl> root = CreateAndSetUpTestTreeWithTwoSurfaces();
+ LayerImpl* surface = root->children()[0];
+ LayerImpl* child = surface->children()[0];
+
+ FilterOperations filters;
+ filters.Append(FilterOperation::CreateBlurFilter(5.f));
+ int outset_top, outset_right, outset_bottom, outset_left;
+ filters.GetOutsets(&outset_top, &outset_right, &outset_bottom, &outset_left);
+
+ // Setting the filter will damage the whole surface.
+ ClearDamageForAllSurfaces(root.get());
+ surface->SetFilters(filters);
+ EmulateDrawingOneFrame(root.get());
+
+ // Setting the update rect should cause the corresponding damage to the
+ // surface, blurred based on the size of the blur filter.
+ ClearDamageForAllSurfaces(root.get());
+ child->set_update_rect(gfx::RectF(1.f, 2.f, 3.f, 4.f));
+ EmulateDrawingOneFrame(root.get());
+
+ // Damage position on the surface should be: position of update_rect (1, 2)
+ // relative to the child (300, 300), but expanded by the blur outsets.
+ gfx::RectF root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ gfx::RectF expected_damage_rect =
+ gfx::RectF(301.f, 302.f, 3.f, 4.f);
+
+ expected_damage_rect.Inset(-outset_left,
+ -outset_top,
+ -outset_right,
+ -outset_bottom);
+ EXPECT_FLOAT_RECT_EQ(expected_damage_rect, root_damage_rect);
+}
+
+TEST_F(DamageTrackerTest, VerifyDamageForImageFilter) {
+ scoped_ptr<LayerImpl> root = CreateAndSetUpTestTreeWithOneSurface();
+ LayerImpl* child = root->children()[0];
+ gfx::RectF root_damage_rect, child_damage_rect;
+
+ // Allow us to set damage on child too.
+ child->SetDrawsContent(true);
+
+ skia::RefPtr<SkImageFilter> filter =
+ skia::AdoptRef(new SkBlurImageFilter(SkIntToScalar(2),
+ SkIntToScalar(2)));
+
+ // Setting the filter will damage the whole surface.
+ ClearDamageForAllSurfaces(root.get());
+ child->SetFilter(filter);
+ EmulateDrawingOneFrame(root.get());
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ child_damage_rect =
+ child->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(100.f, 100.f, 30.f, 30.f), root_damage_rect);
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(0.f, 0.f, 30.f, 30.f), child_damage_rect);
+
+ // CASE 1: Setting the update rect should damage the whole surface (for now)
+ ClearDamageForAllSurfaces(root.get());
+ child->set_update_rect(gfx::RectF(0.f, 0.f, 1.f, 1.f));
+ EmulateDrawingOneFrame(root.get());
+
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ child_damage_rect =
+ child->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(100.f, 100.f, 30.f, 30.f), root_damage_rect);
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(0.f, 0.f, 30.f, 30.f), child_damage_rect);
+}
+
+TEST_F(DamageTrackerTest, VerifyDamageForBackgroundBlurredChild) {
+ scoped_ptr<LayerImpl> root = CreateAndSetUpTestTreeWithTwoSurfaces();
+ LayerImpl* child1 = root->children()[0];
+ LayerImpl* child2 = root->children()[1];
+
+ // Allow us to set damage on child1 too.
+ child1->SetDrawsContent(true);
+
+ FilterOperations filters;
+ filters.Append(FilterOperation::CreateBlurFilter(2.f));
+ int outset_top, outset_right, outset_bottom, outset_left;
+ filters.GetOutsets(&outset_top, &outset_right, &outset_bottom, &outset_left);
+
+ // Setting the filter will damage the whole surface.
+ ClearDamageForAllSurfaces(root.get());
+ child1->SetBackgroundFilters(filters);
+ EmulateDrawingOneFrame(root.get());
+
+ // CASE 1: Setting the update rect should cause the corresponding damage to
+ // the surface, blurred based on the size of the child's background
+ // blur filter.
+ ClearDamageForAllSurfaces(root.get());
+ root->set_update_rect(gfx::RectF(297.f, 297.f, 2.f, 2.f));
+ EmulateDrawingOneFrame(root.get());
+
+ gfx::RectF root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ // Damage position on the surface should be a composition of the damage on
+ // the root and on child2. Damage on the root should be: position of
+ // update_rect (297, 297), but expanded by the blur outsets.
+ gfx::RectF expected_damage_rect =
+ gfx::RectF(297.f, 297.f, 2.f, 2.f);
+
+ expected_damage_rect.Inset(-outset_left,
+ -outset_top,
+ -outset_right,
+ -outset_bottom);
+ EXPECT_FLOAT_RECT_EQ(expected_damage_rect, root_damage_rect);
+
+ // CASE 2: Setting the update rect should cause the corresponding damage to
+ // the surface, blurred based on the size of the child's background
+ // blur filter. Since the damage extends to the right/bottom outside
+ // of the blurred layer, only the left/top should end up expanded.
+ ClearDamageForAllSurfaces(root.get());
+ root->set_update_rect(gfx::RectF(297.f, 297.f, 30.f, 30.f));
+ EmulateDrawingOneFrame(root.get());
+
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ // Damage position on the surface should be a composition of the damage on
+ // the root and on child2. Damage on the root should be: position of
+ // update_rect (297, 297), but expanded on the left/top by the blur outsets.
+ expected_damage_rect =
+ gfx::RectF(297.f, 297.f, 30.f, 30.f);
+
+ expected_damage_rect.Inset(-outset_left,
+ -outset_top,
+ 0,
+ 0);
+ EXPECT_FLOAT_RECT_EQ(expected_damage_rect, root_damage_rect);
+
+ // CASE 3: Setting this update rect outside the blurred content_bounds of the
+ // blurred child1 will not cause it to be expanded.
+ ClearDamageForAllSurfaces(root.get());
+ root->set_update_rect(gfx::RectF(30.f, 30.f, 2.f, 2.f));
+ EmulateDrawingOneFrame(root.get());
+
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ // Damage on the root should be: position of update_rect (30, 30), not
+ // expanded.
+ expected_damage_rect =
+ gfx::RectF(30.f, 30.f, 2.f, 2.f);
+
+ EXPECT_FLOAT_RECT_EQ(expected_damage_rect, root_damage_rect);
+
+ // CASE 4: Setting this update rect inside the blurred content_bounds but
+ // outside the original content_bounds of the blurred child1 will
+ // cause it to be expanded.
+ ClearDamageForAllSurfaces(root.get());
+ root->set_update_rect(gfx::RectF(99.f, 99.f, 1.f, 1.f));
+ EmulateDrawingOneFrame(root.get());
+
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ // Damage on the root should be: position of update_rect (99, 99), expanded by
+ // the blurring on child1, but since it is 1 pixel outside the layer, the
+ // expanding should be reduced by 1.
+ expected_damage_rect =
+ gfx::RectF(99.f, 99.f, 1.f, 1.f);
+
+ expected_damage_rect.Inset(-outset_left + 1,
+ -outset_top + 1,
+ -outset_right,
+ -outset_bottom);
+ EXPECT_FLOAT_RECT_EQ(expected_damage_rect, root_damage_rect);
+
+ // CASE 5: Setting the update rect on child2, which is above child1, will
+ // not get blurred by child1, so it does not need to get expanded.
+ ClearDamageForAllSurfaces(root.get());
+ child2->set_update_rect(gfx::RectF(0.f, 0.f, 1.f, 1.f));
+ EmulateDrawingOneFrame(root.get());
+
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ // Damage on child2 should be: position of update_rect offset by the child's
+ // position (11, 11), and not expanded by anything.
+ expected_damage_rect =
+ gfx::RectF(11.f, 11.f, 1.f, 1.f);
+
+ EXPECT_FLOAT_RECT_EQ(expected_damage_rect, root_damage_rect);
+
+ // CASE 6: Setting the update rect on child1 will also blur the damage, so
+ // that any pixels needed for the blur are redrawn in the current
+ // frame.
+ ClearDamageForAllSurfaces(root.get());
+ child1->set_update_rect(gfx::RectF(0.f, 0.f, 1.f, 1.f));
+ EmulateDrawingOneFrame(root.get());
+
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ // Damage on child1 should be: position of update_rect offset by the child's
+ // position (100, 100), and expanded by the damage.
+ expected_damage_rect =
+ gfx::RectF(100.f, 100.f, 1.f, 1.f);
+
+ expected_damage_rect.Inset(-outset_left,
+ -outset_top,
+ -outset_right,
+ -outset_bottom);
+ EXPECT_FLOAT_RECT_EQ(expected_damage_rect, root_damage_rect);
+}
+
+TEST_F(DamageTrackerTest, VerifyDamageForAddingAndRemovingLayer) {
+ scoped_ptr<LayerImpl> root = CreateAndSetUpTestTreeWithOneSurface();
+ LayerImpl* child1 = root->children()[0];
+
+ // CASE 1: Adding a new layer should cause the appropriate damage.
+ //
+ ClearDamageForAllSurfaces(root.get());
+ {
+ scoped_ptr<LayerImpl> child2 =
+ LayerImpl::Create(host_impl_.active_tree(), 3);
+ child2->SetPosition(gfx::PointF(400.f, 380.f));
+ child2->SetAnchorPoint(gfx::PointF());
+ child2->SetBounds(gfx::Size(6, 8));
+ child2->SetContentBounds(gfx::Size(6, 8));
+ child2->SetDrawsContent(true);
+ root->AddChild(child2.Pass());
+ }
+ EmulateDrawingOneFrame(root.get());
+
+ // Sanity check - all 3 layers should be on the same render surface; render
+ // surfaces are tested elsewhere.
+ ASSERT_EQ(3u, root->render_surface()->layer_list().size());
+
+ gfx::RectF root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(400.f, 380.f, 6.f, 8.f), root_damage_rect);
+
+ // CASE 2: If the layer is removed, its entire old layer becomes exposed, not
+ // just the last update rect.
+
+ // Advance one frame without damage so that we know the damage rect is not
+ // leftover from the previous case.
+ ClearDamageForAllSurfaces(root.get());
+ EmulateDrawingOneFrame(root.get());
+
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_TRUE(root_damage_rect.IsEmpty());
+
+ // Then, test removing child1.
+ root->RemoveChild(child1);
+ child1 = NULL;
+ EmulateDrawingOneFrame(root.get());
+
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(100.f, 100.f, 30.f, 30.f), root_damage_rect);
+}
+
+TEST_F(DamageTrackerTest, VerifyDamageForNewUnchangedLayer) {
+ // If child2 is added to the layer tree, but it doesn't have any explicit
+ // damage of its own, it should still indeed damage the target surface.
+
+ scoped_ptr<LayerImpl> root = CreateAndSetUpTestTreeWithOneSurface();
+
+ ClearDamageForAllSurfaces(root.get());
+ {
+ scoped_ptr<LayerImpl> child2 =
+ LayerImpl::Create(host_impl_.active_tree(), 3);
+ child2->SetPosition(gfx::PointF(400.f, 380.f));
+ child2->SetAnchorPoint(gfx::PointF());
+ child2->SetBounds(gfx::Size(6, 8));
+ child2->SetContentBounds(gfx::Size(6, 8));
+ child2->SetDrawsContent(true);
+ child2->ResetAllChangeTrackingForSubtree();
+ // Sanity check the initial conditions of the test, if these asserts
+ // trigger, it means the test no longer actually covers the intended
+ // scenario.
+ ASSERT_FALSE(child2->LayerPropertyChanged());
+ ASSERT_TRUE(child2->update_rect().IsEmpty());
+ root->AddChild(child2.Pass());
+ }
+ EmulateDrawingOneFrame(root.get());
+
+ // Sanity check - all 3 layers should be on the same render surface; render
+ // surfaces are tested elsewhere.
+ ASSERT_EQ(3u, root->render_surface()->layer_list().size());
+
+ gfx::RectF root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(400.f, 380.f, 6.f, 8.f), root_damage_rect);
+}
+
+TEST_F(DamageTrackerTest, VerifyDamageForMultipleLayers) {
+ scoped_ptr<LayerImpl> root = CreateAndSetUpTestTreeWithOneSurface();
+ LayerImpl* child1 = root->children()[0];
+
+ // In this test we don't want the above tree manipulation to be considered
+ // part of the same frame.
+ ClearDamageForAllSurfaces(root.get());
+ {
+ scoped_ptr<LayerImpl> child2 =
+ LayerImpl::Create(host_impl_.active_tree(), 3);
+ child2->SetPosition(gfx::PointF(400.f, 380.f));
+ child2->SetAnchorPoint(gfx::PointF());
+ child2->SetBounds(gfx::Size(6, 8));
+ child2->SetContentBounds(gfx::Size(6, 8));
+ child2->SetDrawsContent(true);
+ root->AddChild(child2.Pass());
+ }
+ LayerImpl* child2 = root->children()[1];
+ EmulateDrawingOneFrame(root.get());
+
+ // Damaging two layers simultaneously should cause combined damage.
+ // - child1 update rect in surface space: gfx::RectF(100.f, 100.f, 1.f, 2.f);
+ // - child2 update rect in surface space: gfx::RectF(400.f, 380.f, 3.f, 4.f);
+ ClearDamageForAllSurfaces(root.get());
+ child1->set_update_rect(gfx::RectF(0.f, 0.f, 1.f, 2.f));
+ child2->set_update_rect(gfx::RectF(0.f, 0.f, 3.f, 4.f));
+ EmulateDrawingOneFrame(root.get());
+ gfx::RectF root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_FLOAT_RECT_EQ(
+ gfx::RectF(100.f, 100.f, 303.f, 284.f), root_damage_rect);
+}
+
+TEST_F(DamageTrackerTest, VerifyDamageForNestedSurfaces) {
+ scoped_ptr<LayerImpl> root = CreateAndSetUpTestTreeWithTwoSurfaces();
+ LayerImpl* child1 = root->children()[0];
+ LayerImpl* child2 = root->children()[1];
+ LayerImpl* grand_child1 = root->children()[0]->children()[0];
+ gfx::RectF child_damage_rect;
+ gfx::RectF root_damage_rect;
+
+ // CASE 1: Damage to a descendant surface should propagate properly to
+ // ancestor surface.
+ ClearDamageForAllSurfaces(root.get());
+ grand_child1->SetOpacity(0.5f);
+ EmulateDrawingOneFrame(root.get());
+ child_damage_rect =
+ child1->render_surface()->damage_tracker()->current_damage_rect();
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(200.f, 200.f, 6.f, 8.f), child_damage_rect);
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(300.f, 300.f, 6.f, 8.f), root_damage_rect);
+
+ // CASE 2: Same as previous case, but with additional damage elsewhere that
+ // should be properly unioned.
+ // - child1 surface damage in root surface space:
+ // gfx::RectF(300.f, 300.f, 6.f, 8.f);
+ // - child2 damage in root surface space:
+ // gfx::RectF(11.f, 11.f, 18.f, 18.f);
+ ClearDamageForAllSurfaces(root.get());
+ grand_child1->SetOpacity(0.7f);
+ child2->SetOpacity(0.7f);
+ EmulateDrawingOneFrame(root.get());
+ child_damage_rect =
+ child1->render_surface()->damage_tracker()->current_damage_rect();
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(200.f, 200.f, 6.f, 8.f), child_damage_rect);
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(11.f, 11.f, 295.f, 297.f), root_damage_rect);
+}
+
+TEST_F(DamageTrackerTest, VerifyDamageForSurfaceChangeFromDescendantLayer) {
+ // If descendant layer changes and affects the content bounds of the render
+ // surface, then the entire descendant surface should be damaged, and it
+ // should damage its ancestor surface with the old and new surface regions.
+
+ // This is a tricky case, since only the first grand_child changes, but the
+ // entire surface should be marked dirty.
+
+ scoped_ptr<LayerImpl> root = CreateAndSetUpTestTreeWithTwoSurfaces();
+ LayerImpl* child1 = root->children()[0];
+ LayerImpl* grand_child1 = root->children()[0]->children()[0];
+ gfx::RectF child_damage_rect;
+ gfx::RectF root_damage_rect;
+
+ ClearDamageForAllSurfaces(root.get());
+ grand_child1->SetPosition(gfx::PointF(195.f, 205.f));
+ EmulateDrawingOneFrame(root.get());
+ child_damage_rect =
+ child1->render_surface()->damage_tracker()->current_damage_rect();
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+
+ // The new surface bounds should be damaged entirely, even though only one of
+ // the layers changed.
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(190.f, 190.f, 11.f, 23.f), child_damage_rect);
+
+ // Damage to the root surface should be the union of child1's *entire* render
+ // surface (in target space), and its old exposed area (also in target
+ // space).
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(290.f, 290.f, 16.f, 23.f), root_damage_rect);
+}
+
+TEST_F(DamageTrackerTest, VerifyDamageForSurfaceChangeFromAncestorLayer) {
+ // An ancestor/owning layer changes that affects the position/transform of
+ // the render surface. Note that in this case, the layer_property_changed flag
+ // already propagates to the subtree (tested in LayerImpltest), which damages
+ // the entire child1 surface, but the damage tracker still needs the correct
+ // logic to compute the exposed region on the root surface.
+
+ // TODO(shawnsingh): the expectations of this test case should change when we
+ // add support for a unique scissor_rect per RenderSurface. In that case, the
+ // child1 surface should be completely unchanged, since we are only
+ // transforming it, while the root surface would be damaged appropriately.
+
+ scoped_ptr<LayerImpl> root = CreateAndSetUpTestTreeWithTwoSurfaces();
+ LayerImpl* child1 = root->children()[0];
+ gfx::RectF child_damage_rect;
+ gfx::RectF root_damage_rect;
+
+ ClearDamageForAllSurfaces(root.get());
+ child1->SetPosition(gfx::PointF(50.f, 50.f));
+ EmulateDrawingOneFrame(root.get());
+ child_damage_rect =
+ child1->render_surface()->damage_tracker()->current_damage_rect();
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+
+ // The new surface bounds should be damaged entirely.
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(190.f, 190.f, 16.f, 18.f), child_damage_rect);
+
+ // The entire child1 surface and the old exposed child1 surface should damage
+ // the root surface.
+ // - old child1 surface in target space: gfx::RectF(290.f, 290.f, 16.f, 18.f)
+ // - new child1 surface in target space: gfx::RectF(240.f, 240.f, 16.f, 18.f)
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(240.f, 240.f, 66.f, 68.f), root_damage_rect);
+}
+
+TEST_F(DamageTrackerTest, VerifyDamageForAddingAndRemovingRenderSurfaces) {
+ scoped_ptr<LayerImpl> root = CreateAndSetUpTestTreeWithTwoSurfaces();
+ LayerImpl* child1 = root->children()[0];
+ gfx::RectF child_damage_rect;
+ gfx::RectF root_damage_rect;
+
+ // CASE 1: If a descendant surface disappears, its entire old area becomes
+ // exposed.
+ ClearDamageForAllSurfaces(root.get());
+ child1->SetOpacity(1.f);
+ child1->SetForceRenderSurface(false);
+ EmulateDrawingOneFrame(root.get());
+
+ // Sanity check that there is only one surface now.
+ ASSERT_FALSE(child1->render_surface());
+ ASSERT_EQ(4u, root->render_surface()->layer_list().size());
+
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(290.f, 290.f, 16.f, 18.f), root_damage_rect);
+
+ // CASE 2: If a descendant surface appears, its entire old area becomes
+ // exposed.
+
+ // Cycle one frame of no change, just to sanity check that the next rect is
+ // not because of the old damage state.
+ ClearDamageForAllSurfaces(root.get());
+ EmulateDrawingOneFrame(root.get());
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_TRUE(root_damage_rect.IsEmpty());
+
+ // Then change the tree so that the render surface is added back.
+ ClearDamageForAllSurfaces(root.get());
+ child1->SetOpacity(0.5f);
+ child1->SetForceRenderSurface(true);
+ EmulateDrawingOneFrame(root.get());
+
+ // Sanity check that there is a new surface now.
+ ASSERT_TRUE(child1->render_surface());
+ EXPECT_EQ(3u, root->render_surface()->layer_list().size());
+ EXPECT_EQ(2u, child1->render_surface()->layer_list().size());
+
+ child_damage_rect =
+ child1->render_surface()->damage_tracker()->current_damage_rect();
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(190.f, 190.f, 16.f, 18.f), child_damage_rect);
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(290.f, 290.f, 16.f, 18.f), root_damage_rect);
+}
+
+TEST_F(DamageTrackerTest, VerifyNoDamageWhenNothingChanged) {
+ scoped_ptr<LayerImpl> root = CreateAndSetUpTestTreeWithTwoSurfaces();
+ LayerImpl* child1 = root->children()[0];
+ gfx::RectF child_damage_rect;
+ gfx::RectF root_damage_rect;
+
+ // CASE 1: If nothing changes, the damage rect should be empty.
+ //
+ ClearDamageForAllSurfaces(root.get());
+ EmulateDrawingOneFrame(root.get());
+ child_damage_rect =
+ child1->render_surface()->damage_tracker()->current_damage_rect();
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_TRUE(child_damage_rect.IsEmpty());
+ EXPECT_TRUE(root_damage_rect.IsEmpty());
+
+ // CASE 2: If nothing changes twice in a row, the damage rect should still be
+ // empty.
+ //
+ ClearDamageForAllSurfaces(root.get());
+ EmulateDrawingOneFrame(root.get());
+ child_damage_rect =
+ child1->render_surface()->damage_tracker()->current_damage_rect();
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_TRUE(child_damage_rect.IsEmpty());
+ EXPECT_TRUE(root_damage_rect.IsEmpty());
+}
+
+TEST_F(DamageTrackerTest, VerifyNoDamageForUpdateRectThatDoesNotDrawContent) {
+ scoped_ptr<LayerImpl> root = CreateAndSetUpTestTreeWithTwoSurfaces();
+ LayerImpl* child1 = root->children()[0];
+ gfx::RectF child_damage_rect;
+ gfx::RectF root_damage_rect;
+
+ // In our specific tree, the update rect of child1 should not cause any
+ // damage to any surface because it does not actually draw content.
+ ClearDamageForAllSurfaces(root.get());
+ child1->set_update_rect(gfx::RectF(0.f, 0.f, 1.f, 2.f));
+ EmulateDrawingOneFrame(root.get());
+ child_damage_rect =
+ child1->render_surface()->damage_tracker()->current_damage_rect();
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_TRUE(child_damage_rect.IsEmpty());
+ EXPECT_TRUE(root_damage_rect.IsEmpty());
+}
+
+TEST_F(DamageTrackerTest, VerifyDamageForReplica) {
+ scoped_ptr<LayerImpl> root = CreateAndSetUpTestTreeWithTwoSurfaces();
+ LayerImpl* child1 = root->children()[0];
+ LayerImpl* grand_child1 = child1->children()[0];
+ LayerImpl* grand_child2 = child1->children()[1];
+
+ // Damage on a surface that has a reflection should cause the target surface
+ // to receive the surface's damage and the surface's reflected damage.
+
+ // For this test case, we modify grand_child2, and add grand_child3 to extend
+ // the bounds of child1's surface. This way, we can test reflection changes
+ // without changing content_bounds of the surface.
+ grand_child2->SetPosition(gfx::PointF(180.f, 180.f));
+ {
+ scoped_ptr<LayerImpl> grand_child3 =
+ LayerImpl::Create(host_impl_.active_tree(), 6);
+ grand_child3->SetPosition(gfx::PointF(240.f, 240.f));
+ grand_child3->SetAnchorPoint(gfx::PointF());
+ grand_child3->SetBounds(gfx::Size(10, 10));
+ grand_child3->SetContentBounds(gfx::Size(10, 10));
+ grand_child3->SetDrawsContent(true);
+ child1->AddChild(grand_child3.Pass());
+ }
+ child1->SetOpacity(0.5f);
+ EmulateDrawingOneFrame(root.get());
+
+ // CASE 1: adding a reflection about the left edge of grand_child1.
+ //
+ ClearDamageForAllSurfaces(root.get());
+ {
+ scoped_ptr<LayerImpl> grand_child1_replica =
+ LayerImpl::Create(host_impl_.active_tree(), 7);
+ grand_child1_replica->SetPosition(gfx::PointF());
+ grand_child1_replica->SetAnchorPoint(gfx::PointF());
+ gfx::Transform reflection;
+ reflection.Scale3d(-1.0, 1.0, 1.0);
+ grand_child1_replica->SetTransform(reflection);
+ grand_child1->SetReplicaLayer(grand_child1_replica.Pass());
+ }
+ EmulateDrawingOneFrame(root.get());
+
+ gfx::RectF grand_child_damage_rect =
+ grand_child1->render_surface()->damage_tracker()->
+ current_damage_rect();
+ gfx::RectF child_damage_rect =
+ child1->render_surface()->damage_tracker()->current_damage_rect();
+ gfx::RectF root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+
+ // The grand_child surface damage should not include its own replica. The
+ // child surface damage should include the normal and replica surfaces.
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(0.f, 0.f, 6.f, 8.f), grand_child_damage_rect);
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(194.f, 200.f, 12.f, 8.f), child_damage_rect);
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(294.f, 300.f, 12.f, 8.f), root_damage_rect);
+
+ // CASE 2: moving the descendant surface should cause both the original and
+ // reflected areas to be damaged on the target.
+ ClearDamageForAllSurfaces(root.get());
+ gfx::Rect old_content_rect = child1->render_surface()->content_rect();
+ grand_child1->SetPosition(gfx::PointF(195.f, 205.f));
+ EmulateDrawingOneFrame(root.get());
+ ASSERT_EQ(old_content_rect.width(),
+ child1->render_surface()->content_rect().width());
+ ASSERT_EQ(old_content_rect.height(),
+ child1->render_surface()->content_rect().height());
+
+ grand_child_damage_rect =
+ grand_child1->render_surface()->
+ damage_tracker()->current_damage_rect();
+ child_damage_rect =
+ child1->render_surface()->damage_tracker()->current_damage_rect();
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+
+ // The child surface damage should include normal and replica surfaces for
+ // both old and new locations.
+ // - old location in target space: gfx::RectF(194.f, 200.f, 12.f, 8.f)
+ // - new location in target space: gfx::RectF(189.f, 205.f, 12.f, 8.f)
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(0.f, 0.f, 6.f, 8.f), grand_child_damage_rect);
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(189.f, 200.f, 17.f, 13.f), child_damage_rect);
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(289.f, 300.f, 17.f, 13.f), root_damage_rect);
+
+ // CASE 3: removing the reflection should cause the entire region including
+ // reflection to damage the target surface.
+ ClearDamageForAllSurfaces(root.get());
+ grand_child1->SetReplicaLayer(scoped_ptr<LayerImpl>());
+ EmulateDrawingOneFrame(root.get());
+ ASSERT_EQ(old_content_rect.width(),
+ child1->render_surface()->content_rect().width());
+ ASSERT_EQ(old_content_rect.height(),
+ child1->render_surface()->content_rect().height());
+
+ EXPECT_FALSE(grand_child1->render_surface());
+ child_damage_rect =
+ child1->render_surface()->damage_tracker()->current_damage_rect();
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(189.f, 205.f, 12.f, 8.f), child_damage_rect);
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(289.f, 305.f, 12.f, 8.f), root_damage_rect);
+}
+
+TEST_F(DamageTrackerTest, VerifyDamageForMask) {
+ scoped_ptr<LayerImpl> root = CreateAndSetUpTestTreeWithOneSurface();
+ LayerImpl* child = root->children()[0];
+
+ // In the current implementation of the damage tracker, changes to mask
+ // layers should damage the entire corresponding surface.
+
+ ClearDamageForAllSurfaces(root.get());
+
+ // Set up the mask layer.
+ {
+ scoped_ptr<LayerImpl> mask_layer =
+ LayerImpl::Create(host_impl_.active_tree(), 3);
+ mask_layer->SetPosition(child->position());
+ mask_layer->SetAnchorPoint(gfx::PointF());
+ mask_layer->SetBounds(child->bounds());
+ mask_layer->SetContentBounds(child->bounds());
+ child->SetMaskLayer(mask_layer.Pass());
+ }
+ LayerImpl* mask_layer = child->mask_layer();
+
+ // Add opacity and a grand_child so that the render surface persists even
+ // after we remove the mask.
+ child->SetOpacity(0.5f);
+ {
+ scoped_ptr<LayerImpl> grand_child =
+ LayerImpl::Create(host_impl_.active_tree(), 4);
+ grand_child->SetPosition(gfx::PointF(2.f, 2.f));
+ grand_child->SetAnchorPoint(gfx::PointF());
+ grand_child->SetBounds(gfx::Size(2, 2));
+ grand_child->SetContentBounds(gfx::Size(2, 2));
+ grand_child->SetDrawsContent(true);
+ child->AddChild(grand_child.Pass());
+ }
+ EmulateDrawingOneFrame(root.get());
+
+ // Sanity check that a new surface was created for the child.
+ ASSERT_TRUE(child->render_surface());
+
+ // CASE 1: the update_rect on a mask layer should damage the entire target
+ // surface.
+ ClearDamageForAllSurfaces(root.get());
+ mask_layer->set_update_rect(gfx::RectF(1.f, 2.f, 3.f, 4.f));
+ EmulateDrawingOneFrame(root.get());
+ gfx::RectF child_damage_rect =
+ child->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(0.f, 0.f, 30.f, 30.f), child_damage_rect);
+
+ // CASE 2: a property change on the mask layer should damage the entire
+ // target surface.
+
+ // Advance one frame without damage so that we know the damage rect is not
+ // leftover from the previous case.
+ ClearDamageForAllSurfaces(root.get());
+ EmulateDrawingOneFrame(root.get());
+ child_damage_rect =
+ child->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_TRUE(child_damage_rect.IsEmpty());
+
+ // Then test the property change.
+ ClearDamageForAllSurfaces(root.get());
+ mask_layer->SetStackingOrderChanged(true);
+
+ EmulateDrawingOneFrame(root.get());
+ child_damage_rect =
+ child->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(0.f, 0.f, 30.f, 30.f), child_damage_rect);
+
+ // CASE 3: removing the mask also damages the entire target surface.
+ //
+
+ // Advance one frame without damage so that we know the damage rect is not
+ // leftover from the previous case.
+ ClearDamageForAllSurfaces(root.get());
+ EmulateDrawingOneFrame(root.get());
+ child_damage_rect =
+ child->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_TRUE(child_damage_rect.IsEmpty());
+
+ // Then test mask removal.
+ ClearDamageForAllSurfaces(root.get());
+ child->SetMaskLayer(scoped_ptr<LayerImpl>());
+ ASSERT_TRUE(child->LayerPropertyChanged());
+ EmulateDrawingOneFrame(root.get());
+
+ // Sanity check that a render surface still exists.
+ ASSERT_TRUE(child->render_surface());
+
+ child_damage_rect =
+ child->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(0.f, 0.f, 30.f, 30.f), child_damage_rect);
+}
+
+TEST_F(DamageTrackerTest, VerifyDamageForReplicaMask) {
+ scoped_ptr<LayerImpl> root = CreateAndSetUpTestTreeWithTwoSurfaces();
+ LayerImpl* child1 = root->children()[0];
+ LayerImpl* grand_child1 = child1->children()[0];
+
+ // Changes to a replica's mask should not damage the original surface,
+ // because it is not masked. But it does damage the ancestor target surface.
+
+ ClearDamageForAllSurfaces(root.get());
+
+ // Create a reflection about the left edge of grand_child1.
+ {
+ scoped_ptr<LayerImpl> grand_child1_replica =
+ LayerImpl::Create(host_impl_.active_tree(), 6);
+ grand_child1_replica->SetPosition(gfx::PointF());
+ grand_child1_replica->SetAnchorPoint(gfx::PointF());
+ gfx::Transform reflection;
+ reflection.Scale3d(-1.0, 1.0, 1.0);
+ grand_child1_replica->SetTransform(reflection);
+ grand_child1->SetReplicaLayer(grand_child1_replica.Pass());
+ }
+ LayerImpl* grand_child1_replica = grand_child1->replica_layer();
+
+ // Set up the mask layer on the replica layer
+ {
+ scoped_ptr<LayerImpl> replica_mask_layer =
+ LayerImpl::Create(host_impl_.active_tree(), 7);
+ replica_mask_layer->SetPosition(gfx::PointF());
+ replica_mask_layer->SetAnchorPoint(gfx::PointF());
+ replica_mask_layer->SetBounds(grand_child1->bounds());
+ replica_mask_layer->SetContentBounds(grand_child1->bounds());
+ grand_child1_replica->SetMaskLayer(replica_mask_layer.Pass());
+ }
+ LayerImpl* replica_mask_layer = grand_child1_replica->mask_layer();
+
+ EmulateDrawingOneFrame(root.get());
+
+ // Sanity check that the appropriate render surfaces were created
+ ASSERT_TRUE(grand_child1->render_surface());
+
+ // CASE 1: a property change on the mask should damage only the reflected
+ // region on the target surface.
+ ClearDamageForAllSurfaces(root.get());
+ replica_mask_layer->SetStackingOrderChanged(true);
+ EmulateDrawingOneFrame(root.get());
+
+ gfx::RectF grand_child_damage_rect =
+ grand_child1->render_surface()->damage_tracker()->
+ current_damage_rect();
+ gfx::RectF child_damage_rect =
+ child1->render_surface()->damage_tracker()->current_damage_rect();
+
+ EXPECT_TRUE(grand_child_damage_rect.IsEmpty());
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(194.f, 200.f, 6.f, 8.f), child_damage_rect);
+
+ // CASE 2: removing the replica mask damages only the reflected region on the
+ // target surface.
+ //
+ ClearDamageForAllSurfaces(root.get());
+ grand_child1_replica->SetMaskLayer(scoped_ptr<LayerImpl>());
+ EmulateDrawingOneFrame(root.get());
+
+ grand_child_damage_rect =
+ grand_child1->render_surface()->damage_tracker()->
+ current_damage_rect();
+ child_damage_rect =
+ child1->render_surface()->damage_tracker()->current_damage_rect();
+
+ EXPECT_TRUE(grand_child_damage_rect.IsEmpty());
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(194.f, 200.f, 6.f, 8.f), child_damage_rect);
+}
+
+TEST_F(DamageTrackerTest, VerifyDamageForReplicaMaskWithAnchor) {
+ scoped_ptr<LayerImpl> root = CreateAndSetUpTestTreeWithTwoSurfaces();
+ LayerImpl* child1 = root->children()[0];
+ LayerImpl* grand_child1 = child1->children()[0];
+
+ // Verify that the correct replica_origin_transform is used for the
+ // replica_mask.
+ ClearDamageForAllSurfaces(root.get());
+
+ // This is not actually the anchor point being tested, but by convention its
+ // expected to be the same as the replica's anchor point.
+ grand_child1->SetAnchorPoint(gfx::PointF(1.f, 0.f));
+
+ {
+ scoped_ptr<LayerImpl> grand_child1_replica =
+ LayerImpl::Create(host_impl_.active_tree(), 6);
+ grand_child1_replica->SetPosition(gfx::PointF());
+
+ // This is the anchor being tested.
+ grand_child1_replica->SetAnchorPoint(gfx::PointF(1.f, 0.f));
+ gfx::Transform reflection;
+ reflection.Scale3d(-1.0, 1.0, 1.0);
+ grand_child1_replica->SetTransform(reflection);
+ grand_child1->SetReplicaLayer(grand_child1_replica.Pass());
+ }
+ LayerImpl* grand_child1_replica = grand_child1->replica_layer();
+
+ // Set up the mask layer on the replica layer
+ {
+ scoped_ptr<LayerImpl> replica_mask_layer =
+ LayerImpl::Create(host_impl_.active_tree(), 7);
+ replica_mask_layer->SetPosition(gfx::PointF());
+ // Note: this is not the anchor being tested.
+ replica_mask_layer->SetAnchorPoint(gfx::PointF());
+ replica_mask_layer->SetBounds(grand_child1->bounds());
+ replica_mask_layer->SetContentBounds(grand_child1->bounds());
+ grand_child1_replica->SetMaskLayer(replica_mask_layer.Pass());
+ }
+ LayerImpl* replica_mask_layer = grand_child1_replica->mask_layer();
+
+ EmulateDrawingOneFrame(root.get());
+
+ // Sanity check that the appropriate render surfaces were created
+ ASSERT_TRUE(grand_child1->render_surface());
+
+ // A property change on the replica_mask should damage the reflected region on
+ // the target surface.
+ ClearDamageForAllSurfaces(root.get());
+ replica_mask_layer->SetStackingOrderChanged(true);
+
+ EmulateDrawingOneFrame(root.get());
+
+ gfx::RectF child_damage_rect =
+ child1->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(206.f, 200.f, 6.f, 8.f), child_damage_rect);
+}
+
+TEST_F(DamageTrackerTest, DamageWhenAddedExternally) {
+ scoped_ptr<LayerImpl> root = CreateAndSetUpTestTreeWithOneSurface();
+ LayerImpl* child = root->children()[0];
+
+ // Case 1: This test ensures that when the tracker is given damage, that
+ // it is included with any other partial damage.
+ //
+ ClearDamageForAllSurfaces(root.get());
+ child->set_update_rect(gfx::RectF(10, 11, 12, 13));
+ root->render_surface()->damage_tracker()->AddDamageNextUpdate(
+ gfx::RectF(15, 16, 32, 33));
+ EmulateDrawingOneFrame(root.get());
+ gfx::RectF root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_FLOAT_RECT_EQ(
+ gfx::UnionRects(gfx::RectF(15, 16, 32, 33),
+ gfx::RectF(100+10, 100+11, 12, 13)),
+ root_damage_rect);
+
+ // Case 2: An additional sanity check that adding damage works even when
+ // nothing on the layer tree changed.
+ //
+ ClearDamageForAllSurfaces(root.get());
+ root->render_surface()->damage_tracker()->AddDamageNextUpdate(
+ gfx::RectF(30, 31, 14, 15));
+ EmulateDrawingOneFrame(root.get());
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(30, 31, 14, 15), root_damage_rect);
+}
+
+TEST_F(DamageTrackerTest, VerifyDamageForEmptyLayerList) {
+ // Though it should never happen, its a good idea to verify that the damage
+ // tracker does not crash when it receives an empty layer_list.
+
+ scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl_.active_tree(), 1);
+ root->CreateRenderSurface();
+
+ ASSERT_TRUE(root == root->render_target());
+ RenderSurfaceImpl* target_surface = root->render_surface();
+
+ LayerImplList empty_list;
+ target_surface->damage_tracker()->UpdateDamageTrackingState(
+ empty_list,
+ target_surface->OwningLayerId(),
+ false,
+ gfx::Rect(),
+ NULL,
+ FilterOperations(),
+ NULL);
+
+ gfx::RectF damage_rect =
+ target_surface->damage_tracker()->current_damage_rect();
+ EXPECT_TRUE(damage_rect.IsEmpty());
+}
+
+TEST_F(DamageTrackerTest, VerifyDamageAccumulatesUntilReset) {
+ // If damage is not cleared, it should accumulate.
+
+ scoped_ptr<LayerImpl> root = CreateAndSetUpTestTreeWithOneSurface();
+ LayerImpl* child = root->children()[0];
+
+ ClearDamageForAllSurfaces(root.get());
+ child->set_update_rect(gfx::RectF(10.f, 11.f, 1.f, 2.f));
+ EmulateDrawingOneFrame(root.get());
+
+ // Sanity check damage after the first frame; this isnt the actual test yet.
+ gfx::RectF root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(110.f, 111.f, 1.f, 2.f), root_damage_rect);
+
+ // New damage, without having cleared the previous damage, should be unioned
+ // to the previous one.
+ child->set_update_rect(gfx::RectF(20.f, 25.f, 1.f, 2.f));
+ EmulateDrawingOneFrame(root.get());
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_FLOAT_RECT_EQ(gfx::RectF(110.f, 111.f, 11.f, 16.f), root_damage_rect);
+
+ // If we notify the damage tracker that we drew the damaged area, then damage
+ // should be emptied.
+ root->render_surface()->damage_tracker()->DidDrawDamagedArea();
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_TRUE(root_damage_rect.IsEmpty());
+
+ // Damage should remain empty even after one frame, since there's yet no new
+ // damage.
+ EmulateDrawingOneFrame(root.get());
+ root_damage_rect =
+ root->render_surface()->damage_tracker()->current_damage_rect();
+ EXPECT_TRUE(root_damage_rect.IsEmpty());
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/trees/layer_sorter.cc b/chromium/cc/trees/layer_sorter.cc
new file mode 100644
index 00000000000..73fe3efcb1e
--- /dev/null
+++ b/chromium/cc/trees/layer_sorter.cc
@@ -0,0 +1,469 @@
+// Copyright 2011 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 "cc/trees/layer_sorter.h"
+
+#include <algorithm>
+#include <deque>
+#include <limits>
+#include <vector>
+
+#include "base/logging.h"
+#include "cc/base/math_util.h"
+#include "cc/layers/render_surface_impl.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+
+// This epsilon is used to determine if two layers are too close to each other
+// to be able to tell which is in front of the other. It's a relative epsilon
+// so it is robust to changes in scene scale. This value was chosen by picking
+// a value near machine epsilon and then increasing it until the flickering on
+// the test scene went away.
+const float k_layer_epsilon = 1e-4f;
+
+inline static float PerpProduct(gfx::Vector2dF u, gfx::Vector2dF v) {
+ return u.x() * v.y() - u.y() * v.x();
+}
+
+// Tests if two edges defined by their endpoints (a,b) and (c,d) intersect.
+// Returns true and the point of intersection if they do and false otherwise.
+static bool EdgeEdgeTest(gfx::PointF a,
+ gfx::PointF b,
+ gfx::PointF c,
+ gfx::PointF d,
+ gfx::PointF* r) {
+ gfx::Vector2dF u = b - a;
+ gfx::Vector2dF v = d - c;
+ gfx::Vector2dF w = a - c;
+
+ float denom = PerpProduct(u, v);
+
+ // If denom == 0 then the edges are parallel. While they could be overlapping
+ // we don't bother to check here as the we'll find their intersections from
+ // the corner to quad tests.
+ if (!denom)
+ return false;
+
+ float s = PerpProduct(v, w) / denom;
+ if (s < 0.f || s > 1.f)
+ return false;
+
+ float t = PerpProduct(u, w) / denom;
+ if (t < 0.f || t > 1.f)
+ return false;
+
+ u.Scale(s);
+ *r = a + u;
+ return true;
+}
+
+GraphNode::GraphNode(LayerImpl* layer_impl)
+ : layer(layer_impl),
+ incoming_edge_weight(0.f) {}
+
+GraphNode::~GraphNode() {}
+
+LayerSorter::LayerSorter()
+ : z_range_(0.f) {}
+
+LayerSorter::~LayerSorter() {}
+
+static float CheckFloatingPointNumericAccuracy(float a, float b) {
+ float abs_dif = std::abs(b - a);
+ float abs_max = std::max(std::abs(b), std::abs(a));
+ // Check to see if we've got a result with a reasonable amount of error.
+ return abs_dif / abs_max;
+}
+
+// Checks whether layer "a" draws on top of layer "b". The weight value returned
+// is an indication of the maximum z-depth difference between the layers or zero
+// if the layers are found to be intesecting (some features are in front and
+// some are behind).
+LayerSorter::ABCompareResult LayerSorter::CheckOverlap(LayerShape* a,
+ LayerShape* b,
+ float z_threshold,
+ float* weight) {
+ *weight = 0.f;
+
+ // Early out if the projected bounds don't overlap.
+ if (!a->projected_bounds.Intersects(b->projected_bounds))
+ return None;
+
+ gfx::PointF aPoints[4] = { a->projected_quad.p1(),
+ a->projected_quad.p2(),
+ a->projected_quad.p3(),
+ a->projected_quad.p4() };
+ gfx::PointF bPoints[4] = { b->projected_quad.p1(),
+ b->projected_quad.p2(),
+ b->projected_quad.p3(),
+ b->projected_quad.p4() };
+
+ // Make a list of points that inside both layer quad projections.
+ std::vector<gfx::PointF> overlap_points;
+
+ // Check all four corners of one layer against the other layer's quad.
+ for (int i = 0; i < 4; ++i) {
+ if (a->projected_quad.Contains(bPoints[i]))
+ overlap_points.push_back(bPoints[i]);
+ if (b->projected_quad.Contains(aPoints[i]))
+ overlap_points.push_back(aPoints[i]);
+ }
+
+ // Check all the edges of one layer for intersection with the other layer's
+ // edges.
+ gfx::PointF r;
+ for (int ea = 0; ea < 4; ++ea)
+ for (int eb = 0; eb < 4; ++eb)
+ if (EdgeEdgeTest(aPoints[ea], aPoints[(ea + 1) % 4],
+ bPoints[eb], bPoints[(eb + 1) % 4],
+ &r))
+ overlap_points.push_back(r);
+
+ if (overlap_points.empty())
+ return None;
+
+ // Check the corresponding layer depth value for all overlap points to
+ // determine which layer is in front.
+ float max_positive = 0.f;
+ float max_negative = 0.f;
+
+ // This flag tracks the existance of a numerically accurate seperation
+ // between two layers. If there is no accurate seperation, the layers
+ // cannot be effectively sorted.
+ bool accurate = false;
+
+ for (size_t o = 0; o < overlap_points.size(); o++) {
+ float za = a->LayerZFromProjectedPoint(overlap_points[o]);
+ float zb = b->LayerZFromProjectedPoint(overlap_points[o]);
+
+ // Here we attempt to avoid numeric issues with layers that are too
+ // close together. If we have 2-sided quads that are very close
+ // together then we will draw them in document order to avoid
+ // flickering. The correct solution is for the content maker to turn
+ // on back-face culling or move the quads apart (if they're not two
+ // sides of one object).
+ if (CheckFloatingPointNumericAccuracy(za, zb) > k_layer_epsilon)
+ accurate = true;
+
+ float diff = za - zb;
+ if (diff > max_positive)
+ max_positive = diff;
+ if (diff < max_negative)
+ max_negative = diff;
+ }
+
+ // If we can't tell which should come first, we use document order.
+ if (!accurate)
+ return ABeforeB;
+
+ float max_diff =
+ std::abs(max_positive) > std::abs(max_negative) ?
+ max_positive : max_negative;
+
+ // If the results are inconsistent (and the z difference substantial to rule
+ // out numerical errors) then the layers are intersecting. We will still
+ // return an order based on the maximum depth difference but with an edge
+ // weight of zero these layers will get priority if a graph cycle is present
+ // and needs to be broken.
+ if (max_positive > z_threshold && max_negative < -z_threshold)
+ *weight = 0.f;
+ else
+ *weight = std::abs(max_diff);
+
+ // Maintain relative order if the layers have the same depth at all
+ // intersection points.
+ if (max_diff <= 0.f)
+ return ABeforeB;
+
+ return BBeforeA;
+}
+
+LayerShape::LayerShape() {}
+
+LayerShape::LayerShape(float width,
+ float height,
+ const gfx::Transform& draw_transform) {
+ gfx::QuadF layer_quad(gfx::RectF(0.f, 0.f, width, height));
+
+ // Compute the projection of the layer quad onto the z = 0 plane.
+
+ gfx::PointF clipped_quad[8];
+ int num_vertices_in_clipped_quad;
+ MathUtil::MapClippedQuad(draw_transform,
+ layer_quad,
+ clipped_quad,
+ &num_vertices_in_clipped_quad);
+
+ if (num_vertices_in_clipped_quad < 3) {
+ projected_bounds = gfx::RectF();
+ return;
+ }
+
+ projected_bounds =
+ MathUtil::ComputeEnclosingRectOfVertices(clipped_quad,
+ num_vertices_in_clipped_quad);
+
+ // NOTE: it will require very significant refactoring and overhead to deal
+ // with generalized polygons or multiple quads per layer here. For the sake of
+ // layer sorting it is equally correct to take a subsection of the polygon
+ // that can be made into a quad. This will only be incorrect in the case of
+ // intersecting layers, which are not supported yet anyway.
+ projected_quad.set_p1(clipped_quad[0]);
+ projected_quad.set_p2(clipped_quad[1]);
+ projected_quad.set_p3(clipped_quad[2]);
+ if (num_vertices_in_clipped_quad >= 4) {
+ projected_quad.set_p4(clipped_quad[3]);
+ } else {
+ // This will be a degenerate quad that is actually a triangle.
+ projected_quad.set_p4(clipped_quad[2]);
+ }
+
+ // Compute the normal of the layer's plane.
+ bool clipped = false;
+ gfx::Point3F c1 =
+ MathUtil::MapPoint(draw_transform, gfx::Point3F(0.f, 0.f, 0.f), &clipped);
+ gfx::Point3F c2 =
+ MathUtil::MapPoint(draw_transform, gfx::Point3F(0.f, 1.f, 0.f), &clipped);
+ gfx::Point3F c3 =
+ MathUtil::MapPoint(draw_transform, gfx::Point3F(1.f, 0.f, 0.f), &clipped);
+ // TODO(shawnsingh): Deal with clipping.
+ gfx::Vector3dF c12 = c2 - c1;
+ gfx::Vector3dF c13 = c3 - c1;
+ layer_normal = gfx::CrossProduct(c13, c12);
+
+ transform_origin = c1;
+}
+
+LayerShape::~LayerShape() {}
+
+// Returns the Z coordinate of a point on the layer that projects
+// to point p which lies on the z = 0 plane. It does it by computing the
+// intersection of a line starting from p along the Z axis and the plane
+// of the layer.
+float LayerShape::LayerZFromProjectedPoint(gfx::PointF p) const {
+ gfx::Vector3dF z_axis(0.f, 0.f, 1.f);
+ gfx::Vector3dF w = gfx::Point3F(p) - transform_origin;
+
+ float d = gfx::DotProduct(layer_normal, z_axis);
+ float n = -gfx::DotProduct(layer_normal, w);
+
+ // Check if layer is parallel to the z = 0 axis which will make it
+ // invisible and hence returning zero is fine.
+ if (!d)
+ return 0.f;
+
+ // The intersection point would be given by:
+ // p + (n / d) * u but since we are only interested in the
+ // z coordinate and p's z coord is zero, all we need is the value of n/d.
+ return n / d;
+}
+
+void LayerSorter::CreateGraphNodes(LayerImplList::iterator first,
+ LayerImplList::iterator last) {
+ DVLOG(2) << "Creating graph nodes:";
+ float min_z = FLT_MAX;
+ float max_z = -FLT_MAX;
+ for (LayerImplList::const_iterator it = first; it < last; it++) {
+ nodes_.push_back(GraphNode(*it));
+ GraphNode& node = nodes_.at(nodes_.size() - 1);
+ RenderSurfaceImpl* render_surface = node.layer->render_surface();
+ if (!node.layer->DrawsContent() && !render_surface)
+ continue;
+
+ DVLOG(2) << "Layer " << node.layer->id() <<
+ " (" << node.layer->bounds().width() <<
+ " x " << node.layer->bounds().height() << ")";
+
+ gfx::Transform draw_transform;
+ float layer_width, layer_height;
+ if (render_surface) {
+ draw_transform = render_surface->draw_transform();
+ layer_width = render_surface->content_rect().width();
+ layer_height = render_surface->content_rect().height();
+ } else {
+ draw_transform = node.layer->draw_transform();
+ layer_width = node.layer->content_bounds().width();
+ layer_height = node.layer->content_bounds().height();
+ }
+
+ node.shape = LayerShape(layer_width, layer_height, draw_transform);
+
+ max_z = std::max(max_z, node.shape.transform_origin.z());
+ min_z = std::min(min_z, node.shape.transform_origin.z());
+ }
+
+ z_range_ = std::abs(max_z - min_z);
+}
+
+void LayerSorter::CreateGraphEdges() {
+ DVLOG(2) << "Edges:";
+ // Fraction of the total z_range below which z differences
+ // are not considered reliable.
+ const float z_threshold_factor = 0.01f;
+ float z_threshold = z_range_ * z_threshold_factor;
+
+ for (size_t na = 0; na < nodes_.size(); na++) {
+ GraphNode& node_a = nodes_[na];
+ if (!node_a.layer->DrawsContent() && !node_a.layer->render_surface())
+ continue;
+ for (size_t nb = na + 1; nb < nodes_.size(); nb++) {
+ GraphNode& node_b = nodes_[nb];
+ if (!node_b.layer->DrawsContent() && !node_b.layer->render_surface())
+ continue;
+ float weight = 0.f;
+ ABCompareResult overlap_result = CheckOverlap(&node_a.shape,
+ &node_b.shape,
+ z_threshold,
+ &weight);
+ GraphNode* start_node = NULL;
+ GraphNode* end_node = NULL;
+ if (overlap_result == ABeforeB) {
+ start_node = &node_a;
+ end_node = &node_b;
+ } else if (overlap_result == BBeforeA) {
+ start_node = &node_b;
+ end_node = &node_a;
+ }
+
+ if (start_node) {
+ DVLOG(2) << start_node->layer->id() << " -> " << end_node->layer->id();
+ edges_.push_back(GraphEdge(start_node, end_node, weight));
+ }
+ }
+ }
+
+ for (size_t i = 0; i < edges_.size(); i++) {
+ GraphEdge& edge = edges_[i];
+ active_edges_[&edge] = &edge;
+ edge.from->outgoing.push_back(&edge);
+ edge.to->incoming.push_back(&edge);
+ edge.to->incoming_edge_weight += edge.weight;
+ }
+}
+
+// Finds and removes an edge from the list by doing a swap with the
+// last element of the list.
+void LayerSorter::RemoveEdgeFromList(GraphEdge* edge,
+ std::vector<GraphEdge*>* list) {
+ std::vector<GraphEdge*>::iterator iter =
+ std::find(list->begin(), list->end(), edge);
+ DCHECK(iter != list->end());
+ list->erase(iter);
+}
+
+// Sorts the given list of layers such that they can be painted in a
+// back-to-front order. Sorting produces correct results for non-intersecting
+// layers that don't have cyclical order dependencies. Cycles and intersections
+// are broken (somewhat) aribtrarily. Sorting of layers is done via a
+// topological sort of a directed graph whose nodes are the layers themselves.
+// An edge from node A to node B signifies that layer A needs to be drawn before
+// layer B. If A and B have no dependency between each other, then we preserve
+// the ordering of those layers as they were in the original list.
+//
+// The draw order between two layers is determined by projecting the two
+// triangles making up each layer quad to the Z = 0 plane, finding points of
+// intersection between the triangles and backprojecting those points to the
+// plane of the layer to determine the corresponding Z coordinate. The layer
+// with the lower Z coordinate (farther from the eye) needs to be rendered
+// first.
+//
+// If the layer projections don't intersect, then no edges (dependencies) are
+// created between them in the graph. HOWEVER, in this case we still need to
+// preserve the ordering of the original list of layers, since that list should
+// already have proper z-index ordering of layers.
+//
+void LayerSorter::Sort(LayerImplList::iterator first,
+ LayerImplList::iterator last) {
+ DVLOG(2) << "Sorting start ----";
+ CreateGraphNodes(first, last);
+
+ CreateGraphEdges();
+
+ std::vector<GraphNode*> sorted_list;
+ std::deque<GraphNode*> no_incoming_edge_node_list;
+
+ // Find all the nodes that don't have incoming edges.
+ for (NodeList::iterator la = nodes_.begin(); la < nodes_.end(); la++) {
+ if (!la->incoming.size())
+ no_incoming_edge_node_list.push_back(&(*la));
+ }
+
+ DVLOG(2) << "Sorted list: ";
+ while (active_edges_.size() || no_incoming_edge_node_list.size()) {
+ while (no_incoming_edge_node_list.size()) {
+ // It is necessary to preserve the existing ordering of layers, when there
+ // are no explicit dependencies (because this existing ordering has
+ // correct z-index/layout ordering). To preserve this ordering, we process
+ // Nodes in the same order that they were added to the list.
+ GraphNode* from_node = no_incoming_edge_node_list.front();
+ no_incoming_edge_node_list.pop_front();
+
+ // Add it to the final list.
+ sorted_list.push_back(from_node);
+
+ DVLOG(2) << from_node->layer->id() << ", ";
+
+ // Remove all its outgoing edges from the graph.
+ for (size_t i = 0; i < from_node->outgoing.size(); i++) {
+ GraphEdge* outgoing_edge = from_node->outgoing[i];
+
+ active_edges_.erase(outgoing_edge);
+ RemoveEdgeFromList(outgoing_edge, &outgoing_edge->to->incoming);
+ outgoing_edge->to->incoming_edge_weight -= outgoing_edge->weight;
+
+ if (!outgoing_edge->to->incoming.size())
+ no_incoming_edge_node_list.push_back(outgoing_edge->to);
+ }
+ from_node->outgoing.clear();
+ }
+
+ if (!active_edges_.size())
+ break;
+
+ // If there are still active edges but the list of nodes without incoming
+ // edges is empty then we have run into a cycle. Break the cycle by finding
+ // the node with the smallest overall incoming edge weight and use it. This
+ // will favor nodes that have zero-weight incoming edges i.e. layers that
+ // are being occluded by a layer that intersects them.
+ float min_incoming_edge_weight = FLT_MAX;
+ GraphNode* next_node = NULL;
+ for (size_t i = 0; i < nodes_.size(); i++) {
+ if (nodes_[i].incoming.size() &&
+ nodes_[i].incoming_edge_weight < min_incoming_edge_weight) {
+ min_incoming_edge_weight = nodes_[i].incoming_edge_weight;
+ next_node = &nodes_[i];
+ }
+ }
+ DCHECK(next_node);
+ // Remove all its incoming edges.
+ for (size_t e = 0; e < next_node->incoming.size(); e++) {
+ GraphEdge* incoming_edge = next_node->incoming[e];
+
+ active_edges_.erase(incoming_edge);
+ RemoveEdgeFromList(incoming_edge, &incoming_edge->from->outgoing);
+ }
+ next_node->incoming.clear();
+ next_node->incoming_edge_weight = 0.f;
+ no_incoming_edge_node_list.push_back(next_node);
+ DVLOG(2) << "Breaking cycle by cleaning up incoming edges from " <<
+ next_node->layer->id() <<
+ " (weight = " << min_incoming_edge_weight << ")";
+ }
+
+ // Note: The original elements of the list are in no danger of having their
+ // ref count go to zero here as they are all nodes of the layer hierarchy and
+ // are kept alive by their parent nodes.
+ int count = 0;
+ for (LayerImplList::iterator it = first; it < last; it++)
+ *it = sorted_list[count++]->layer;
+
+ DVLOG(2) << "Sorting end ----";
+
+ nodes_.clear();
+ edges_.clear();
+ active_edges_.clear();
+}
+
+} // namespace cc
diff --git a/chromium/cc/trees/layer_sorter.h b/chromium/cc/trees/layer_sorter.h
new file mode 100644
index 00000000000..361a5c85f75
--- /dev/null
+++ b/chromium/cc/trees/layer_sorter.h
@@ -0,0 +1,114 @@
+// Copyright 2011 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.
+
+#ifndef CC_TREES_LAYER_SORTER_H_
+#define CC_TREES_LAYER_SORTER_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "cc/base/cc_export.h"
+#include "cc/layers/layer_impl.h"
+#include "ui/gfx/point3_f.h"
+#include "ui/gfx/quad_f.h"
+#include "ui/gfx/rect_f.h"
+#include "ui/gfx/vector3d_f.h"
+
+#if defined(COMPILER_GCC)
+namespace cc { struct GraphEdge; }
+
+namespace BASE_HASH_NAMESPACE {
+template <>
+struct hash<cc::GraphEdge*> {
+ size_t operator()(cc::GraphEdge* ptr) const {
+ return hash<size_t>()(reinterpret_cast<size_t>(ptr));
+ }
+};
+} // namespace BASE_HASH_NAMESPACE
+#endif // COMPILER
+
+namespace gfx {
+class Transform;
+}
+
+namespace cc {
+struct GraphEdge;
+
+// Holds various useful properties derived from a layer's 3D outline.
+struct CC_EXPORT LayerShape {
+ LayerShape();
+ LayerShape(float width, float height, const gfx::Transform& draw_transform);
+ ~LayerShape();
+
+ float LayerZFromProjectedPoint(gfx::PointF p) const;
+
+ gfx::Vector3dF layer_normal;
+ gfx::Point3F transform_origin;
+ gfx::QuadF projected_quad;
+ gfx::RectF projected_bounds;
+};
+
+struct GraphNode {
+ explicit GraphNode(LayerImpl* layer_impl);
+ ~GraphNode();
+
+ LayerImpl* layer;
+ LayerShape shape;
+ std::vector<GraphEdge*> incoming;
+ std::vector<GraphEdge*> outgoing;
+ float incoming_edge_weight;
+};
+
+struct GraphEdge {
+ GraphEdge(GraphNode* from_node, GraphNode* to_node, float weight)
+ : from(from_node),
+ to(to_node),
+ weight(weight) {}
+
+ GraphNode* from;
+ GraphNode* to;
+ float weight;
+};
+
+
+
+class CC_EXPORT LayerSorter {
+ public:
+ LayerSorter();
+ ~LayerSorter();
+
+ void Sort(LayerImplList::iterator first, LayerImplList::iterator last);
+
+ enum ABCompareResult {
+ ABeforeB,
+ BBeforeA,
+ None
+ };
+
+ static ABCompareResult CheckOverlap(LayerShape* a,
+ LayerShape* b,
+ float z_threshold,
+ float* weight);
+
+ private:
+ typedef std::vector<GraphNode> NodeList;
+ typedef std::vector<GraphEdge> EdgeList;
+ NodeList nodes_;
+ EdgeList edges_;
+ float z_range_;
+
+ typedef base::hash_map<GraphEdge*, GraphEdge*> EdgeMap;
+ EdgeMap active_edges_;
+
+ void CreateGraphNodes(LayerImplList::iterator first,
+ LayerImplList::iterator last);
+ void CreateGraphEdges();
+ void RemoveEdgeFromList(GraphEdge* graph, std::vector<GraphEdge*>* list);
+
+ DISALLOW_COPY_AND_ASSIGN(LayerSorter);
+};
+
+} // namespace cc
+#endif // CC_TREES_LAYER_SORTER_H_
diff --git a/chromium/cc/trees/layer_sorter_unittest.cc b/chromium/cc/trees/layer_sorter_unittest.cc
new file mode 100644
index 00000000000..b1d7b819fc5
--- /dev/null
+++ b/chromium/cc/trees/layer_sorter_unittest.cc
@@ -0,0 +1,326 @@
+// Copyright 2011 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 "cc/trees/layer_sorter.h"
+
+#include "cc/base/math_util.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/test/fake_impl_proxy.h"
+#include "cc/test/fake_layer_tree_host_impl.h"
+#include "cc/trees/single_thread_proxy.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+namespace {
+
+// Note: In the following overlap tests, the "camera" is looking down the
+// negative Z axis, meaning that layers with smaller z values (more negative)
+// are further from the camera and therefore must be drawn before layers with
+// higher z values.
+
+TEST(LayerSorterTest, BasicOverlap) {
+ LayerSorter::ABCompareResult overlap_result;
+ const float z_threshold = 0.1f;
+ float weight = 0.f;
+
+ // Trivial test, with one layer directly obscuring the other.
+ gfx::Transform neg4_translate;
+ neg4_translate.Translate3d(0.0, 0.0, -4.0);
+ LayerShape front(2.f, 2.f, neg4_translate);
+
+ gfx::Transform neg5_translate;
+ neg5_translate.Translate3d(0.0, 0.0, -5.0);
+ LayerShape back(2.f, 2.f, neg5_translate);
+
+ overlap_result =
+ LayerSorter::CheckOverlap(&front, &back, z_threshold, &weight);
+ EXPECT_EQ(LayerSorter::BBeforeA, overlap_result);
+ EXPECT_EQ(1.f, weight);
+
+ overlap_result =
+ LayerSorter::CheckOverlap(&back, &front, z_threshold, &weight);
+ EXPECT_EQ(LayerSorter::ABeforeB, overlap_result);
+ EXPECT_EQ(1.f, weight);
+
+ // One layer translated off to the right. No overlap should be detected.
+ gfx::Transform right_translate;
+ right_translate.Translate3d(10.0, 0.0, -5.0);
+ LayerShape back_right(2.f, 2.f, right_translate);
+ overlap_result =
+ LayerSorter::CheckOverlap(&front, &back_right, z_threshold, &weight);
+ EXPECT_EQ(LayerSorter::None, overlap_result);
+
+ // When comparing a layer with itself, z difference is always 0.
+ overlap_result =
+ LayerSorter::CheckOverlap(&front, &front, z_threshold, &weight);
+ EXPECT_EQ(0.f, weight);
+}
+
+TEST(LayerSorterTest, RightAngleOverlap) {
+ LayerSorter::ABCompareResult overlap_result;
+ const float z_threshold = 0.1f;
+ float weight = 0.f;
+
+ gfx::Transform perspective_matrix;
+ perspective_matrix.ApplyPerspectiveDepth(1000.0);
+
+ // Two layers forming a right angle with a perspective viewing transform.
+ gfx::Transform left_face_matrix;
+ left_face_matrix.Translate3d(-1.0, 0.0, -5.0);
+ left_face_matrix.RotateAboutYAxis(-90.0);
+ left_face_matrix.Translate(-1.0, -1.0);
+ LayerShape left_face(2.f, 2.f, perspective_matrix * left_face_matrix);
+ gfx::Transform front_face_matrix;
+ front_face_matrix.Translate3d(0.0, 0.0, -4.0);
+ front_face_matrix.Translate(-1.0, -1.0);
+ LayerShape front_face(2.f, 2.f, perspective_matrix * front_face_matrix);
+
+ overlap_result =
+ LayerSorter::CheckOverlap(&front_face, &left_face, z_threshold, &weight);
+ EXPECT_EQ(LayerSorter::BBeforeA, overlap_result);
+}
+
+TEST(LayerSorterTest, IntersectingLayerOverlap) {
+ LayerSorter::ABCompareResult overlap_result;
+ const float z_threshold = 0.1f;
+ float weight = 0.f;
+
+ gfx::Transform perspective_matrix;
+ perspective_matrix.ApplyPerspectiveDepth(1000.0);
+
+ // Intersecting layers. An explicit order will be returned based on relative z
+ // values at the overlapping features but the weight returned should be zero.
+ gfx::Transform front_face_matrix;
+ front_face_matrix.Translate3d(0.0, 0.0, -4.0);
+ front_face_matrix.Translate(-1.0, -1.0);
+ LayerShape front_face(2.f, 2.f, perspective_matrix * front_face_matrix);
+
+ gfx::Transform through_matrix;
+ through_matrix.Translate3d(0.0, 0.0, -4.0);
+ through_matrix.RotateAboutYAxis(45.0);
+ through_matrix.Translate(-1.0, -1.0);
+ LayerShape rotated_face(2.f, 2.f, perspective_matrix * through_matrix);
+ overlap_result = LayerSorter::CheckOverlap(&front_face,
+ &rotated_face,
+ z_threshold,
+ &weight);
+ EXPECT_NE(LayerSorter::None, overlap_result);
+ EXPECT_EQ(0.f, weight);
+}
+
+TEST(LayerSorterTest, LayersAtAngleOverlap) {
+ LayerSorter::ABCompareResult overlap_result;
+ const float z_threshold = 0.1f;
+ float weight = 0.f;
+
+ // Trickier test with layers at an angle.
+ //
+ // -x . . . . 0 . . . . +x
+ // -z /
+ // : /----B----
+ // 0 C
+ // : ----A----/
+ // +z /
+ //
+ // C is in front of A and behind B (not what you'd expect by comparing
+ // centers). A and B don't overlap, so they're incomparable.
+
+ gfx::Transform transform_a;
+ transform_a.Translate3d(-6.0, 0.0, 1.0);
+ transform_a.Translate(-4.0, -10.0);
+ LayerShape layer_a(8.f, 20.f, transform_a);
+
+ gfx::Transform transform_b;
+ transform_b.Translate3d(6.0, 0.0, -1.0);
+ transform_b.Translate(-4.0, -10.0);
+ LayerShape layer_b(8.f, 20.f, transform_b);
+
+ gfx::Transform transform_c;
+ transform_c.RotateAboutYAxis(40.0);
+ transform_c.Translate(-4.0, -10.0);
+ LayerShape layer_c(8.f, 20.f, transform_c);
+
+ overlap_result =
+ LayerSorter::CheckOverlap(&layer_a, &layer_c, z_threshold, &weight);
+ EXPECT_EQ(LayerSorter::ABeforeB, overlap_result);
+ overlap_result =
+ LayerSorter::CheckOverlap(&layer_c, &layer_b, z_threshold, &weight);
+ EXPECT_EQ(LayerSorter::ABeforeB, overlap_result);
+ overlap_result =
+ LayerSorter::CheckOverlap(&layer_a, &layer_b, z_threshold, &weight);
+ EXPECT_EQ(LayerSorter::None, overlap_result);
+}
+
+TEST(LayerSorterTest, LayersUnderPathologicalPerspectiveTransform) {
+ LayerSorter::ABCompareResult overlap_result;
+ const float z_threshold = 0.1f;
+ float weight = 0.f;
+
+ // On perspective projection, if w becomes negative, the re-projected point
+ // will be invalid and un-usable. Correct code needs to clip away portions of
+ // the geometry where w < 0. If the code uses the invalid value, it will think
+ // that a layer has different bounds than it really does, which can cause
+ // things to sort incorrectly.
+
+ gfx::Transform perspective_matrix;
+ perspective_matrix.ApplyPerspectiveDepth(1);
+
+ gfx::Transform transform_a;
+ transform_a.Translate3d(-15.0, 0.0, -2.0);
+ transform_a.Translate(-5.0, -5.0);
+ LayerShape layer_a(10.f, 10.f, perspective_matrix * transform_a);
+
+ // With this sequence of transforms, when layer B is correctly clipped, it
+ // will be visible on the left half of the projection plane, in front of
+ // layer_a. When it is not clipped, its bounds will actually incorrectly
+ // appear much smaller and the correct sorting dependency will not be found.
+ gfx::Transform transform_b;
+ transform_b.Translate3d(0.0, 0.0, 0.7);
+ transform_b.RotateAboutYAxis(45.0);
+ transform_b.Translate(-5.0, -5.0);
+ LayerShape layer_b(10.f, 10.f, perspective_matrix * transform_b);
+
+ // Sanity check that the test case actually covers the intended scenario,
+ // where part of layer B go behind the w = 0 plane.
+ gfx::QuadF test_quad = gfx::QuadF(gfx::RectF(-0.5f, -0.5f, 1.f, 1.f));
+ bool clipped = false;
+ MathUtil::MapQuad(perspective_matrix * transform_b, test_quad, &clipped);
+ ASSERT_TRUE(clipped);
+
+ overlap_result =
+ LayerSorter::CheckOverlap(&layer_a, &layer_b, z_threshold, &weight);
+ EXPECT_EQ(LayerSorter::ABeforeB, overlap_result);
+}
+
+TEST(LayerSorterTest, VerifyExistingOrderingPreservedWhenNoZDiff) {
+ // If there is no reason to re-sort the layers (i.e. no 3d z difference), then
+ // the existing ordering provided on input should be retained. This test
+ // covers the fix in https://bugs.webkit.org/show_bug.cgi?id=75046. Before
+ // this fix, ordering was accidentally reversed, causing bugs in z-index
+ // ordering on websites when preserves3D triggered the LayerSorter.
+
+ // Input list of layers: [1, 2, 3, 4, 5].
+ // Expected output: [3, 4, 1, 2, 5].
+ // - 1, 2, and 5 do not have a 3d z difference, and therefore their
+ // relative ordering should be retained.
+ // - 3 and 4 do not have a 3d z difference, and therefore their relative
+ // ordering should be retained.
+ // - 3 and 4 should be re-sorted so they are in front of 1, 2, and 5.
+
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+
+ scoped_ptr<LayerImpl> layer1 = LayerImpl::Create(host_impl.active_tree(), 1);
+ scoped_ptr<LayerImpl> layer2 = LayerImpl::Create(host_impl.active_tree(), 2);
+ scoped_ptr<LayerImpl> layer3 = LayerImpl::Create(host_impl.active_tree(), 3);
+ scoped_ptr<LayerImpl> layer4 = LayerImpl::Create(host_impl.active_tree(), 4);
+ scoped_ptr<LayerImpl> layer5 = LayerImpl::Create(host_impl.active_tree(), 5);
+
+ gfx::Transform BehindMatrix;
+ BehindMatrix.Translate3d(0.0, 0.0, 2.0);
+ gfx::Transform FrontMatrix;
+ FrontMatrix.Translate3d(0.0, 0.0, 1.0);
+
+ layer1->SetBounds(gfx::Size(10, 10));
+ layer1->SetContentBounds(gfx::Size(10, 10));
+ layer1->draw_properties().target_space_transform = BehindMatrix;
+ layer1->SetDrawsContent(true);
+
+ layer2->SetBounds(gfx::Size(20, 20));
+ layer2->SetContentBounds(gfx::Size(20, 20));
+ layer2->draw_properties().target_space_transform = BehindMatrix;
+ layer2->SetDrawsContent(true);
+
+ layer3->SetBounds(gfx::Size(30, 30));
+ layer3->SetContentBounds(gfx::Size(30, 30));
+ layer3->draw_properties().target_space_transform = FrontMatrix;
+ layer3->SetDrawsContent(true);
+
+ layer4->SetBounds(gfx::Size(40, 40));
+ layer4->SetContentBounds(gfx::Size(40, 40));
+ layer4->draw_properties().target_space_transform = FrontMatrix;
+ layer4->SetDrawsContent(true);
+
+ layer5->SetBounds(gfx::Size(50, 50));
+ layer5->SetContentBounds(gfx::Size(50, 50));
+ layer5->draw_properties().target_space_transform = BehindMatrix;
+ layer5->SetDrawsContent(true);
+
+ LayerImplList layer_list;
+ layer_list.push_back(layer1.get());
+ layer_list.push_back(layer2.get());
+ layer_list.push_back(layer3.get());
+ layer_list.push_back(layer4.get());
+ layer_list.push_back(layer5.get());
+
+ ASSERT_EQ(5u, layer_list.size());
+ EXPECT_EQ(1, layer_list[0]->id());
+ EXPECT_EQ(2, layer_list[1]->id());
+ EXPECT_EQ(3, layer_list[2]->id());
+ EXPECT_EQ(4, layer_list[3]->id());
+ EXPECT_EQ(5, layer_list[4]->id());
+
+ LayerSorter layer_sorter;
+ layer_sorter.Sort(layer_list.begin(), layer_list.end());
+
+ ASSERT_EQ(5u, layer_list.size());
+ EXPECT_EQ(3, layer_list[0]->id());
+ EXPECT_EQ(4, layer_list[1]->id());
+ EXPECT_EQ(1, layer_list[2]->id());
+ EXPECT_EQ(2, layer_list[3]->id());
+ EXPECT_EQ(5, layer_list[4]->id());
+}
+
+TEST(LayerSorterTest, VerifyConcidentLayerPrecisionLossResultsInDocumentOrder) {
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+
+ scoped_ptr<LayerImpl> layer1 = LayerImpl::Create(host_impl.active_tree(), 1);
+ scoped_ptr<LayerImpl> layer2 = LayerImpl::Create(host_impl.active_tree(), 2);
+
+ // Layer 1 should occur before layer 2 in paint. However, due to numeric
+ // issues in the sorter, it will put the layers in the wrong order
+ // in some situations. Here we test a patch that results in document
+ // order rather than calculated order when numeric percision is suspect
+ // in calculated order.
+
+ gfx::Transform BehindMatrix;
+ BehindMatrix.Translate3d(0.0, 0.0, 0.999999);
+ BehindMatrix.RotateAboutXAxis(38.5);
+ BehindMatrix.RotateAboutYAxis(77.0);
+ gfx::Transform FrontMatrix;
+ FrontMatrix.Translate3d(0, 0, 1.0);
+ FrontMatrix.RotateAboutXAxis(38.5);
+ FrontMatrix.RotateAboutYAxis(77.0);
+
+ layer1->SetBounds(gfx::Size(10, 10));
+ layer1->SetContentBounds(gfx::Size(10, 10));
+ layer1->draw_properties().target_space_transform = BehindMatrix;
+ layer1->SetDrawsContent(true);
+
+ layer2->SetBounds(gfx::Size(10, 10));
+ layer2->SetContentBounds(gfx::Size(10, 10));
+ layer2->draw_properties().target_space_transform = FrontMatrix;
+ layer2->SetDrawsContent(true);
+
+ LayerImplList layer_list;
+ layer_list.push_back(layer1.get());
+ layer_list.push_back(layer2.get());
+
+ ASSERT_EQ(2u, layer_list.size());
+ EXPECT_EQ(1, layer_list[0]->id());
+ EXPECT_EQ(2, layer_list[1]->id());
+
+ LayerSorter layer_sorter;
+ layer_sorter.Sort(layer_list.begin(), layer_list.end());
+
+ ASSERT_EQ(2u, layer_list.size());
+ EXPECT_EQ(1, layer_list[0]->id());
+ EXPECT_EQ(2, layer_list[1]->id());
+}
+
+} // namespace
+} // namespace cc
+
diff --git a/chromium/cc/trees/layer_tree_host.cc b/chromium/cc/trees/layer_tree_host.cc
new file mode 100644
index 00000000000..9956f4c7359
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_host.cc
@@ -0,0 +1,1181 @@
+// Copyright 2011 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 "cc/trees/layer_tree_host.h"
+
+#include <algorithm>
+#include <stack>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/debug/trace_event.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "cc/animation/animation_registrar.h"
+#include "cc/animation/layer_animation_controller.h"
+#include "cc/base/math_util.h"
+#include "cc/debug/benchmark_instrumentation.h"
+#include "cc/debug/devtools_instrumentation.h"
+#include "cc/debug/overdraw_metrics.h"
+#include "cc/debug/rendering_stats_instrumentation.h"
+#include "cc/input/top_controls_manager.h"
+#include "cc/layers/heads_up_display_layer.h"
+#include "cc/layers/heads_up_display_layer_impl.h"
+#include "cc/layers/layer.h"
+#include "cc/layers/layer_iterator.h"
+#include "cc/layers/render_surface.h"
+#include "cc/layers/scrollbar_layer.h"
+#include "cc/resources/prioritized_resource_manager.h"
+#include "cc/resources/ui_resource_client.h"
+#include "cc/trees/layer_tree_host_client.h"
+#include "cc/trees/layer_tree_host_common.h"
+#include "cc/trees/layer_tree_host_impl.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "cc/trees/occlusion_tracker.h"
+#include "cc/trees/single_thread_proxy.h"
+#include "cc/trees/thread_proxy.h"
+#include "cc/trees/tree_synchronizer.h"
+#include "ui/gfx/size_conversions.h"
+
+namespace {
+static int s_num_layer_tree_instances;
+}
+
+namespace cc {
+
+RendererCapabilities::RendererCapabilities()
+ : best_texture_format(0),
+ using_partial_swap(false),
+ using_set_visibility(false),
+ using_egl_image(false),
+ allow_partial_texture_updates(false),
+ using_offscreen_context3d(false),
+ max_texture_size(0),
+ avoid_pow2_textures(false),
+ using_map_image(false),
+ using_shared_memory_resources(false) {}
+
+RendererCapabilities::~RendererCapabilities() {}
+
+UIResourceRequest::UIResourceRequest()
+ : type(UIResourceInvalidRequest), id(0), bitmap(NULL) {}
+
+UIResourceRequest::~UIResourceRequest() {}
+
+bool LayerTreeHost::AnyLayerTreeHostInstanceExists() {
+ return s_num_layer_tree_instances > 0;
+}
+
+scoped_ptr<LayerTreeHost> LayerTreeHost::Create(
+ LayerTreeHostClient* client,
+ const LayerTreeSettings& settings,
+ scoped_refptr<base::SingleThreadTaskRunner> impl_task_runner) {
+ scoped_ptr<LayerTreeHost> layer_tree_host(new LayerTreeHost(client,
+ settings));
+ if (!layer_tree_host->Initialize(impl_task_runner))
+ return scoped_ptr<LayerTreeHost>();
+ return layer_tree_host.Pass();
+}
+
+static int s_next_tree_id = 1;
+
+LayerTreeHost::LayerTreeHost(LayerTreeHostClient* client,
+ const LayerTreeSettings& settings)
+ : next_ui_resource_id_(1),
+ animating_(false),
+ needs_full_tree_sync_(true),
+ needs_filter_context_(false),
+ client_(client),
+ source_frame_number_(0),
+ rendering_stats_instrumentation_(RenderingStatsInstrumentation::Create()),
+ output_surface_can_be_initialized_(true),
+ output_surface_lost_(true),
+ num_failed_recreate_attempts_(0),
+ settings_(settings),
+ debug_state_(settings.initial_debug_state),
+ overdraw_bottom_height_(0.f),
+ device_scale_factor_(1.f),
+ visible_(true),
+ page_scale_factor_(1.f),
+ min_page_scale_factor_(1.f),
+ max_page_scale_factor_(1.f),
+ trigger_idle_updates_(true),
+ background_color_(SK_ColorWHITE),
+ has_transparent_background_(false),
+ partial_texture_update_requests_(0),
+ in_paint_layer_contents_(false),
+ total_frames_used_for_lcd_text_metrics_(0),
+ tree_id_(s_next_tree_id++) {
+ if (settings_.accelerated_animation_enabled)
+ animation_registrar_ = AnimationRegistrar::Create();
+ s_num_layer_tree_instances++;
+ rendering_stats_instrumentation_->set_record_rendering_stats(
+ debug_state_.RecordRenderingStats());
+}
+
+bool LayerTreeHost::Initialize(
+ scoped_refptr<base::SingleThreadTaskRunner> impl_task_runner) {
+ if (impl_task_runner.get())
+ return InitializeProxy(ThreadProxy::Create(this, impl_task_runner));
+ else
+ return InitializeProxy(SingleThreadProxy::Create(this));
+}
+
+bool LayerTreeHost::InitializeForTesting(scoped_ptr<Proxy> proxy_for_testing) {
+ return InitializeProxy(proxy_for_testing.Pass());
+}
+
+bool LayerTreeHost::InitializeProxy(scoped_ptr<Proxy> proxy) {
+ TRACE_EVENT0("cc", "LayerTreeHost::InitializeForReal");
+
+ scoped_ptr<OutputSurface> output_surface(CreateOutputSurface());
+ if (!output_surface)
+ return false;
+
+ proxy_ = proxy.Pass();
+ proxy_->Start(output_surface.Pass());
+ return true;
+}
+
+LayerTreeHost::~LayerTreeHost() {
+ TRACE_EVENT0("cc", "LayerTreeHost::~LayerTreeHost");
+ if (root_layer_.get())
+ root_layer_->SetLayerTreeHost(NULL);
+
+ if (proxy_) {
+ DCHECK(proxy_->IsMainThread());
+ proxy_->Stop();
+ }
+
+ s_num_layer_tree_instances--;
+ RateLimiterMap::iterator it = rate_limiters_.begin();
+ if (it != rate_limiters_.end())
+ it->second->Stop();
+
+ if (root_layer_.get()) {
+ // The layer tree must be destroyed before the layer tree host. We've
+ // made a contract with our animation controllers that the registrar
+ // will outlive them, and we must make good.
+ root_layer_ = NULL;
+ }
+}
+
+void LayerTreeHost::SetLayerTreeHostClientReady() {
+ proxy_->SetLayerTreeHostClientReady();
+}
+
+LayerTreeHost::CreateResult
+LayerTreeHost::OnCreateAndInitializeOutputSurfaceAttempted(bool success) {
+ TRACE_EVENT1("cc",
+ "LayerTreeHost::OnCreateAndInitializeOutputSurfaceAttempted",
+ "success",
+ success);
+
+ DCHECK(output_surface_lost_);
+ if (success) {
+ output_surface_lost_ = false;
+
+ // Update settings_ based on partial update capability.
+ size_t max_partial_texture_updates = 0;
+ if (proxy_->GetRendererCapabilities().allow_partial_texture_updates &&
+ !settings_.impl_side_painting) {
+ max_partial_texture_updates = std::min(
+ settings_.max_partial_texture_updates,
+ proxy_->MaxPartialTextureUpdates());
+ }
+ settings_.max_partial_texture_updates = max_partial_texture_updates;
+
+ if (!contents_texture_manager_ &&
+ (!settings_.impl_side_painting || !settings_.solid_color_scrollbars)) {
+ contents_texture_manager_ =
+ PrioritizedResourceManager::Create(proxy_.get());
+ surface_memory_placeholder_ =
+ contents_texture_manager_->CreateTexture(gfx::Size(), GL_RGBA);
+ }
+
+ client_->DidInitializeOutputSurface(true);
+ return CreateSucceeded;
+ }
+
+ // Failure path.
+
+ client_->DidFailToInitializeOutputSurface();
+
+ // Tolerate a certain number of recreation failures to work around races
+ // in the output-surface-lost machinery.
+ ++num_failed_recreate_attempts_;
+ if (num_failed_recreate_attempts_ >= 5) {
+ // We have tried too many times to recreate the output surface. Tell the
+ // host to fall back to software rendering.
+ output_surface_can_be_initialized_ = false;
+ client_->DidInitializeOutputSurface(false);
+ return CreateFailedAndGaveUp;
+ }
+
+ return CreateFailedButTryAgain;
+}
+
+void LayerTreeHost::DeleteContentsTexturesOnImplThread(
+ ResourceProvider* resource_provider) {
+ DCHECK(proxy_->IsImplThread());
+ if (contents_texture_manager_)
+ contents_texture_manager_->ClearAllMemory(resource_provider);
+}
+
+void LayerTreeHost::AcquireLayerTextures() {
+ DCHECK(proxy_->IsMainThread());
+ proxy_->AcquireLayerTextures();
+}
+
+void LayerTreeHost::DidBeginFrame() {
+ client_->DidBeginFrame();
+}
+
+void LayerTreeHost::UpdateClientAnimations(base::TimeTicks frame_begin_time) {
+ animating_ = true;
+ client_->Animate((frame_begin_time - base::TimeTicks()).InSecondsF());
+ animating_ = false;
+}
+
+void LayerTreeHost::DidStopFlinging() {
+ proxy_->MainThreadHasStoppedFlinging();
+}
+
+void LayerTreeHost::Layout() {
+ client_->Layout();
+}
+
+void LayerTreeHost::BeginCommitOnImplThread(LayerTreeHostImpl* host_impl) {
+ DCHECK(proxy_->IsImplThread());
+ TRACE_EVENT0("cc", "LayerTreeHost::CommitTo");
+}
+
+// This function commits the LayerTreeHost to an impl tree. When modifying
+// this function, keep in mind that the function *runs* on the impl thread! Any
+// code that is logically a main thread operation, e.g. deletion of a Layer,
+// should be delayed until the LayerTreeHost::CommitComplete, which will run
+// after the commit, but on the main thread.
+void LayerTreeHost::FinishCommitOnImplThread(LayerTreeHostImpl* host_impl) {
+ DCHECK(proxy_->IsImplThread());
+
+ // If there are linked evicted backings, these backings' resources may be put
+ // into the impl tree, so we can't draw yet. Determine this before clearing
+ // all evicted backings.
+ bool new_impl_tree_has_no_evicted_resources = false;
+ if (contents_texture_manager_) {
+ new_impl_tree_has_no_evicted_resources =
+ !contents_texture_manager_->LinkedEvictedBackingsExist();
+
+ // If the memory limit has been increased since this now-finishing
+ // commit began, and the extra now-available memory would have been used,
+ // then request another commit.
+ if (contents_texture_manager_->MaxMemoryLimitBytes() <
+ host_impl->memory_allocation_limit_bytes() &&
+ contents_texture_manager_->MaxMemoryLimitBytes() <
+ contents_texture_manager_->MaxMemoryNeededBytes()) {
+ host_impl->SetNeedsCommit();
+ }
+
+ host_impl->set_max_memory_needed_bytes(
+ contents_texture_manager_->MaxMemoryNeededBytes());
+
+ contents_texture_manager_->UpdateBackingsInDrawingImplTree();
+ }
+
+ // In impl-side painting, synchronize to the pending tree so that it has
+ // time to raster before being displayed. If no pending tree is needed,
+ // synchronization can happen directly to the active tree and
+ // unlinked contents resources can be reclaimed immediately.
+ LayerTreeImpl* sync_tree;
+ if (settings_.impl_side_painting) {
+ // Commits should not occur while there is already a pending tree.
+ DCHECK(!host_impl->pending_tree());
+ host_impl->CreatePendingTree();
+ sync_tree = host_impl->pending_tree();
+ } else {
+ contents_texture_manager_->ReduceMemory(host_impl->resource_provider());
+ sync_tree = host_impl->active_tree();
+ }
+
+ sync_tree->set_source_frame_number(source_frame_number());
+
+ if (needs_full_tree_sync_)
+ sync_tree->SetRootLayer(TreeSynchronizer::SynchronizeTrees(
+ root_layer(), sync_tree->DetachLayerTree(), sync_tree));
+ {
+ TRACE_EVENT0("cc", "LayerTreeHost::PushProperties");
+ TreeSynchronizer::PushProperties(root_layer(), sync_tree->root_layer());
+ }
+
+ sync_tree->set_needs_full_tree_sync(needs_full_tree_sync_);
+ needs_full_tree_sync_ = false;
+
+ if (hud_layer_.get()) {
+ LayerImpl* hud_impl = LayerTreeHostCommon::FindLayerInSubtree(
+ sync_tree->root_layer(), hud_layer_->id());
+ sync_tree->set_hud_layer(static_cast<HeadsUpDisplayLayerImpl*>(hud_impl));
+ } else {
+ sync_tree->set_hud_layer(NULL);
+ }
+
+ sync_tree->set_background_color(background_color_);
+ sync_tree->set_has_transparent_background(has_transparent_background_);
+
+ sync_tree->FindRootScrollLayer();
+
+ float page_scale_delta, sent_page_scale_delta;
+ if (settings_.impl_side_painting) {
+ // Update the delta from the active tree, which may have
+ // adjusted its delta prior to the pending tree being created.
+ // This code is equivalent to that in LayerTreeImpl::SetPageScaleDelta.
+ DCHECK_EQ(1.f, sync_tree->sent_page_scale_delta());
+ page_scale_delta = host_impl->active_tree()->page_scale_delta();
+ sent_page_scale_delta = host_impl->active_tree()->sent_page_scale_delta();
+ } else {
+ page_scale_delta = sync_tree->page_scale_delta();
+ sent_page_scale_delta = sync_tree->sent_page_scale_delta();
+ sync_tree->set_sent_page_scale_delta(1.f);
+ }
+
+ sync_tree->SetPageScaleFactorAndLimits(page_scale_factor_,
+ min_page_scale_factor_,
+ max_page_scale_factor_);
+ sync_tree->SetPageScaleDelta(page_scale_delta / sent_page_scale_delta);
+ sync_tree->SetLatencyInfo(latency_info_);
+ latency_info_.Clear();
+
+ host_impl->SetViewportSize(device_viewport_size_);
+ host_impl->SetOverdrawBottomHeight(overdraw_bottom_height_);
+ host_impl->SetDeviceScaleFactor(device_scale_factor_);
+ host_impl->SetDebugState(debug_state_);
+ if (pending_page_scale_animation_) {
+ host_impl->StartPageScaleAnimation(
+ pending_page_scale_animation_->target_offset,
+ pending_page_scale_animation_->use_anchor,
+ pending_page_scale_animation_->scale,
+ base::TimeTicks::Now(),
+ pending_page_scale_animation_->duration);
+ pending_page_scale_animation_.reset();
+ }
+
+ if (!ui_resource_request_queue_.empty()) {
+ sync_tree->set_ui_resource_request_queue(ui_resource_request_queue_);
+ ui_resource_request_queue_.clear();
+ // Process any ui resource requests in the queue. For impl-side-painting,
+ // the queue is processed in LayerTreeHostImpl::ActivatePendingTree.
+ if (!settings_.impl_side_painting)
+ sync_tree->ProcessUIResourceRequestQueue();
+ }
+
+ DCHECK(!sync_tree->ViewportSizeInvalid());
+
+ if (new_impl_tree_has_no_evicted_resources) {
+ if (sync_tree->ContentsTexturesPurged())
+ sync_tree->ResetContentsTexturesPurged();
+ }
+
+ if (!settings_.impl_side_painting) {
+ // If we're not in impl-side painting, the tree is immediately
+ // considered active.
+ sync_tree->DidBecomeActive();
+ }
+
+ source_frame_number_++;
+}
+
+void LayerTreeHost::WillCommit() {
+ client_->WillCommit();
+}
+
+void LayerTreeHost::UpdateHudLayer() {
+ if (debug_state_.ShowHudInfo()) {
+ if (!hud_layer_.get())
+ hud_layer_ = HeadsUpDisplayLayer::Create();
+
+ if (root_layer_.get() && !hud_layer_->parent())
+ root_layer_->AddChild(hud_layer_);
+ } else if (hud_layer_.get()) {
+ hud_layer_->RemoveFromParent();
+ hud_layer_ = NULL;
+ }
+}
+
+void LayerTreeHost::CommitComplete() {
+ client_->DidCommit();
+}
+
+scoped_ptr<OutputSurface> LayerTreeHost::CreateOutputSurface() {
+ return client_->CreateOutputSurface(num_failed_recreate_attempts_ >= 4);
+}
+
+scoped_ptr<LayerTreeHostImpl> LayerTreeHost::CreateLayerTreeHostImpl(
+ LayerTreeHostImplClient* client) {
+ DCHECK(proxy_->IsImplThread());
+ scoped_ptr<LayerTreeHostImpl> host_impl =
+ LayerTreeHostImpl::Create(settings_,
+ client,
+ proxy_.get(),
+ rendering_stats_instrumentation_.get());
+ if (settings_.calculate_top_controls_position &&
+ host_impl->top_controls_manager()) {
+ top_controls_manager_weak_ptr_ =
+ host_impl->top_controls_manager()->AsWeakPtr();
+ }
+ input_handler_weak_ptr_ = host_impl->AsWeakPtr();
+ return host_impl.Pass();
+}
+
+void LayerTreeHost::DidLoseOutputSurface() {
+ TRACE_EVENT0("cc", "LayerTreeHost::DidLoseOutputSurface");
+ DCHECK(proxy_->IsMainThread());
+
+ if (output_surface_lost_)
+ return;
+
+ DidLoseUIResources();
+
+ num_failed_recreate_attempts_ = 0;
+ output_surface_lost_ = true;
+ SetNeedsCommit();
+}
+
+bool LayerTreeHost::CompositeAndReadback(void* pixels,
+ gfx::Rect rect_in_device_viewport) {
+ trigger_idle_updates_ = false;
+ bool ret = proxy_->CompositeAndReadback(pixels, rect_in_device_viewport);
+ trigger_idle_updates_ = true;
+ return ret;
+}
+
+void LayerTreeHost::FinishAllRendering() {
+ proxy_->FinishAllRendering();
+}
+
+void LayerTreeHost::SetDeferCommits(bool defer_commits) {
+ proxy_->SetDeferCommits(defer_commits);
+}
+
+void LayerTreeHost::DidDeferCommit() {}
+
+void LayerTreeHost::SetNeedsDisplayOnAllLayers() {
+ std::stack<Layer*> layer_stack;
+ layer_stack.push(root_layer());
+ while (!layer_stack.empty()) {
+ Layer* current_layer = layer_stack.top();
+ layer_stack.pop();
+ current_layer->SetNeedsDisplay();
+ for (unsigned int i = 0; i < current_layer->children().size(); i++) {
+ layer_stack.push(current_layer->child_at(i));
+ }
+ }
+}
+
+void LayerTreeHost::CollectRenderingStats(RenderingStats* stats) const {
+ CHECK(debug_state_.RecordRenderingStats());
+ *stats = rendering_stats_instrumentation_->GetRenderingStats();
+}
+
+const RendererCapabilities& LayerTreeHost::GetRendererCapabilities() const {
+ return proxy_->GetRendererCapabilities();
+}
+
+void LayerTreeHost::SetNeedsAnimate() {
+ DCHECK(proxy_->HasImplThread());
+ proxy_->SetNeedsAnimate();
+}
+
+void LayerTreeHost::SetNeedsUpdateLayers() { proxy_->SetNeedsUpdateLayers(); }
+
+void LayerTreeHost::SetNeedsCommit() {
+ if (!prepaint_callback_.IsCancelled()) {
+ TRACE_EVENT_INSTANT0("cc",
+ "LayerTreeHost::SetNeedsCommit::cancel prepaint",
+ TRACE_EVENT_SCOPE_THREAD);
+ prepaint_callback_.Cancel();
+ }
+ proxy_->SetNeedsCommit();
+}
+
+void LayerTreeHost::SetNeedsFullTreeSync() {
+ needs_full_tree_sync_ = true;
+ SetNeedsCommit();
+}
+
+void LayerTreeHost::SetNeedsRedraw() {
+ SetNeedsRedrawRect(gfx::Rect(device_viewport_size_));
+}
+
+void LayerTreeHost::SetNeedsRedrawRect(gfx::Rect damage_rect) {
+ proxy_->SetNeedsRedraw(damage_rect);
+ if (!proxy_->HasImplThread())
+ client_->ScheduleComposite();
+}
+
+bool LayerTreeHost::CommitRequested() const {
+ return proxy_->CommitRequested();
+}
+
+void LayerTreeHost::SetAnimationEvents(scoped_ptr<AnimationEventsVector> events,
+ base::Time wall_clock_time) {
+ DCHECK(proxy_->IsMainThread());
+ for (size_t event_index = 0; event_index < events->size(); ++event_index) {
+ int event_layer_id = (*events)[event_index].layer_id;
+
+ // Use the map of all controllers, not just active ones, since non-active
+ // controllers may still receive events for impl-only animations.
+ const AnimationRegistrar::AnimationControllerMap& animation_controllers =
+ animation_registrar_->all_animation_controllers();
+ AnimationRegistrar::AnimationControllerMap::const_iterator iter =
+ animation_controllers.find(event_layer_id);
+ if (iter != animation_controllers.end()) {
+ switch ((*events)[event_index].type) {
+ case AnimationEvent::Started:
+ (*iter).second->NotifyAnimationStarted((*events)[event_index],
+ wall_clock_time.ToDoubleT());
+ break;
+
+ case AnimationEvent::Finished:
+ (*iter).second->NotifyAnimationFinished((*events)[event_index],
+ wall_clock_time.ToDoubleT());
+ break;
+
+ case AnimationEvent::PropertyUpdate:
+ (*iter).second->NotifyAnimationPropertyUpdate((*events)[event_index]);
+ break;
+
+ default:
+ NOTREACHED();
+ }
+ }
+ }
+}
+
+void LayerTreeHost::SetRootLayer(scoped_refptr<Layer> root_layer) {
+ if (root_layer_.get() == root_layer.get())
+ return;
+
+ if (root_layer_.get())
+ root_layer_->SetLayerTreeHost(NULL);
+ root_layer_ = root_layer;
+ if (root_layer_.get()) {
+ DCHECK(!root_layer_->parent());
+ root_layer_->SetLayerTreeHost(this);
+ }
+
+ if (hud_layer_.get())
+ hud_layer_->RemoveFromParent();
+
+ SetNeedsFullTreeSync();
+}
+
+void LayerTreeHost::SetDebugState(const LayerTreeDebugState& debug_state) {
+ LayerTreeDebugState new_debug_state =
+ LayerTreeDebugState::Unite(settings_.initial_debug_state, debug_state);
+
+ if (LayerTreeDebugState::Equal(debug_state_, new_debug_state))
+ return;
+
+ debug_state_ = new_debug_state;
+
+ rendering_stats_instrumentation_->set_record_rendering_stats(
+ debug_state_.RecordRenderingStats());
+
+ SetNeedsCommit();
+}
+
+void LayerTreeHost::SetViewportSize(gfx::Size device_viewport_size) {
+ if (device_viewport_size == device_viewport_size_)
+ return;
+
+ device_viewport_size_ = device_viewport_size;
+
+ SetNeedsCommit();
+}
+
+void LayerTreeHost::SetOverdrawBottomHeight(float overdraw_bottom_height) {
+ if (overdraw_bottom_height_ == overdraw_bottom_height)
+ return;
+
+ overdraw_bottom_height_ = overdraw_bottom_height;
+ SetNeedsCommit();
+}
+
+void LayerTreeHost::ApplyPageScaleDeltaFromImplSide(float page_scale_delta) {
+ DCHECK(CommitRequested());
+ page_scale_factor_ *= page_scale_delta;
+}
+
+void LayerTreeHost::SetPageScaleFactorAndLimits(float page_scale_factor,
+ float min_page_scale_factor,
+ float max_page_scale_factor) {
+ if (page_scale_factor == page_scale_factor_ &&
+ min_page_scale_factor == min_page_scale_factor_ &&
+ max_page_scale_factor == max_page_scale_factor_)
+ return;
+
+ page_scale_factor_ = page_scale_factor;
+ min_page_scale_factor_ = min_page_scale_factor;
+ max_page_scale_factor_ = max_page_scale_factor;
+ SetNeedsCommit();
+}
+
+void LayerTreeHost::SetVisible(bool visible) {
+ if (visible_ == visible)
+ return;
+ visible_ = visible;
+ if (!visible)
+ ReduceMemoryUsage();
+ proxy_->SetVisible(visible);
+}
+
+void LayerTreeHost::SetLatencyInfo(const ui::LatencyInfo& latency_info) {
+ latency_info_.MergeWith(latency_info);
+}
+
+void LayerTreeHost::StartPageScaleAnimation(gfx::Vector2d target_offset,
+ bool use_anchor,
+ float scale,
+ base::TimeDelta duration) {
+ pending_page_scale_animation_.reset(new PendingPageScaleAnimation);
+ pending_page_scale_animation_->target_offset = target_offset;
+ pending_page_scale_animation_->use_anchor = use_anchor;
+ pending_page_scale_animation_->scale = scale;
+ pending_page_scale_animation_->duration = duration;
+
+ SetNeedsCommit();
+}
+
+void LayerTreeHost::NotifyInputThrottledUntilCommit() {
+ proxy_->NotifyInputThrottledUntilCommit();
+}
+
+void LayerTreeHost::Composite(base::TimeTicks frame_begin_time) {
+ if (!proxy_->HasImplThread())
+ static_cast<SingleThreadProxy*>(proxy_.get())->CompositeImmediately(
+ frame_begin_time);
+ else
+ SetNeedsCommit();
+}
+
+void LayerTreeHost::ScheduleComposite() {
+ client_->ScheduleComposite();
+}
+
+bool LayerTreeHost::InitializeOutputSurfaceIfNeeded() {
+ if (!output_surface_can_be_initialized_)
+ return false;
+
+ if (output_surface_lost_)
+ proxy_->CreateAndInitializeOutputSurface();
+ return !output_surface_lost_;
+}
+
+bool LayerTreeHost::UpdateLayers(ResourceUpdateQueue* queue,
+ size_t memory_allocation_limit_bytes) {
+ DCHECK(!output_surface_lost_);
+
+ if (!root_layer())
+ return false;
+
+ DCHECK(!root_layer()->parent());
+
+ if (contents_texture_manager_ && memory_allocation_limit_bytes) {
+ contents_texture_manager_->SetMaxMemoryLimitBytes(
+ memory_allocation_limit_bytes);
+ }
+
+ return UpdateLayers(root_layer(), queue);
+}
+
+static Layer* FindFirstScrollableLayer(Layer* layer) {
+ if (!layer)
+ return NULL;
+
+ if (layer->scrollable())
+ return layer;
+
+ for (size_t i = 0; i < layer->children().size(); ++i) {
+ Layer* found = FindFirstScrollableLayer(layer->children()[i].get());
+ if (found)
+ return found;
+ }
+
+ return NULL;
+}
+
+void LayerTreeHost::CalculateLCDTextMetricsCallback(Layer* layer) {
+ if (!layer->SupportsLCDText())
+ return;
+
+ lcd_text_metrics_.total_num_cc_layers++;
+ if (layer->draw_properties().can_use_lcd_text) {
+ lcd_text_metrics_.total_num_cc_layers_can_use_lcd_text++;
+ if (layer->contents_opaque())
+ lcd_text_metrics_.total_num_cc_layers_will_use_lcd_text++;
+ }
+}
+
+bool LayerTreeHost::UsingSharedMemoryResources() {
+ return GetRendererCapabilities().using_shared_memory_resources;
+}
+
+bool LayerTreeHost::UpdateLayers(Layer* root_layer,
+ ResourceUpdateQueue* queue) {
+ TRACE_EVENT1(benchmark_instrumentation::kCategory,
+ benchmark_instrumentation::kLayerTreeHostUpdateLayers,
+ benchmark_instrumentation::kSourceFrameNumber,
+ source_frame_number());
+
+ RenderSurfaceLayerList update_list;
+ {
+ UpdateHudLayer();
+
+ Layer* root_scroll = FindFirstScrollableLayer(root_layer);
+
+ if (hud_layer_) {
+ hud_layer_->PrepareForCalculateDrawProperties(
+ device_viewport_size(), device_scale_factor_);
+ }
+
+ TRACE_EVENT0("cc", "LayerTreeHost::UpdateLayers::CalcDrawProps");
+ LayerTreeHostCommon::CalcDrawPropsMainInputs inputs(
+ root_layer,
+ device_viewport_size(),
+ gfx::Transform(),
+ device_scale_factor_,
+ page_scale_factor_,
+ root_scroll ? root_scroll->parent() : NULL,
+ GetRendererCapabilities().max_texture_size,
+ settings_.can_use_lcd_text,
+ settings_.layer_transforms_should_scale_layer_contents,
+ &update_list);
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ if (total_frames_used_for_lcd_text_metrics_ <=
+ kTotalFramesToUseForLCDTextMetrics) {
+ LayerTreeHostCommon::CallFunctionForSubtree(
+ root_layer,
+ base::Bind(&LayerTreeHost::CalculateLCDTextMetricsCallback,
+ base::Unretained(this)));
+ total_frames_used_for_lcd_text_metrics_++;
+ }
+
+ if (total_frames_used_for_lcd_text_metrics_ ==
+ kTotalFramesToUseForLCDTextMetrics) {
+ total_frames_used_for_lcd_text_metrics_++;
+
+ UMA_HISTOGRAM_PERCENTAGE(
+ "Renderer4.LCDText.PercentageOfCandidateLayers",
+ lcd_text_metrics_.total_num_cc_layers_can_use_lcd_text * 100.0 /
+ lcd_text_metrics_.total_num_cc_layers);
+ UMA_HISTOGRAM_PERCENTAGE(
+ "Renderer4.LCDText.PercentageOfAALayers",
+ lcd_text_metrics_.total_num_cc_layers_will_use_lcd_text * 100.0 /
+ lcd_text_metrics_.total_num_cc_layers_can_use_lcd_text);
+ }
+ }
+
+ // Reset partial texture update requests.
+ partial_texture_update_requests_ = 0;
+
+ bool did_paint_content = false;
+ bool need_more_updates = false;
+ PaintLayerContents(
+ update_list, queue, &did_paint_content, &need_more_updates);
+ if (trigger_idle_updates_ && need_more_updates) {
+ TRACE_EVENT0("cc", "LayerTreeHost::UpdateLayers::posting prepaint task");
+ prepaint_callback_.Reset(base::Bind(&LayerTreeHost::TriggerPrepaint,
+ base::Unretained(this)));
+ static base::TimeDelta prepaint_delay =
+ base::TimeDelta::FromMilliseconds(100);
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, prepaint_callback_.callback(), prepaint_delay);
+ }
+
+ return did_paint_content;
+}
+
+void LayerTreeHost::TriggerPrepaint() {
+ prepaint_callback_.Cancel();
+ TRACE_EVENT0("cc", "LayerTreeHost::TriggerPrepaint");
+ SetNeedsCommit();
+}
+
+static void LayerTreeHostReduceMemoryCallback(Layer* layer) {
+ layer->ReduceMemoryUsage();
+}
+
+void LayerTreeHost::ReduceMemoryUsage() {
+ if (!root_layer())
+ return;
+
+ LayerTreeHostCommon::CallFunctionForSubtree(
+ root_layer(),
+ base::Bind(&LayerTreeHostReduceMemoryCallback));
+}
+
+void LayerTreeHost::SetPrioritiesForSurfaces(size_t surface_memory_bytes) {
+ DCHECK(surface_memory_placeholder_);
+
+ // Surfaces have a place holder for their memory since they are managed
+ // independantly but should still be tracked and reduce other memory usage.
+ surface_memory_placeholder_->SetTextureManager(
+ contents_texture_manager_.get());
+ surface_memory_placeholder_->set_request_priority(
+ PriorityCalculator::RenderSurfacePriority());
+ surface_memory_placeholder_->SetToSelfManagedMemoryPlaceholder(
+ surface_memory_bytes);
+}
+
+void LayerTreeHost::SetPrioritiesForLayers(
+ const RenderSurfaceLayerList& update_list) {
+ // Use BackToFront since it's cheap and this isn't order-dependent.
+ typedef LayerIterator<Layer,
+ RenderSurfaceLayerList,
+ RenderSurface,
+ LayerIteratorActions::BackToFront> LayerIteratorType;
+
+ PriorityCalculator calculator;
+ LayerIteratorType end = LayerIteratorType::End(&update_list);
+ for (LayerIteratorType it = LayerIteratorType::Begin(&update_list);
+ it != end;
+ ++it) {
+ if (it.represents_itself()) {
+ it->SetTexturePriorities(calculator);
+ } else if (it.represents_target_render_surface()) {
+ if (it->mask_layer())
+ it->mask_layer()->SetTexturePriorities(calculator);
+ if (it->replica_layer() && it->replica_layer()->mask_layer())
+ it->replica_layer()->mask_layer()->SetTexturePriorities(calculator);
+ }
+ }
+}
+
+void LayerTreeHost::PrioritizeTextures(
+ const RenderSurfaceLayerList& render_surface_layer_list,
+ OverdrawMetrics* metrics) {
+ if (!contents_texture_manager_)
+ return;
+
+ contents_texture_manager_->ClearPriorities();
+
+ size_t memory_for_render_surfaces_metric =
+ CalculateMemoryForRenderSurfaces(render_surface_layer_list);
+
+ SetPrioritiesForLayers(render_surface_layer_list);
+ SetPrioritiesForSurfaces(memory_for_render_surfaces_metric);
+
+ metrics->DidUseContentsTextureMemoryBytes(
+ contents_texture_manager_->MemoryAboveCutoffBytes());
+ metrics->DidUseRenderSurfaceTextureMemoryBytes(
+ memory_for_render_surfaces_metric);
+
+ contents_texture_manager_->PrioritizeTextures();
+}
+
+size_t LayerTreeHost::CalculateMemoryForRenderSurfaces(
+ const RenderSurfaceLayerList& update_list) {
+ size_t readback_bytes = 0;
+ size_t max_background_texture_bytes = 0;
+ size_t contents_texture_bytes = 0;
+
+ // Start iteration at 1 to skip the root surface as it does not have a texture
+ // cost.
+ for (size_t i = 1; i < update_list.size(); ++i) {
+ Layer* render_surface_layer = update_list.at(i);
+ RenderSurface* render_surface = render_surface_layer->render_surface();
+
+ size_t bytes =
+ Resource::MemorySizeBytes(render_surface->content_rect().size(),
+ GL_RGBA);
+ contents_texture_bytes += bytes;
+
+ if (render_surface_layer->background_filters().IsEmpty())
+ continue;
+
+ if (bytes > max_background_texture_bytes)
+ max_background_texture_bytes = bytes;
+ if (!readback_bytes) {
+ readback_bytes = Resource::MemorySizeBytes(device_viewport_size_,
+ GL_RGBA);
+ }
+ }
+ return readback_bytes + max_background_texture_bytes + contents_texture_bytes;
+}
+
+void LayerTreeHost::PaintMasksForRenderSurface(Layer* render_surface_layer,
+ ResourceUpdateQueue* queue,
+ bool* did_paint_content,
+ bool* need_more_updates) {
+ // Note: Masks and replicas only exist for layers that own render surfaces. If
+ // we reach this point in code, we already know that at least something will
+ // be drawn into this render surface, so the mask and replica should be
+ // painted.
+
+ Layer* mask_layer = render_surface_layer->mask_layer();
+ if (mask_layer) {
+ devtools_instrumentation::ScopedLayerTreeTask
+ update_layer(devtools_instrumentation::kUpdateLayer,
+ mask_layer->id(),
+ id());
+ *did_paint_content |= mask_layer->Update(queue, NULL);
+ *need_more_updates |= mask_layer->NeedMoreUpdates();
+ }
+
+ Layer* replica_mask_layer =
+ render_surface_layer->replica_layer() ?
+ render_surface_layer->replica_layer()->mask_layer() : NULL;
+ if (replica_mask_layer) {
+ devtools_instrumentation::ScopedLayerTreeTask
+ update_layer(devtools_instrumentation::kUpdateLayer,
+ replica_mask_layer->id(),
+ id());
+ *did_paint_content |= replica_mask_layer->Update(queue, NULL);
+ *need_more_updates |= replica_mask_layer->NeedMoreUpdates();
+ }
+}
+
+void LayerTreeHost::PaintLayerContents(
+ const RenderSurfaceLayerList& render_surface_layer_list,
+ ResourceUpdateQueue* queue,
+ bool* did_paint_content,
+ bool* need_more_updates) {
+ // Use FrontToBack to allow for testing occlusion and performing culling
+ // during the tree walk.
+ typedef LayerIterator<Layer,
+ RenderSurfaceLayerList,
+ RenderSurface,
+ LayerIteratorActions::FrontToBack> LayerIteratorType;
+
+ bool record_metrics_for_frame =
+ settings_.show_overdraw_in_tracing &&
+ base::debug::TraceLog::GetInstance() &&
+ base::debug::TraceLog::GetInstance()->IsEnabled();
+ OcclusionTracker occlusion_tracker(
+ root_layer_->render_surface()->content_rect(), record_metrics_for_frame);
+ occlusion_tracker.set_minimum_tracking_size(
+ settings_.minimum_occlusion_tracking_size);
+
+ PrioritizeTextures(render_surface_layer_list,
+ occlusion_tracker.overdraw_metrics());
+
+ in_paint_layer_contents_ = true;
+
+ LayerIteratorType end = LayerIteratorType::End(&render_surface_layer_list);
+ for (LayerIteratorType it =
+ LayerIteratorType::Begin(&render_surface_layer_list);
+ it != end;
+ ++it) {
+ bool prevent_occlusion = it.target_render_surface_layer()->HasCopyRequest();
+ occlusion_tracker.EnterLayer(it, prevent_occlusion);
+
+ if (it.represents_target_render_surface()) {
+ PaintMasksForRenderSurface(
+ *it, queue, did_paint_content, need_more_updates);
+ } else if (it.represents_itself()) {
+ devtools_instrumentation::ScopedLayerTreeTask
+ update_layer(devtools_instrumentation::kUpdateLayer, it->id(), id());
+ DCHECK(!it->paint_properties().bounds.IsEmpty());
+ *did_paint_content |= it->Update(queue, &occlusion_tracker);
+ *need_more_updates |= it->NeedMoreUpdates();
+ }
+
+ occlusion_tracker.LeaveLayer(it);
+ }
+
+ in_paint_layer_contents_ = false;
+
+ occlusion_tracker.overdraw_metrics()->RecordMetrics(this);
+}
+
+void LayerTreeHost::ApplyScrollAndScale(const ScrollAndScaleSet& info) {
+ if (!root_layer_.get())
+ return;
+
+ gfx::Vector2d root_scroll_delta;
+ Layer* root_scroll_layer = FindFirstScrollableLayer(root_layer_.get());
+
+ for (size_t i = 0; i < info.scrolls.size(); ++i) {
+ Layer* layer =
+ LayerTreeHostCommon::FindLayerInSubtree(root_layer_.get(),
+ info.scrolls[i].layer_id);
+ if (!layer)
+ continue;
+ if (layer == root_scroll_layer) {
+ root_scroll_delta += info.scrolls[i].scroll_delta;
+ } else {
+ layer->SetScrollOffsetFromImplSide(layer->scroll_offset() +
+ info.scrolls[i].scroll_delta);
+ }
+ }
+
+ if (!root_scroll_delta.IsZero() || info.page_scale_delta != 1.f) {
+ // SetScrollOffsetFromImplSide above could have destroyed the tree,
+ // so re-get this layer before doing anything to it.
+ root_scroll_layer = FindFirstScrollableLayer(root_layer_.get());
+
+ // Preemptively apply the scroll offset and scale delta here before sending
+ // it to the client. If the client comes back and sets it to the same
+ // value, then the layer can early out without needing a full commit.
+ if (root_scroll_layer) {
+ root_scroll_layer->SetScrollOffsetFromImplSide(
+ root_scroll_layer->scroll_offset() + root_scroll_delta);
+ }
+ ApplyPageScaleDeltaFromImplSide(info.page_scale_delta);
+ client_->ApplyScrollAndScale(root_scroll_delta, info.page_scale_delta);
+ }
+}
+
+void LayerTreeHost::StartRateLimiter(WebKit::WebGraphicsContext3D* context3d) {
+ if (animating_)
+ return;
+
+ DCHECK(context3d);
+ RateLimiterMap::iterator it = rate_limiters_.find(context3d);
+ if (it != rate_limiters_.end()) {
+ it->second->Start();
+ } else {
+ scoped_refptr<RateLimiter> rate_limiter =
+ RateLimiter::Create(context3d, this, proxy_->MainThreadTaskRunner());
+ rate_limiters_[context3d] = rate_limiter;
+ rate_limiter->Start();
+ }
+}
+
+void LayerTreeHost::StopRateLimiter(WebKit::WebGraphicsContext3D* context3d) {
+ RateLimiterMap::iterator it = rate_limiters_.find(context3d);
+ if (it != rate_limiters_.end()) {
+ it->second->Stop();
+ rate_limiters_.erase(it);
+ }
+}
+
+void LayerTreeHost::RateLimit() {
+ // Force a no-op command on the compositor context, so that any ratelimiting
+ // commands will wait for the compositing context, and therefore for the
+ // SwapBuffers.
+ proxy_->ForceSerializeOnSwapBuffers();
+}
+
+bool LayerTreeHost::RequestPartialTextureUpdate() {
+ if (partial_texture_update_requests_ >= settings_.max_partial_texture_updates)
+ return false;
+
+ partial_texture_update_requests_++;
+ return true;
+}
+
+void LayerTreeHost::SetDeviceScaleFactor(float device_scale_factor) {
+ if (device_scale_factor == device_scale_factor_)
+ return;
+ device_scale_factor_ = device_scale_factor;
+
+ SetNeedsCommit();
+}
+
+void LayerTreeHost::UpdateTopControlsState(TopControlsState constraints,
+ TopControlsState current,
+ bool animate) {
+ if (!settings_.calculate_top_controls_position)
+ return;
+
+ // Top controls are only used in threaded mode.
+ proxy_->ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&TopControlsManager::UpdateTopControlsState,
+ top_controls_manager_weak_ptr_,
+ constraints,
+ current,
+ animate));
+}
+
+bool LayerTreeHost::BlocksPendingCommit() const {
+ if (!root_layer_.get())
+ return false;
+ return root_layer_->BlocksPendingCommitRecursive();
+}
+
+scoped_ptr<base::Value> LayerTreeHost::AsValue() const {
+ scoped_ptr<base::DictionaryValue> state(new base::DictionaryValue());
+ state->Set("proxy", proxy_->AsValue().release());
+ return state.PassAs<base::Value>();
+}
+
+void LayerTreeHost::AnimateLayers(base::TimeTicks time) {
+ rendering_stats_instrumentation_->IncrementAnimationFrameCount();
+ if (!settings_.accelerated_animation_enabled ||
+ animation_registrar_->active_animation_controllers().empty())
+ return;
+
+ TRACE_EVENT0("cc", "LayerTreeHost::AnimateLayers");
+
+ double monotonic_time = (time - base::TimeTicks()).InSecondsF();
+
+ AnimationRegistrar::AnimationControllerMap copy =
+ animation_registrar_->active_animation_controllers();
+ for (AnimationRegistrar::AnimationControllerMap::iterator iter = copy.begin();
+ iter != copy.end();
+ ++iter) {
+ (*iter).second->Animate(monotonic_time);
+ bool start_ready_animations = true;
+ (*iter).second->UpdateState(start_ready_animations, NULL);
+ }
+}
+
+UIResourceId LayerTreeHost::CreateUIResource(UIResourceClient* client) {
+ DCHECK(client);
+
+ UIResourceRequest request;
+ bool resource_lost = false;
+ request.type = UIResourceRequest::UIResourceCreate;
+ request.id = next_ui_resource_id_++;
+
+ DCHECK(ui_resource_client_map_.find(request.id) ==
+ ui_resource_client_map_.end());
+
+ request.bitmap = client->GetBitmap(request.id, resource_lost);
+ ui_resource_request_queue_.push_back(request);
+ ui_resource_client_map_[request.id] = client;
+ return request.id;
+}
+
+// Deletes a UI resource. May safely be called more than once.
+void LayerTreeHost::DeleteUIResource(UIResourceId uid) {
+ UIResourceClientMap::iterator iter = ui_resource_client_map_.find(uid);
+ if (iter == ui_resource_client_map_.end())
+ return;
+
+ UIResourceRequest request;
+ request.type = UIResourceRequest::UIResourceDelete;
+ request.id = uid;
+ ui_resource_request_queue_.push_back(request);
+ ui_resource_client_map_.erase(uid);
+}
+
+void LayerTreeHost::UIResourceLost(UIResourceId uid) {
+ UIResourceClientMap::iterator iter = ui_resource_client_map_.find(uid);
+ if (iter == ui_resource_client_map_.end())
+ return;
+
+ UIResourceRequest request;
+ bool resource_lost = true;
+ request.type = UIResourceRequest::UIResourceCreate;
+ request.id = uid;
+ request.bitmap = iter->second->GetBitmap(uid, resource_lost);
+ DCHECK(request.bitmap.get());
+ ui_resource_request_queue_.push_back(request);
+}
+
+void LayerTreeHost::DidLoseUIResources() {
+ // When output surface is lost, we need to recreate the resource.
+ for (UIResourceClientMap::iterator iter = ui_resource_client_map_.begin();
+ iter != ui_resource_client_map_.end();
+ ++iter) {
+ UIResourceLost(iter->first);
+ }
+}
+
+} // namespace cc
diff --git a/chromium/cc/trees/layer_tree_host.h b/chromium/cc/trees/layer_tree_host.h
new file mode 100644
index 00000000000..619c18e6574
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_host.h
@@ -0,0 +1,420 @@
+// Copyright 2011 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.
+
+#ifndef CC_TREES_LAYER_TREE_HOST_H_
+#define CC_TREES_LAYER_TREE_HOST_H_
+
+#include <limits>
+#include <list>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/cancelable_callback.h"
+#include "base/containers/hash_tables.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "cc/animation/animation_events.h"
+#include "cc/base/cc_export.h"
+#include "cc/base/scoped_ptr_vector.h"
+#include "cc/input/input_handler.h"
+#include "cc/input/scrollbar.h"
+#include "cc/input/top_controls_state.h"
+#include "cc/layers/layer_lists.h"
+#include "cc/output/output_surface.h"
+#include "cc/resources/ui_resource_bitmap.h"
+#include "cc/resources/ui_resource_client.h"
+#include "cc/scheduler/rate_limiter.h"
+#include "cc/trees/layer_tree_host_client.h"
+#include "cc/trees/layer_tree_host_common.h"
+#include "cc/trees/layer_tree_settings.h"
+#include "cc/trees/occlusion_tracker.h"
+#include "cc/trees/proxy.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/base/latency_info.h"
+#include "ui/gfx/rect.h"
+
+namespace WebKit { class WebGraphicsContext3D; }
+
+#if defined(COMPILER_GCC)
+namespace BASE_HASH_NAMESPACE {
+template <>
+struct hash<WebKit::WebGraphicsContext3D*> {
+ size_t operator()(WebKit::WebGraphicsContext3D* ptr) const {
+ return hash<size_t>()(reinterpret_cast<size_t>(ptr));
+ }
+};
+} // namespace BASE_HASH_NAMESPACE
+#endif // COMPILER
+
+namespace cc {
+
+class AnimationRegistrar;
+class HeadsUpDisplayLayer;
+class Layer;
+class LayerTreeHostImpl;
+class LayerTreeHostImplClient;
+class PrioritizedResourceManager;
+class PrioritizedResource;
+class Region;
+class RenderingStatsInstrumentation;
+class ResourceProvider;
+class ResourceUpdateQueue;
+class TopControlsManager;
+struct RenderingStats;
+struct ScrollAndScaleSet;
+
+// Provides information on an Impl's rendering capabilities back to the
+// LayerTreeHost.
+struct CC_EXPORT RendererCapabilities {
+ RendererCapabilities();
+ ~RendererCapabilities();
+
+ unsigned best_texture_format;
+ bool using_partial_swap;
+ bool using_set_visibility;
+ bool using_egl_image;
+ bool allow_partial_texture_updates;
+ bool using_offscreen_context3d;
+ int max_texture_size;
+ bool avoid_pow2_textures;
+ bool using_map_image;
+ bool using_shared_memory_resources;
+};
+
+struct CC_EXPORT UIResourceRequest {
+ enum UIResourceRequestType {
+ UIResourceCreate,
+ UIResourceDelete,
+ UIResourceInvalidRequest
+ };
+
+ UIResourceRequest();
+ ~UIResourceRequest();
+ UIResourceRequestType type;
+ UIResourceId id;
+ scoped_refptr<UIResourceBitmap> bitmap;
+};
+
+class CC_EXPORT LayerTreeHost : NON_EXPORTED_BASE(public RateLimiterClient) {
+ public:
+ static scoped_ptr<LayerTreeHost> Create(
+ LayerTreeHostClient* client,
+ const LayerTreeSettings& settings,
+ scoped_refptr<base::SingleThreadTaskRunner> impl_task_runner);
+ virtual ~LayerTreeHost();
+
+ void SetLayerTreeHostClientReady();
+
+ // Returns true if any LayerTreeHost is alive.
+ static bool AnyLayerTreeHostInstanceExists();
+
+ void set_needs_filter_context() { needs_filter_context_ = true; }
+ bool needs_offscreen_context() const {
+ return needs_filter_context_;
+ }
+
+ // LayerTreeHost interface to Proxy.
+ void WillBeginFrame() { client_->WillBeginFrame(); }
+ void DidBeginFrame();
+ void UpdateClientAnimations(base::TimeTicks monotonic_frame_begin_time);
+ void AnimateLayers(base::TimeTicks monotonic_frame_begin_time);
+ void DidStopFlinging();
+ void Layout();
+ void BeginCommitOnImplThread(LayerTreeHostImpl* host_impl);
+ void FinishCommitOnImplThread(LayerTreeHostImpl* host_impl);
+ void WillCommit();
+ void CommitComplete();
+ scoped_ptr<OutputSurface> CreateOutputSurface();
+ virtual scoped_ptr<LayerTreeHostImpl> CreateLayerTreeHostImpl(
+ LayerTreeHostImplClient* client);
+ void DidLoseOutputSurface();
+ bool output_surface_lost() const { return output_surface_lost_; }
+ enum CreateResult {
+ CreateSucceeded,
+ CreateFailedButTryAgain,
+ CreateFailedAndGaveUp,
+ };
+ CreateResult OnCreateAndInitializeOutputSurfaceAttempted(bool success);
+ void DidCommitAndDrawFrame() { client_->DidCommitAndDrawFrame(); }
+ void DidCompleteSwapBuffers() { client_->DidCompleteSwapBuffers(); }
+ void DeleteContentsTexturesOnImplThread(ResourceProvider* resource_provider);
+ virtual void AcquireLayerTextures();
+ // Returns false if we should abort this frame due to initialization failure.
+ bool InitializeOutputSurfaceIfNeeded();
+ bool UpdateLayers(ResourceUpdateQueue* queue,
+ size_t contents_memory_limit_bytes);
+
+ LayerTreeHostClient* client() { return client_; }
+ const base::WeakPtr<InputHandler>& GetInputHandler() {
+ return input_handler_weak_ptr_;
+ }
+
+ void NotifyInputThrottledUntilCommit();
+
+ void Composite(base::TimeTicks frame_begin_time);
+
+ // Only used when compositing on the main thread.
+ void ScheduleComposite();
+
+ // Composites and attempts to read back the result into the provided
+ // buffer. If it wasn't possible, e.g. due to context lost, will return
+ // false.
+ bool CompositeAndReadback(void* pixels, gfx::Rect rect_in_device_viewport);
+
+ void FinishAllRendering();
+
+ void SetDeferCommits(bool defer_commits);
+
+ // Test only hook
+ virtual void DidDeferCommit();
+
+ int source_frame_number() const { return source_frame_number_; }
+
+ void SetNeedsDisplayOnAllLayers();
+
+ void CollectRenderingStats(RenderingStats* stats) const;
+
+ RenderingStatsInstrumentation* rendering_stats_instrumentation() const {
+ return rendering_stats_instrumentation_.get();
+ }
+
+ const RendererCapabilities& GetRendererCapabilities() const;
+
+ void SetNeedsAnimate();
+ virtual void SetNeedsUpdateLayers();
+ virtual void SetNeedsCommit();
+ virtual void SetNeedsFullTreeSync();
+ void SetNeedsRedraw();
+ void SetNeedsRedrawRect(gfx::Rect damage_rect);
+ bool CommitRequested() const;
+
+ void SetAnimationEvents(scoped_ptr<AnimationEventsVector> events,
+ base::Time wall_clock_time);
+
+ void SetRootLayer(scoped_refptr<Layer> root_layer);
+ Layer* root_layer() { return root_layer_.get(); }
+ const Layer* root_layer() const { return root_layer_.get(); }
+
+ const LayerTreeSettings& settings() const { return settings_; }
+
+ void SetDebugState(const LayerTreeDebugState& debug_state);
+ const LayerTreeDebugState& debug_state() const { return debug_state_; }
+
+ void SetViewportSize(gfx::Size device_viewport_size);
+ void SetOverdrawBottomHeight(float overdraw_bottom_height);
+
+ gfx::Size device_viewport_size() const { return device_viewport_size_; }
+ float overdraw_bottom_height() const { return overdraw_bottom_height_; }
+
+ void ApplyPageScaleDeltaFromImplSide(float page_scale_delta);
+ void SetPageScaleFactorAndLimits(float page_scale_factor,
+ float min_page_scale_factor,
+ float max_page_scale_factor);
+ float page_scale_factor() const { return page_scale_factor_; }
+
+ SkColor background_color() const { return background_color_; }
+ void set_background_color(SkColor color) { background_color_ = color; }
+
+ void set_has_transparent_background(bool transparent) {
+ has_transparent_background_ = transparent;
+ }
+
+ PrioritizedResourceManager* contents_texture_manager() const {
+ return contents_texture_manager_.get();
+ }
+
+ void SetVisible(bool visible);
+ bool visible() const { return visible_; }
+
+ void StartPageScaleAnimation(gfx::Vector2d target_offset,
+ bool use_anchor,
+ float scale,
+ base::TimeDelta duration);
+
+ void ApplyScrollAndScale(const ScrollAndScaleSet& info);
+
+ void SetImplTransform(const gfx::Transform& transform);
+ void SetLatencyInfo(const ui::LatencyInfo& latency_info);
+
+ virtual void StartRateLimiter(WebKit::WebGraphicsContext3D* context3d);
+ virtual void StopRateLimiter(WebKit::WebGraphicsContext3D* context3d);
+
+ // RateLimiterClient implementation.
+ virtual void RateLimit() OVERRIDE;
+
+ bool buffered_updates() const {
+ return settings_.max_partial_texture_updates !=
+ std::numeric_limits<size_t>::max();
+ }
+ bool RequestPartialTextureUpdate();
+
+ void SetDeviceScaleFactor(float device_scale_factor);
+ float device_scale_factor() const { return device_scale_factor_; }
+
+ void UpdateTopControlsState(TopControlsState constraints,
+ TopControlsState current,
+ bool animate);
+
+ HeadsUpDisplayLayer* hud_layer() const { return hud_layer_.get(); }
+
+ Proxy* proxy() const { return proxy_.get(); }
+
+ AnimationRegistrar* animation_registrar() const {
+ return animation_registrar_.get();
+ }
+
+ bool BlocksPendingCommit() const;
+
+ // Obtains a thorough dump of the LayerTreeHost as a value.
+ scoped_ptr<base::Value> AsValue() const;
+
+ bool in_paint_layer_contents() const { return in_paint_layer_contents_; }
+
+ // CreateUIResource creates a resource given a bitmap. The bitmap is
+ // generated via an interface function, which is called when initializing the
+ // resource and when the resource has been lost (due to lost context). The
+ // parameter of the interface is a single boolean, which indicates whether the
+ // resource has been lost or not. CreateUIResource returns an Id of the
+ // resource, which is always positive.
+ virtual UIResourceId CreateUIResource(UIResourceClient* client);
+ // Deletes a UI resource. May safely be called more than once.
+ virtual void DeleteUIResource(UIResourceId id);
+
+ bool UsingSharedMemoryResources();
+ int id() const { return tree_id_; }
+
+ protected:
+ LayerTreeHost(LayerTreeHostClient* client, const LayerTreeSettings& settings);
+ bool Initialize(scoped_refptr<base::SingleThreadTaskRunner> impl_task_runner);
+ bool InitializeForTesting(scoped_ptr<Proxy> proxy_for_testing);
+
+ private:
+ bool InitializeProxy(scoped_ptr<Proxy> proxy);
+
+ void PaintLayerContents(
+ const RenderSurfaceLayerList& render_surface_layer_list,
+ ResourceUpdateQueue* queue,
+ bool* did_paint_content,
+ bool* need_more_updates);
+ void PaintMasksForRenderSurface(Layer* render_surface_layer,
+ ResourceUpdateQueue* queue,
+ bool* did_paint_content,
+ bool* need_more_updates);
+ bool UpdateLayers(Layer* root_layer, ResourceUpdateQueue* queue);
+ void UpdateHudLayer();
+ void TriggerPrepaint();
+
+ void ReduceMemoryUsage();
+
+ void PrioritizeTextures(
+ const RenderSurfaceLayerList& render_surface_layer_list,
+ OverdrawMetrics* metrics);
+ void SetPrioritiesForSurfaces(size_t surface_memory_bytes);
+ void SetPrioritiesForLayers(const RenderSurfaceLayerList& update_list);
+ size_t CalculateMemoryForRenderSurfaces(
+ const RenderSurfaceLayerList& update_list);
+
+ bool AnimateLayersRecursive(Layer* current, base::TimeTicks time);
+
+ void UIResourceLost(UIResourceId id);
+
+ void DidLoseUIResources();
+
+ typedef base::hash_map<UIResourceId, UIResourceClient*> UIResourceClientMap;
+ UIResourceClientMap ui_resource_client_map_;
+ int next_ui_resource_id_;
+
+ typedef std::list<UIResourceRequest> UIResourceRequestQueue;
+ UIResourceRequestQueue ui_resource_request_queue_;
+
+ void CalculateLCDTextMetricsCallback(Layer* layer);
+
+ bool animating_;
+ bool needs_full_tree_sync_;
+ bool needs_filter_context_;
+
+ base::CancelableClosure prepaint_callback_;
+
+ LayerTreeHostClient* client_;
+ scoped_ptr<Proxy> proxy_;
+
+ int source_frame_number_;
+ scoped_ptr<RenderingStatsInstrumentation> rendering_stats_instrumentation_;
+
+ bool output_surface_can_be_initialized_;
+ bool output_surface_lost_;
+ int num_failed_recreate_attempts_;
+
+ scoped_refptr<Layer> root_layer_;
+ scoped_refptr<HeadsUpDisplayLayer> hud_layer_;
+
+ scoped_ptr<PrioritizedResourceManager> contents_texture_manager_;
+ scoped_ptr<PrioritizedResource> surface_memory_placeholder_;
+
+ base::WeakPtr<InputHandler> input_handler_weak_ptr_;
+ base::WeakPtr<TopControlsManager> top_controls_manager_weak_ptr_;
+
+ LayerTreeSettings settings_;
+ LayerTreeDebugState debug_state_;
+
+ gfx::Size device_viewport_size_;
+ float overdraw_bottom_height_;
+ float device_scale_factor_;
+
+ bool visible_;
+
+ typedef base::hash_map<WebKit::WebGraphicsContext3D*,
+ scoped_refptr<RateLimiter> > RateLimiterMap;
+ RateLimiterMap rate_limiters_;
+
+ float page_scale_factor_;
+ float min_page_scale_factor_;
+ float max_page_scale_factor_;
+ gfx::Transform impl_transform_;
+ bool trigger_idle_updates_;
+
+ SkColor background_color_;
+ bool has_transparent_background_;
+
+ typedef ScopedPtrVector<PrioritizedResource> TextureList;
+ size_t partial_texture_update_requests_;
+
+ scoped_ptr<AnimationRegistrar> animation_registrar_;
+
+ struct PendingPageScaleAnimation {
+ gfx::Vector2d target_offset;
+ bool use_anchor;
+ float scale;
+ base::TimeDelta duration;
+ };
+ scoped_ptr<PendingPageScaleAnimation> pending_page_scale_animation_;
+
+ bool in_paint_layer_contents_;
+
+ ui::LatencyInfo latency_info_;
+
+ static const int kTotalFramesToUseForLCDTextMetrics = 50;
+ int total_frames_used_for_lcd_text_metrics_;
+
+ struct LCDTextMetrics {
+ LCDTextMetrics()
+ : total_num_cc_layers(0),
+ total_num_cc_layers_can_use_lcd_text(0),
+ total_num_cc_layers_will_use_lcd_text(0) {}
+
+ int64 total_num_cc_layers;
+ int64 total_num_cc_layers_can_use_lcd_text;
+ int64 total_num_cc_layers_will_use_lcd_text;
+ };
+ LCDTextMetrics lcd_text_metrics_;
+ int tree_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(LayerTreeHost);
+};
+
+} // namespace cc
+
+#endif // CC_TREES_LAYER_TREE_HOST_H_
diff --git a/chromium/cc/trees/layer_tree_host_client.h b/chromium/cc/trees/layer_tree_host_client.h
new file mode 100644
index 00000000000..9e11f7cbd5d
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_host_client.h
@@ -0,0 +1,58 @@
+// 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.
+
+#ifndef CC_TREES_LAYER_TREE_HOST_CLIENT_H_
+#define CC_TREES_LAYER_TREE_HOST_CLIENT_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace gfx {
+class Vector2d;
+}
+
+namespace cc {
+class ContextProvider;
+class InputHandlerClient;
+class OutputSurface;
+
+class LayerTreeHostClient {
+ public:
+ virtual void WillBeginFrame() = 0;
+ // Marks finishing compositing-related tasks on the main thread. In threaded
+ // mode, this corresponds to DidCommit().
+ virtual void DidBeginFrame() = 0;
+ virtual void Animate(double frame_begin_time) = 0;
+ virtual void Layout() = 0;
+ virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta,
+ float page_scale) = 0;
+ // Creates an OutputSurface. If fallback is true, it should attempt to
+ // create an OutputSurface that is guaranteed to initialize correctly.
+ virtual scoped_ptr<OutputSurface> CreateOutputSurface(bool fallback) = 0;
+ virtual void DidInitializeOutputSurface(bool success) = 0;
+ virtual void WillCommit() = 0;
+ virtual void DidCommit() = 0;
+ virtual void DidCommitAndDrawFrame() = 0;
+ virtual void DidCompleteSwapBuffers() = 0;
+
+ // Used only in the single-threaded path.
+ virtual void ScheduleComposite() = 0;
+
+ // These must always return a valid ContextProvider. But the provider does not
+ // need to be capable of creating contexts.
+ virtual scoped_refptr<cc::ContextProvider>
+ OffscreenContextProviderForMainThread() = 0;
+ virtual scoped_refptr<cc::ContextProvider>
+ OffscreenContextProviderForCompositorThread() = 0;
+
+ // This hook is for testing.
+ virtual void DidFailToInitializeOutputSurface() {}
+
+ protected:
+ virtual ~LayerTreeHostClient() {}
+};
+
+} // namespace cc
+
+#endif // CC_TREES_LAYER_TREE_HOST_CLIENT_H_
diff --git a/chromium/cc/trees/layer_tree_host_common.cc b/chromium/cc/trees/layer_tree_host_common.cc
new file mode 100644
index 00000000000..68dadd5f9b9
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_host_common.cc
@@ -0,0 +1,1955 @@
+// Copyright 2011 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 "cc/trees/layer_tree_host_common.h"
+
+#include <algorithm>
+
+#include "base/debug/trace_event.h"
+#include "cc/base/math_util.h"
+#include "cc/layers/heads_up_display_layer_impl.h"
+#include "cc/layers/layer.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/layers/layer_iterator.h"
+#include "cc/layers/render_surface.h"
+#include "cc/layers/render_surface_impl.h"
+#include "cc/trees/layer_sorter.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "ui/gfx/point_conversions.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+
+ScrollAndScaleSet::ScrollAndScaleSet() {}
+
+ScrollAndScaleSet::~ScrollAndScaleSet() {}
+
+static void SortLayers(LayerList::iterator forst,
+ LayerList::iterator end,
+ void* layer_sorter) {
+ NOTREACHED();
+}
+
+static void SortLayers(LayerImplList::iterator first,
+ LayerImplList::iterator end,
+ LayerSorter* layer_sorter) {
+ DCHECK(layer_sorter);
+ TRACE_EVENT0("cc", "LayerTreeHostCommon::SortLayers");
+ layer_sorter->Sort(first, end);
+}
+
+inline gfx::Rect CalculateVisibleRectWithCachedLayerRect(
+ gfx::Rect target_surface_rect,
+ gfx::Rect layer_bound_rect,
+ gfx::Rect layer_rect_in_target_space,
+ const gfx::Transform& transform) {
+ if (layer_rect_in_target_space.IsEmpty())
+ return gfx::Rect();
+
+ // Is this layer fully contained within the target surface?
+ if (target_surface_rect.Contains(layer_rect_in_target_space))
+ return layer_bound_rect;
+
+ // If the layer doesn't fill up the entire surface, then find the part of
+ // the surface rect where the layer could be visible. This avoids trying to
+ // project surface rect points that are behind the projection point.
+ gfx::Rect minimal_surface_rect = target_surface_rect;
+ minimal_surface_rect.Intersect(layer_rect_in_target_space);
+
+ // Project the corners of the target surface rect into the layer space.
+ // This bounding rectangle may be larger than it needs to be (being
+ // axis-aligned), but is a reasonable filter on the space to consider.
+ // Non-invertible transforms will create an empty rect here.
+
+ gfx::Transform surface_to_layer(gfx::Transform::kSkipInitialization);
+ if (!transform.GetInverse(&surface_to_layer)) {
+ // TODO(shawnsingh): Some uninvertible transforms may be visible, but
+ // their behaviour is undefined thoughout the compositor. Make their
+ // behaviour well-defined and allow the visible content rect to be non-
+ // empty when needed.
+ return gfx::Rect();
+ }
+
+ gfx::Rect layer_rect = gfx::ToEnclosingRect(MathUtil::ProjectClippedRect(
+ surface_to_layer, gfx::RectF(minimal_surface_rect)));
+ layer_rect.Intersect(layer_bound_rect);
+ return layer_rect;
+}
+
+gfx::Rect LayerTreeHostCommon::CalculateVisibleRect(
+ gfx::Rect target_surface_rect,
+ gfx::Rect layer_bound_rect,
+ const gfx::Transform& transform) {
+ gfx::Rect layer_in_surface_space =
+ MathUtil::MapClippedRect(transform, layer_bound_rect);
+ return CalculateVisibleRectWithCachedLayerRect(
+ target_surface_rect, layer_bound_rect, layer_in_surface_space, transform);
+}
+
+template <typename LayerType> static inline bool IsRootLayer(LayerType* layer) {
+ return !layer->parent();
+}
+
+template <typename LayerType>
+static inline bool LayerIsInExisting3DRenderingContext(LayerType* layer) {
+ // According to current W3C spec on CSS transforms, a layer is part of an
+ // established 3d rendering context if its parent has transform-style of
+ // preserves-3d.
+ return layer->parent() && layer->parent()->preserves_3d();
+}
+
+template <typename LayerType>
+static bool IsRootLayerOfNewRenderingContext(LayerType* layer) {
+ // According to current W3C spec on CSS transforms (Section 6.1), a layer is
+ // the beginning of 3d rendering context if its parent does not have
+ // transform-style: preserve-3d, but this layer itself does.
+ if (layer->parent())
+ return !layer->parent()->preserves_3d() && layer->preserves_3d();
+
+ return layer->preserves_3d();
+}
+
+template <typename LayerType>
+static bool IsLayerBackFaceVisible(LayerType* layer) {
+ // The current W3C spec on CSS transforms says that backface visibility should
+ // be determined differently depending on whether the layer is in a "3d
+ // rendering context" or not. For Chromium code, we can determine whether we
+ // are in a 3d rendering context by checking if the parent preserves 3d.
+
+ if (LayerIsInExisting3DRenderingContext(layer))
+ return layer->draw_transform().IsBackFaceVisible();
+
+ // In this case, either the layer establishes a new 3d rendering context, or
+ // is not in a 3d rendering context at all.
+ return layer->transform().IsBackFaceVisible();
+}
+
+template <typename LayerType>
+static bool IsSurfaceBackFaceVisible(LayerType* layer,
+ const gfx::Transform& draw_transform) {
+ if (LayerIsInExisting3DRenderingContext(layer))
+ return draw_transform.IsBackFaceVisible();
+
+ if (IsRootLayerOfNewRenderingContext(layer))
+ return layer->transform().IsBackFaceVisible();
+
+ // If the render_surface is not part of a new or existing rendering context,
+ // then the layers that contribute to this surface will decide back-face
+ // visibility for themselves.
+ return false;
+}
+
+template <typename LayerType>
+static inline bool LayerClipsSubtree(LayerType* layer) {
+ return layer->masks_to_bounds() || layer->mask_layer();
+}
+
+template <typename LayerType>
+static gfx::Rect CalculateVisibleContentRect(
+ LayerType* layer,
+ gfx::Rect clip_rect_of_target_surface_in_target_space,
+ gfx::Rect layer_rect_in_target_space) {
+ DCHECK(layer->render_target());
+
+ // Nothing is visible if the layer bounds are empty.
+ if (!layer->DrawsContent() || layer->content_bounds().IsEmpty() ||
+ layer->drawable_content_rect().IsEmpty())
+ return gfx::Rect();
+
+ // Compute visible bounds in target surface space.
+ gfx::Rect visible_rect_in_target_surface_space =
+ layer->drawable_content_rect();
+
+ if (!layer->render_target()->render_surface()->clip_rect().IsEmpty()) {
+ // The |layer| L has a target T which owns a surface Ts. The surface Ts
+ // has a target TsT.
+ //
+ // In this case the target surface Ts does clip the layer L that contributes
+ // to it. So, we have to convert the clip rect of Ts from the target space
+ // of Ts (that is the space of TsT), to the current render target's space
+ // (that is the space of T). This conversion is done outside this function
+ // so that it can be cached instead of computing it redundantly for every
+ // layer.
+ visible_rect_in_target_surface_space.Intersect(
+ clip_rect_of_target_surface_in_target_space);
+ }
+
+ if (visible_rect_in_target_surface_space.IsEmpty())
+ return gfx::Rect();
+
+ return CalculateVisibleRectWithCachedLayerRect(
+ visible_rect_in_target_surface_space,
+ gfx::Rect(layer->content_bounds()),
+ layer_rect_in_target_space,
+ layer->draw_transform());
+}
+
+static inline bool TransformToParentIsKnown(LayerImpl* layer) { return true; }
+
+static inline bool TransformToParentIsKnown(Layer* layer) {
+ return !layer->TransformIsAnimating();
+}
+
+static inline bool TransformToScreenIsKnown(LayerImpl* layer) { return true; }
+
+static inline bool TransformToScreenIsKnown(Layer* layer) {
+ return !layer->screen_space_transform_is_animating();
+}
+
+template <typename LayerType>
+static bool LayerShouldBeSkipped(LayerType* layer,
+ bool layer_is_visible) {
+ // Layers can be skipped if any of these conditions are met.
+ // - is not visible due to it or one of its ancestors being hidden.
+ // - does not draw content.
+ // - is transparent
+ // - has empty bounds
+ // - the layer is not double-sided, but its back face is visible.
+ //
+ // Some additional conditions need to be computed at a later point after the
+ // recursion is finished.
+ // - the intersection of render_surface content and layer clip_rect is empty
+ // - the visible_content_rect is empty
+ //
+ // Note, if the layer should not have been drawn due to being fully
+ // transparent, we would have skipped the entire subtree and never made it
+ // into this function, so it is safe to omit this check here.
+
+ if (!layer_is_visible)
+ return true;
+
+ if (!layer->DrawsContent() || layer->bounds().IsEmpty())
+ return true;
+
+ LayerType* backface_test_layer = layer;
+ if (layer->use_parent_backface_visibility()) {
+ DCHECK(layer->parent());
+ DCHECK(!layer->parent()->use_parent_backface_visibility());
+ backface_test_layer = layer->parent();
+ }
+
+ // The layer should not be drawn if (1) it is not double-sided and (2) the
+ // back of the layer is known to be facing the screen.
+ if (!backface_test_layer->double_sided() &&
+ TransformToScreenIsKnown(backface_test_layer) &&
+ IsLayerBackFaceVisible(backface_test_layer))
+ return true;
+
+ return false;
+}
+
+static inline bool SubtreeShouldBeSkipped(LayerImpl* layer,
+ bool layer_is_visible) {
+ // When we need to do a readback/copy of a layer's output, we can not skip
+ // it or any of its ancestors.
+ if (layer->draw_properties().layer_or_descendant_has_copy_request)
+ return false;
+
+ // If the layer is not visible, then skip it and its subtree.
+ if (!layer_is_visible)
+ return true;
+
+ // If layer is on the pending tree and opacity is being animated then
+ // this subtree can't be skipped as we need to create, prioritize and
+ // include tiles for this layer when deciding if tree can be activated.
+ if (layer->layer_tree_impl()->IsPendingTree() && layer->OpacityIsAnimating())
+ return false;
+
+ // The opacity of a layer always applies to its children (either implicitly
+ // via a render surface or explicitly if the parent preserves 3D), so the
+ // entire subtree can be skipped if this layer is fully transparent.
+ return !layer->opacity();
+}
+
+static inline bool SubtreeShouldBeSkipped(Layer* layer,
+ bool layer_is_visible) {
+ // When we need to do a readback/copy of a layer's output, we can not skip
+ // it or any of its ancestors.
+ if (layer->draw_properties().layer_or_descendant_has_copy_request)
+ return false;
+
+ // If the layer is not visible, then skip it and its subtree.
+ if (!layer_is_visible)
+ return true;
+
+ // If the opacity is being animated then the opacity on the main thread is
+ // unreliable (since the impl thread may be using a different opacity), so it
+ // should not be trusted.
+ // In particular, it should not cause the subtree to be skipped.
+ // Similarly, for layers that might animate opacity using an impl-only
+ // animation, their subtree should also not be skipped.
+ return !layer->opacity() && !layer->OpacityIsAnimating() &&
+ !layer->OpacityCanAnimateOnImplThread();
+}
+
+// Called on each layer that could be drawn after all information from
+// CalcDrawProperties has been updated on that layer. May have some false
+// positives (e.g. layers get this called on them but don't actually get drawn).
+static inline void UpdateTilePrioritiesForLayer(LayerImpl* layer) {
+ layer->UpdateTilePriorities();
+
+ // Mask layers don't get this call, so explicitly update them so they can
+ // kick off tile rasterization.
+ if (layer->mask_layer())
+ layer->mask_layer()->UpdateTilePriorities();
+ if (layer->replica_layer() && layer->replica_layer()->mask_layer())
+ layer->replica_layer()->mask_layer()->UpdateTilePriorities();
+}
+
+static inline void UpdateTilePrioritiesForLayer(Layer* layer) {}
+
+static inline void SavePaintPropertiesLayer(LayerImpl* layer) {}
+
+static inline void SavePaintPropertiesLayer(Layer* layer) {
+ layer->SavePaintProperties();
+
+ if (layer->mask_layer())
+ layer->mask_layer()->SavePaintProperties();
+ if (layer->replica_layer() && layer->replica_layer()->mask_layer())
+ layer->replica_layer()->mask_layer()->SavePaintProperties();
+}
+
+template <typename LayerType>
+static bool SubtreeShouldRenderToSeparateSurface(
+ LayerType* layer,
+ bool axis_aligned_with_respect_to_parent) {
+ //
+ // A layer and its descendants should render onto a new RenderSurfaceImpl if
+ // any of these rules hold:
+ //
+
+ // The root layer owns a render surface, but it never acts as a contributing
+ // surface to another render target. Compositor features that are applied via
+ // a contributing surface can not be applied to the root layer. In order to
+ // use these effects, another child of the root would need to be introduced
+ // in order to act as a contributing surface to the root layer's surface.
+ bool is_root = IsRootLayer(layer);
+
+ // If the layer uses a mask.
+ if (layer->mask_layer()) {
+ DCHECK(!is_root);
+ return true;
+ }
+
+ // If the layer has a reflection.
+ if (layer->replica_layer()) {
+ DCHECK(!is_root);
+ return true;
+ }
+
+ // If the layer uses a CSS filter.
+ if (!layer->filters().IsEmpty() || !layer->background_filters().IsEmpty() ||
+ layer->filter()) {
+ DCHECK(!is_root);
+ return true;
+ }
+
+ int num_descendants_that_draw_content =
+ layer->draw_properties().num_descendants_that_draw_content;
+
+ // If the layer flattens its subtree (i.e. the layer doesn't preserve-3d), but
+ // it is treated as a 3D object by its parent (i.e. parent does preserve-3d).
+ if (LayerIsInExisting3DRenderingContext(layer) && !layer->preserves_3d() &&
+ num_descendants_that_draw_content > 0) {
+ TRACE_EVENT_INSTANT0(
+ "cc",
+ "LayerTreeHostCommon::SubtreeShouldRenderToSeparateSurface flattening",
+ TRACE_EVENT_SCOPE_THREAD);
+ DCHECK(!is_root);
+ return true;
+ }
+
+ // If the layer clips its descendants but it is not axis-aligned with respect
+ // to its parent.
+ bool layer_clips_external_content =
+ LayerClipsSubtree(layer) || layer->HasDelegatedContent();
+ if (layer_clips_external_content && !axis_aligned_with_respect_to_parent &&
+ !layer->draw_properties().descendants_can_clip_selves) {
+ TRACE_EVENT_INSTANT0(
+ "cc",
+ "LayerTreeHostCommon::SubtreeShouldRenderToSeparateSurface clipping",
+ TRACE_EVENT_SCOPE_THREAD);
+ DCHECK(!is_root);
+ return true;
+ }
+
+ // If the layer has some translucency and does not have a preserves-3d
+ // transform style. This condition only needs a render surface if two or more
+ // layers in the subtree overlap. But checking layer overlaps is unnecessarily
+ // costly so instead we conservatively create a surface whenever at least two
+ // layers draw content for this subtree.
+ bool at_least_two_layers_in_subtree_draw_content =
+ num_descendants_that_draw_content > 0 &&
+ (layer->DrawsContent() || num_descendants_that_draw_content > 1);
+
+ if (layer->opacity() != 1.f && !layer->preserves_3d() &&
+ at_least_two_layers_in_subtree_draw_content) {
+ TRACE_EVENT_INSTANT0(
+ "cc",
+ "LayerTreeHostCommon::SubtreeShouldRenderToSeparateSurface opacity",
+ TRACE_EVENT_SCOPE_THREAD);
+ DCHECK(!is_root);
+ return true;
+ }
+
+ // The root layer should always have a render_surface.
+ if (is_root)
+ return true;
+
+ //
+ // These are allowed on the root surface, as they don't require the surface to
+ // be used as a contributing surface in order to apply correctly.
+ //
+
+ // If we force it.
+ if (layer->force_render_surface())
+ return true;
+
+ // If we'll make a copy of the layer's contents.
+ if (layer->HasCopyRequest())
+ return true;
+
+ return false;
+}
+
+static LayerImpl* NextTargetSurface(LayerImpl* layer) {
+ return layer->parent() ? layer->parent()->render_target() : 0;
+}
+
+// This function returns a translation matrix that can be applied on a vector
+// that's in the layer's target surface coordinate, while the position offset is
+// specified in some ancestor layer's coordinate.
+gfx::Transform ComputeSizeDeltaCompensation(
+ LayerImpl* layer,
+ LayerImpl* container,
+ gfx::Vector2dF position_offset) {
+ gfx::Transform result_transform;
+
+ // To apply a translate in the container's layer space,
+ // the following steps need to be done:
+ // Step 1a. transform from target surface space to the container's target
+ // surface space
+ // Step 1b. transform from container's target surface space to the
+ // container's layer space
+ // Step 2. apply the compensation
+ // Step 3. transform back to target surface space
+
+ gfx::Transform target_surface_space_to_container_layer_space;
+ // Calculate step 1a
+ LayerImpl* container_target_surface = container->render_target();
+ for (LayerImpl* current_target_surface = NextTargetSurface(layer);
+ current_target_surface &&
+ current_target_surface != container_target_surface;
+ current_target_surface = NextTargetSurface(current_target_surface)) {
+ // Note: Concat is used here to convert the result coordinate space from
+ // current render surface to the next render surface.
+ target_surface_space_to_container_layer_space.ConcatTransform(
+ current_target_surface->render_surface()->draw_transform());
+ }
+ // Calculate step 1b
+ gfx::Transform container_layer_space_to_container_target_surface_space =
+ container->draw_transform();
+ container_layer_space_to_container_target_surface_space.Scale(
+ container->contents_scale_x(), container->contents_scale_y());
+
+ gfx::Transform container_target_surface_space_to_container_layer_space;
+ if (container_layer_space_to_container_target_surface_space.GetInverse(
+ &container_target_surface_space_to_container_layer_space)) {
+ // Note: Again, Concat is used to conver the result coordinate space from
+ // the container render surface to the container layer.
+ target_surface_space_to_container_layer_space.ConcatTransform(
+ container_target_surface_space_to_container_layer_space);
+ }
+
+ // Apply step 3
+ gfx::Transform container_layer_space_to_target_surface_space;
+ if (target_surface_space_to_container_layer_space.GetInverse(
+ &container_layer_space_to_target_surface_space)) {
+ result_transform.PreconcatTransform(
+ container_layer_space_to_target_surface_space);
+ } else {
+ // TODO(shawnsingh): A non-invertible matrix could still make meaningful
+ // projection. For example ScaleZ(0) is non-invertible but the layer is
+ // still visible.
+ return gfx::Transform();
+ }
+
+ // Apply step 2
+ result_transform.Translate(position_offset.x(), position_offset.y());
+
+ // Apply step 1
+ result_transform.PreconcatTransform(
+ target_surface_space_to_container_layer_space);
+
+ return result_transform;
+}
+
+void ApplyPositionAdjustment(
+ Layer* layer,
+ Layer* container,
+ const gfx::Transform& scroll_compensation,
+ gfx::Transform* combined_transform) {}
+void ApplyPositionAdjustment(
+ LayerImpl* layer,
+ LayerImpl* container,
+ const gfx::Transform& scroll_compensation,
+ gfx::Transform* combined_transform) {
+ if (!layer->position_constraint().is_fixed_position())
+ return;
+
+ // Special case: this layer is a composited fixed-position layer; we need to
+ // explicitly compensate for all ancestors' nonzero scroll_deltas to keep
+ // this layer fixed correctly.
+ // Note carefully: this is Concat, not Preconcat
+ // (current_scroll_compensation * combined_transform).
+ combined_transform->ConcatTransform(scroll_compensation);
+
+ // For right-edge or bottom-edge anchored fixed position layers,
+ // the layer should relocate itself if the container changes its size.
+ bool fixed_to_right_edge =
+ layer->position_constraint().is_fixed_to_right_edge();
+ bool fixed_to_bottom_edge =
+ layer->position_constraint().is_fixed_to_bottom_edge();
+ gfx::Vector2dF position_offset = container->fixed_container_size_delta();
+ position_offset.set_x(fixed_to_right_edge ? position_offset.x() : 0);
+ position_offset.set_y(fixed_to_bottom_edge ? position_offset.y() : 0);
+ if (position_offset.IsZero())
+ return;
+
+ // Note: Again, this is Concat. The compensation matrix will be applied on
+ // the vector in target surface space.
+ combined_transform->ConcatTransform(
+ ComputeSizeDeltaCompensation(layer, container, position_offset));
+}
+
+gfx::Transform ComputeScrollCompensationForThisLayer(
+ LayerImpl* scrolling_layer,
+ const gfx::Transform& parent_matrix) {
+ // For every layer that has non-zero scroll_delta, we have to compute a
+ // transform that can undo the scroll_delta translation. In particular, we
+ // want this matrix to premultiply a fixed-position layer's parent_matrix, so
+ // we design this transform in three steps as follows. The steps described
+ // here apply from right-to-left, so Step 1 would be the right-most matrix:
+ //
+ // Step 1. transform from target surface space to the exact space where
+ // scroll_delta is actually applied.
+ // -- this is inverse of parent_matrix
+ // Step 2. undo the scroll_delta
+ // -- this is just a translation by scroll_delta.
+ // Step 3. transform back to target surface space.
+ // -- this transform is the parent_matrix
+ //
+ // These steps create a matrix that both start and end in target surface
+ // space. So this matrix can pre-multiply any fixed-position layer's
+ // draw_transform to undo the scroll_deltas -- as long as that fixed position
+ // layer is fixed onto the same render_target as this scrolling_layer.
+ //
+
+ gfx::Transform scroll_compensation_for_this_layer = parent_matrix; // Step 3
+ scroll_compensation_for_this_layer.Translate(
+ scrolling_layer->ScrollDelta().x(),
+ scrolling_layer->ScrollDelta().y()); // Step 2
+
+ gfx::Transform inverse_parent_matrix(gfx::Transform::kSkipInitialization);
+ if (!parent_matrix.GetInverse(&inverse_parent_matrix)) {
+ // TODO(shawnsingh): Either we need to handle uninvertible transforms
+ // here, or DCHECK that the transform is invertible.
+ }
+ scroll_compensation_for_this_layer.PreconcatTransform(
+ inverse_parent_matrix); // Step 1
+ return scroll_compensation_for_this_layer;
+}
+
+gfx::Transform ComputeScrollCompensationMatrixForChildren(
+ Layer* current_layer,
+ const gfx::Transform& current_parent_matrix,
+ const gfx::Transform& current_scroll_compensation) {
+ // The main thread (i.e. Layer) does not need to worry about scroll
+ // compensation. So we can just return an identity matrix here.
+ return gfx::Transform();
+}
+
+gfx::Transform ComputeScrollCompensationMatrixForChildren(
+ LayerImpl* layer,
+ const gfx::Transform& parent_matrix,
+ const gfx::Transform& current_scroll_compensation_matrix) {
+ // "Total scroll compensation" is the transform needed to cancel out all
+ // scroll_delta translations that occurred since the nearest container layer,
+ // even if there are render_surfaces in-between.
+ //
+ // There are some edge cases to be aware of, that are not explicit in the
+ // code:
+ // - A layer that is both a fixed-position and container should not be its
+ // own container, instead, that means it is fixed to an ancestor, and is a
+ // container for any fixed-position descendants.
+ // - A layer that is a fixed-position container and has a render_surface
+ // should behave the same as a container without a render_surface, the
+ // render_surface is irrelevant in that case.
+ // - A layer that does not have an explicit container is simply fixed to the
+ // viewport. (i.e. the root render_surface.)
+ // - If the fixed-position layer has its own render_surface, then the
+ // render_surface is the one who gets fixed.
+ //
+ // This function needs to be called AFTER layers create their own
+ // render_surfaces.
+ //
+
+ // Avoid the overheads (including stack allocation and matrix
+ // initialization/copy) if we know that the scroll compensation doesn't need
+ // to be reset or adjusted.
+ if (!layer->IsContainerForFixedPositionLayers() &&
+ layer->ScrollDelta().IsZero() && !layer->render_surface())
+ return current_scroll_compensation_matrix;
+
+ // Start as identity matrix.
+ gfx::Transform next_scroll_compensation_matrix;
+
+ // If this layer is not a container, then it inherits the existing scroll
+ // compensations.
+ if (!layer->IsContainerForFixedPositionLayers())
+ next_scroll_compensation_matrix = current_scroll_compensation_matrix;
+
+ // If the current layer has a non-zero scroll_delta, then we should compute
+ // its local scroll compensation and accumulate it to the
+ // next_scroll_compensation_matrix.
+ if (!layer->ScrollDelta().IsZero()) {
+ gfx::Transform scroll_compensation_for_this_layer =
+ ComputeScrollCompensationForThisLayer(
+ layer, parent_matrix);
+ next_scroll_compensation_matrix.PreconcatTransform(
+ scroll_compensation_for_this_layer);
+ }
+
+ // If the layer created its own render_surface, we have to adjust
+ // next_scroll_compensation_matrix. The adjustment allows us to continue
+ // using the scroll compensation on the next surface.
+ // Step 1 (right-most in the math): transform from the new surface to the
+ // original ancestor surface
+ // Step 2: apply the scroll compensation
+ // Step 3: transform back to the new surface.
+ if (layer->render_surface() &&
+ !next_scroll_compensation_matrix.IsIdentity()) {
+ gfx::Transform inverse_surface_draw_transform(
+ gfx::Transform::kSkipInitialization);
+ if (!layer->render_surface()->draw_transform().GetInverse(
+ &inverse_surface_draw_transform)) {
+ // TODO(shawnsingh): Either we need to handle uninvertible transforms
+ // here, or DCHECK that the transform is invertible.
+ }
+ next_scroll_compensation_matrix =
+ inverse_surface_draw_transform * next_scroll_compensation_matrix *
+ layer->render_surface()->draw_transform();
+ }
+
+ return next_scroll_compensation_matrix;
+}
+
+template <typename LayerType>
+static inline void CalculateContentsScale(LayerType* layer,
+ float contents_scale,
+ float device_scale_factor,
+ float page_scale_factor,
+ bool animating_transform_to_screen) {
+ layer->CalculateContentsScale(contents_scale,
+ device_scale_factor,
+ page_scale_factor,
+ animating_transform_to_screen,
+ &layer->draw_properties().contents_scale_x,
+ &layer->draw_properties().contents_scale_y,
+ &layer->draw_properties().content_bounds);
+
+ LayerType* mask_layer = layer->mask_layer();
+ if (mask_layer) {
+ mask_layer->CalculateContentsScale(
+ contents_scale,
+ device_scale_factor,
+ page_scale_factor,
+ animating_transform_to_screen,
+ &mask_layer->draw_properties().contents_scale_x,
+ &mask_layer->draw_properties().contents_scale_y,
+ &mask_layer->draw_properties().content_bounds);
+ }
+
+ LayerType* replica_mask_layer =
+ layer->replica_layer() ? layer->replica_layer()->mask_layer() : NULL;
+ if (replica_mask_layer) {
+ replica_mask_layer->CalculateContentsScale(
+ contents_scale,
+ device_scale_factor,
+ page_scale_factor,
+ animating_transform_to_screen,
+ &replica_mask_layer->draw_properties().contents_scale_x,
+ &replica_mask_layer->draw_properties().contents_scale_y,
+ &replica_mask_layer->draw_properties().content_bounds);
+ }
+}
+
+static inline void UpdateLayerContentsScale(
+ LayerImpl* layer,
+ bool can_adjust_raster_scale,
+ float ideal_contents_scale,
+ float device_scale_factor,
+ float page_scale_factor,
+ bool animating_transform_to_screen) {
+ CalculateContentsScale(layer,
+ ideal_contents_scale,
+ device_scale_factor,
+ page_scale_factor,
+ animating_transform_to_screen);
+}
+
+static inline void UpdateLayerContentsScale(
+ Layer* layer,
+ bool can_adjust_raster_scale,
+ float ideal_contents_scale,
+ float device_scale_factor,
+ float page_scale_factor,
+ bool animating_transform_to_screen) {
+ if (can_adjust_raster_scale) {
+ float ideal_raster_scale =
+ ideal_contents_scale / (device_scale_factor * page_scale_factor);
+
+ bool need_to_set_raster_scale = layer->raster_scale_is_unknown();
+
+ // If we've previously saved a raster_scale but the ideal changes, things
+ // are unpredictable and we should just use 1.
+ if (!need_to_set_raster_scale && layer->raster_scale() != 1.f &&
+ ideal_raster_scale != layer->raster_scale()) {
+ ideal_raster_scale = 1.f;
+ need_to_set_raster_scale = true;
+ }
+
+ if (need_to_set_raster_scale) {
+ bool use_and_save_ideal_scale =
+ ideal_raster_scale >= 1.f && !animating_transform_to_screen;
+ if (use_and_save_ideal_scale)
+ layer->set_raster_scale(ideal_raster_scale);
+ }
+ }
+
+ float raster_scale = 1.f;
+ if (!layer->raster_scale_is_unknown())
+ raster_scale = layer->raster_scale();
+
+
+ float contents_scale = raster_scale * device_scale_factor * page_scale_factor;
+ CalculateContentsScale(layer,
+ contents_scale,
+ device_scale_factor,
+ page_scale_factor,
+ animating_transform_to_screen);
+}
+
+static inline RenderSurface* CreateOrReuseRenderSurface(Layer* layer) {
+ // The render surface should always be new on the main thread, as the
+ // RenderSurfaceLayerList should be a new empty list when given to
+ // CalculateDrawProperties.
+ DCHECK(!layer->render_surface());
+ layer->CreateRenderSurface();
+ return layer->render_surface();
+}
+
+static inline RenderSurfaceImpl* CreateOrReuseRenderSurface(LayerImpl* layer) {
+ if (!layer->render_surface()) {
+ layer->CreateRenderSurface();
+ return layer->render_surface();
+ }
+
+ layer->render_surface()->ClearLayerLists();
+ return layer->render_surface();
+}
+
+template <typename LayerType, typename LayerList>
+static inline void RemoveSurfaceForEarlyExit(
+ LayerType* layer_to_remove,
+ LayerList* render_surface_layer_list) {
+ DCHECK(layer_to_remove->render_surface());
+ // Technically, we know that the layer we want to remove should be
+ // at the back of the render_surface_layer_list. However, we have had
+ // bugs before that added unnecessary layers here
+ // (https://bugs.webkit.org/show_bug.cgi?id=74147), but that causes
+ // things to crash. So here we proactively remove any additional
+ // layers from the end of the list.
+ while (render_surface_layer_list->back() != layer_to_remove) {
+ render_surface_layer_list->back()->ClearRenderSurface();
+ render_surface_layer_list->pop_back();
+ }
+ DCHECK_EQ(render_surface_layer_list->back(), layer_to_remove);
+ render_surface_layer_list->pop_back();
+ layer_to_remove->ClearRenderSurface();
+}
+
+struct PreCalculateMetaInformationRecursiveData {
+ bool layer_or_descendant_has_copy_request;
+
+ PreCalculateMetaInformationRecursiveData()
+ : layer_or_descendant_has_copy_request(false) {}
+
+ void Merge(const PreCalculateMetaInformationRecursiveData& data) {
+ layer_or_descendant_has_copy_request |=
+ data.layer_or_descendant_has_copy_request;
+ }
+};
+
+// Recursively walks the layer tree to compute any information that is needed
+// before doing the main recursion.
+template <typename LayerType>
+static void PreCalculateMetaInformation(
+ LayerType* layer,
+ PreCalculateMetaInformationRecursiveData* recursive_data) {
+ bool has_delegated_content = layer->HasDelegatedContent();
+ int num_descendants_that_draw_content = 0;
+ bool descendants_can_clip_selves = true;
+
+ if (has_delegated_content) {
+ // Layers with delegated content need to be treated as if they have as
+ // many children as the number of layers they own delegated quads for.
+ // Since we don't know this number right now, we choose one that acts like
+ // infinity for our purposes.
+ num_descendants_that_draw_content = 1000;
+ descendants_can_clip_selves = false;
+ }
+
+ for (size_t i = 0; i < layer->children().size(); ++i) {
+ LayerType* child_layer =
+ LayerTreeHostCommon::get_child_as_raw_ptr(layer->children(), i);
+
+ PreCalculateMetaInformationRecursiveData data_for_child;
+ PreCalculateMetaInformation(child_layer, &data_for_child);
+
+ if (!has_delegated_content) {
+ bool sublayer_transform_prevents_clip =
+ !layer->sublayer_transform().IsPositiveScaleOrTranslation();
+
+ num_descendants_that_draw_content += child_layer->DrawsContent() ? 1 : 0;
+ num_descendants_that_draw_content +=
+ child_layer->draw_properties().num_descendants_that_draw_content;
+
+ if ((child_layer->DrawsContent() && !child_layer->CanClipSelf()) ||
+ !child_layer->draw_properties().descendants_can_clip_selves ||
+ sublayer_transform_prevents_clip ||
+ !child_layer->transform().IsPositiveScaleOrTranslation())
+ descendants_can_clip_selves = false;
+ }
+
+ recursive_data->Merge(data_for_child);
+ }
+
+ if (layer->HasCopyRequest())
+ recursive_data->layer_or_descendant_has_copy_request = true;
+
+ layer->draw_properties().num_descendants_that_draw_content =
+ num_descendants_that_draw_content;
+ layer->draw_properties().descendants_can_clip_selves =
+ descendants_can_clip_selves;
+ layer->draw_properties().layer_or_descendant_has_copy_request =
+ recursive_data->layer_or_descendant_has_copy_request;
+}
+
+static void RoundTranslationComponents(gfx::Transform* transform) {
+ transform->matrix().
+ setDouble(0, 3, MathUtil::Round(transform->matrix().getDouble(0, 3)));
+ transform->matrix().
+ setDouble(1, 3, MathUtil::Round(transform->matrix().getDouble(1, 3)));
+}
+
+template <typename LayerType>
+struct SubtreeGlobals {
+ LayerSorter* layer_sorter;
+ int max_texture_size;
+ float device_scale_factor;
+ float page_scale_factor;
+ LayerType* page_scale_application_layer;
+ bool can_adjust_raster_scales;
+};
+
+template<typename LayerType, typename RenderSurfaceType>
+struct DataForRecursion {
+ // The accumulated sequence of transforms a layer will use to determine its
+ // own draw transform.
+ gfx::Transform parent_matrix;
+
+ // The accumulated sequence of transforms a layer will use to determine its
+ // own screen-space transform.
+ gfx::Transform full_hierarchy_matrix;
+
+ // The transform that removes all scrolling that may have occurred between a
+ // fixed-position layer and its container, so that the layer actually does
+ // remain fixed.
+ gfx::Transform scroll_compensation_matrix;
+
+ // The ancestor that would be the container for any fixed-position / sticky
+ // layers.
+ LayerType* fixed_container;
+
+ // This is the normal clip rect that is propagated from parent to child.
+ gfx::Rect clip_rect_in_target_space;
+
+ // When the layer's children want to compute their visible content rect, they
+ // want to know what their target surface's clip rect will be. BUT - they
+ // want to know this clip rect represented in their own target space. This
+ // requires inverse-projecting the surface's clip rect from the surface's
+ // render target space down to the surface's own space. Instead of computing
+ // this value redundantly for each child layer, it is computed only once
+ // while dealing with the parent layer, and then this precomputed value is
+ // passed down the recursion to the children that actually use it.
+ gfx::Rect clip_rect_of_target_surface_in_target_space;
+
+ bool ancestor_clips_subtree;
+ RenderSurfaceType* nearest_ancestor_surface_that_moves_pixels;
+ bool in_subtree_of_page_scale_application_layer;
+ bool subtree_can_use_lcd_text;
+ bool subtree_is_visible_from_ancestor;
+};
+
+// Recursively walks the layer tree starting at the given node and computes all
+// the necessary transformations, clip rects, render surfaces, etc.
+template <typename LayerType,
+ typename LayerListType,
+ typename RenderSurfaceType>
+static void CalculateDrawPropertiesInternal(
+ LayerType* layer,
+ const SubtreeGlobals<LayerType>& globals,
+ const DataForRecursion<LayerType, RenderSurfaceType>& data_from_ancestor,
+ LayerListType* render_surface_layer_list,
+ LayerListType* layer_list,
+ gfx::Rect* drawable_content_rect_of_subtree) {
+ // This function computes the new matrix transformations recursively for this
+ // layer and all its descendants. It also computes the appropriate render
+ // surfaces.
+ // Some important points to remember:
+ //
+ // 0. Here, transforms are notated in Matrix x Vector order, and in words we
+ // describe what the transform does from left to right.
+ //
+ // 1. In our terminology, the "layer origin" refers to the top-left corner of
+ // a layer, and the positive Y-axis points downwards. This interpretation is
+ // valid because the orthographic projection applied at draw time flips the Y
+ // axis appropriately.
+ //
+ // 2. The anchor point, when given as a PointF object, is specified in "unit
+ // layer space", where the bounds of the layer map to [0, 1]. However, as a
+ // Transform object, the transform to the anchor point is specified in "layer
+ // space", where the bounds of the layer map to [bounds.width(),
+ // bounds.height()].
+ //
+ // 3. Definition of various transforms used:
+ // M[parent] is the parent matrix, with respect to the nearest render
+ // surface, passed down recursively.
+ //
+ // M[root] is the full hierarchy, with respect to the root, passed down
+ // recursively.
+ //
+ // Tr[origin] is the translation matrix from the parent's origin to
+ // this layer's origin.
+ //
+ // Tr[origin2anchor] is the translation from the layer's origin to its
+ // anchor point
+ //
+ // Tr[origin2center] is the translation from the layer's origin to its
+ // center
+ //
+ // M[layer] is the layer's matrix (applied at the anchor point)
+ //
+ // M[sublayer] is the layer's sublayer transform (also applied at the
+ // layer's anchor point)
+ //
+ // S[layer2content] is the ratio of a layer's content_bounds() to its
+ // Bounds().
+ //
+ // Some composite transforms can help in understanding the sequence of
+ // transforms:
+ // composite_layer_transform = Tr[origin2anchor] * M[layer] *
+ // Tr[origin2anchor].inverse()
+ //
+ // composite_sublayer_transform = Tr[origin2anchor] * M[sublayer] *
+ // Tr[origin2anchor].inverse()
+ //
+ // 4. When a layer (or render surface) is drawn, it is drawn into a "target
+ // render surface". Therefore the draw transform does not necessarily
+ // transform from screen space to local layer space. Instead, the draw
+ // transform is the transform between the "target render surface space" and
+ // local layer space. Note that render surfaces, except for the root, also
+ // draw themselves into a different target render surface, and so their draw
+ // transform and origin transforms are also described with respect to the
+ // target.
+ //
+ // Using these definitions, then:
+ //
+ // The draw transform for the layer is:
+ // M[draw] = M[parent] * Tr[origin] * composite_layer_transform *
+ // S[layer2content] = M[parent] * Tr[layer->position() + anchor] *
+ // M[layer] * Tr[anchor2origin] * S[layer2content]
+ //
+ // Interpreting the math left-to-right, this transforms from the
+ // layer's render surface to the origin of the layer in content space.
+ //
+ // The screen space transform is:
+ // M[screenspace] = M[root] * Tr[origin] * composite_layer_transform *
+ // S[layer2content]
+ // = M[root] * Tr[layer->position() + anchor] * M[layer]
+ // * Tr[anchor2origin] * S[layer2content]
+ //
+ // Interpreting the math left-to-right, this transforms from the root
+ // render surface's content space to the origin of the layer in content
+ // space.
+ //
+ // The transform hierarchy that is passed on to children (i.e. the child's
+ // parent_matrix) is:
+ // M[parent]_for_child = M[parent] * Tr[origin] *
+ // composite_layer_transform * composite_sublayer_transform
+ // = M[parent] * Tr[layer->position() + anchor] *
+ // M[layer] * Tr[anchor2origin] *
+ // composite_sublayer_transform
+ //
+ // and a similar matrix for the full hierarchy with respect to the
+ // root.
+ //
+ // Finally, note that the final matrix used by the shader for the layer is P *
+ // M[draw] * S . This final product is computed in drawTexturedQuad(), where:
+ // P is the projection matrix
+ // S is the scale adjustment (to scale up a canonical quad to the
+ // layer's size)
+ //
+ // When a render surface has a replica layer, that layer's transform is used
+ // to draw a second copy of the surface. gfx::Transforms named here are
+ // relative to the surface, unless they specify they are relative to the
+ // replica layer.
+ //
+ // We will denote a scale by device scale S[deviceScale]
+ //
+ // The render surface draw transform to its target surface origin is:
+ // M[surfaceDraw] = M[owningLayer->Draw]
+ //
+ // The render surface origin transform to its the root (screen space) origin
+ // is:
+ // M[surface2root] = M[owningLayer->screenspace] *
+ // S[deviceScale].inverse()
+ //
+ // The replica draw transform to its target surface origin is:
+ // M[replicaDraw] = S[deviceScale] * M[surfaceDraw] *
+ // Tr[replica->position() + replica->anchor()] * Tr[replica] *
+ // Tr[origin2anchor].inverse() * S[contents_scale].inverse()
+ //
+ // The replica draw transform to the root (screen space) origin is:
+ // M[replica2root] = M[surface2root] * Tr[replica->position()] *
+ // Tr[replica] * Tr[origin2anchor].inverse()
+ //
+
+ // It makes no sense to have a non-unit page_scale_factor without specifying
+ // which layer roots the subtree the scale is applied to.
+ DCHECK(globals.page_scale_application_layer ||
+ (globals.page_scale_factor == 1.f));
+
+ // If we early-exit anywhere in this function, the drawable_content_rect of
+ // this subtree should be considered empty.
+ *drawable_content_rect_of_subtree = gfx::Rect();
+
+ DataForRecursion<LayerType, RenderSurfaceType> data_for_children;
+ RenderSurfaceType* nearest_ancestor_surface_that_moves_pixels =
+ data_from_ancestor.nearest_ancestor_surface_that_moves_pixels;
+ data_for_children.in_subtree_of_page_scale_application_layer =
+ data_from_ancestor.in_subtree_of_page_scale_application_layer;
+ data_for_children.subtree_can_use_lcd_text =
+ data_from_ancestor.subtree_can_use_lcd_text;
+
+ // Layers with a copy request are always visible, as well as un-hiding their
+ // subtree. Otherise, layers that are marked as hidden will hide themselves
+ // and their subtree.
+ bool layer_is_visible =
+ data_from_ancestor.subtree_is_visible_from_ancestor &&
+ !layer->hide_layer_and_subtree();
+ if (layer->HasCopyRequest())
+ layer_is_visible = true;
+
+ // The root layer cannot skip CalcDrawProperties.
+ if (!IsRootLayer(layer) && SubtreeShouldBeSkipped(layer, layer_is_visible))
+ return;
+
+ // As this function proceeds, these are the properties for the current
+ // layer that actually get computed. To avoid unnecessary copies
+ // (particularly for matrices), we do computations directly on these values
+ // when possible.
+ DrawProperties<LayerType, RenderSurfaceType>& layer_draw_properties =
+ layer->draw_properties();
+
+ gfx::Rect clip_rect_in_target_space;
+ bool layer_or_ancestor_clips_descendants = false;
+
+ // This value is cached on the stack so that we don't have to inverse-project
+ // the surface's clip rect redundantly for every layer. This value is the
+ // same as the target surface's clip rect, except that instead of being
+ // described in the target surface's target's space, it is described in the
+ // current render target's space.
+ gfx::Rect clip_rect_of_target_surface_in_target_space;
+
+ float accumulated_draw_opacity = layer->opacity();
+ bool animating_opacity_to_target = layer->OpacityIsAnimating();
+ bool animating_opacity_to_screen = animating_opacity_to_target;
+ if (layer->parent()) {
+ accumulated_draw_opacity *= layer->parent()->draw_opacity();
+ animating_opacity_to_target |= layer->parent()->draw_opacity_is_animating();
+ animating_opacity_to_screen |=
+ layer->parent()->screen_space_opacity_is_animating();
+ }
+
+ bool animating_transform_to_target = layer->TransformIsAnimating();
+ bool animating_transform_to_screen = animating_transform_to_target;
+ if (layer->parent()) {
+ animating_transform_to_target |=
+ layer->parent()->draw_transform_is_animating();
+ animating_transform_to_screen |=
+ layer->parent()->screen_space_transform_is_animating();
+ }
+
+ gfx::Size bounds = layer->bounds();
+ gfx::PointF anchor_point = layer->anchor_point();
+ gfx::PointF position = layer->position() - layer->TotalScrollOffset();
+
+ gfx::Transform combined_transform = data_from_ancestor.parent_matrix;
+ if (!layer->transform().IsIdentity()) {
+ // LT = Tr[origin] * Tr[origin2anchor]
+ combined_transform.Translate3d(
+ position.x() + anchor_point.x() * bounds.width(),
+ position.y() + anchor_point.y() * bounds.height(),
+ layer->anchor_point_z());
+ // LT = Tr[origin] * Tr[origin2anchor] * M[layer]
+ combined_transform.PreconcatTransform(layer->transform());
+ // LT = Tr[origin] * Tr[origin2anchor] * M[layer] * Tr[anchor2origin]
+ combined_transform.Translate3d(-anchor_point.x() * bounds.width(),
+ -anchor_point.y() * bounds.height(),
+ -layer->anchor_point_z());
+ } else {
+ combined_transform.Translate(position.x(), position.y());
+ }
+
+ if (!animating_transform_to_target && layer->scrollable() &&
+ combined_transform.IsScaleOrTranslation()) {
+ // Align the scrollable layer's position to screen space pixels to avoid
+ // blurriness. To avoid side-effects, do this only if the transform is
+ // simple.
+ RoundTranslationComponents(&combined_transform);
+ }
+
+ // Apply adjustment from position constraints.
+ ApplyPositionAdjustment(layer, data_from_ancestor.fixed_container,
+ data_from_ancestor.scroll_compensation_matrix, &combined_transform);
+
+ // Compute the 2d scale components of the transform hierarchy up to the target
+ // surface. From there, we can decide on a contents scale for the layer.
+ float layer_scale_factors = globals.device_scale_factor;
+ if (data_from_ancestor.in_subtree_of_page_scale_application_layer)
+ layer_scale_factors *= globals.page_scale_factor;
+ gfx::Vector2dF combined_transform_scales =
+ MathUtil::ComputeTransform2dScaleComponents(
+ combined_transform,
+ layer_scale_factors);
+
+ float ideal_contents_scale =
+ globals.can_adjust_raster_scales
+ ? std::max(combined_transform_scales.x(),
+ combined_transform_scales.y())
+ : layer_scale_factors;
+ UpdateLayerContentsScale(
+ layer,
+ globals.can_adjust_raster_scales,
+ ideal_contents_scale,
+ globals.device_scale_factor,
+ data_from_ancestor.in_subtree_of_page_scale_application_layer ?
+ globals.page_scale_factor : 1.f,
+ animating_transform_to_screen);
+
+ // The draw_transform that gets computed below is effectively the layer's
+ // draw_transform, unless the layer itself creates a render_surface. In that
+ // case, the render_surface re-parents the transforms.
+ layer_draw_properties.target_space_transform = combined_transform;
+ // M[draw] = M[parent] * LT * S[layer2content]
+ layer_draw_properties.target_space_transform.Scale
+ (1.f / layer->contents_scale_x(), 1.f / layer->contents_scale_y());
+
+ // The layer's screen_space_transform represents the transform between root
+ // layer's "screen space" and local content space.
+ layer_draw_properties.screen_space_transform =
+ data_from_ancestor.full_hierarchy_matrix;
+ if (!layer->preserves_3d())
+ layer_draw_properties.screen_space_transform.FlattenTo2d();
+ layer_draw_properties.screen_space_transform.PreconcatTransform
+ (layer_draw_properties.target_space_transform);
+
+ // Adjusting text AA method during animation may cause repaints, which in-turn
+ // causes jank.
+ bool adjust_text_aa =
+ !animating_opacity_to_screen && !animating_transform_to_screen;
+ // To avoid color fringing, LCD text should only be used on opaque layers with
+ // just integral translation.
+ bool layer_can_use_lcd_text =
+ data_from_ancestor.subtree_can_use_lcd_text &&
+ accumulated_draw_opacity == 1.f &&
+ layer_draw_properties.target_space_transform.
+ IsIdentityOrIntegerTranslation();
+
+ gfx::RectF content_rect(layer->content_bounds());
+
+ // full_hierarchy_matrix is the matrix that transforms objects between screen
+ // space (except projection matrix) and the most recent RenderSurfaceImpl's
+ // space. next_hierarchy_matrix will only change if this layer uses a new
+ // RenderSurfaceImpl, otherwise remains the same.
+ data_for_children.full_hierarchy_matrix =
+ data_from_ancestor.full_hierarchy_matrix;
+
+ // If the subtree will scale layer contents by the transform hierarchy, then
+ // we should scale things into the render surface by the transform hierarchy
+ // to take advantage of that.
+ gfx::Vector2dF render_surface_sublayer_scale =
+ globals.can_adjust_raster_scales
+ ? combined_transform_scales
+ : gfx::Vector2dF(layer_scale_factors, layer_scale_factors);
+
+ if (SubtreeShouldRenderToSeparateSurface(
+ layer, combined_transform.Preserves2dAxisAlignment())) {
+ // Check back-face visibility before continuing with this surface and its
+ // subtree
+ if (!layer->double_sided() && TransformToParentIsKnown(layer) &&
+ IsSurfaceBackFaceVisible(layer, combined_transform))
+ return;
+
+ RenderSurfaceType* render_surface = CreateOrReuseRenderSurface(layer);
+
+ if (IsRootLayer(layer)) {
+ // The root layer's render surface size is predetermined and so the root
+ // layer can't directly support non-identity transforms. It should just
+ // forward top-level transforms to the rest of the tree.
+ data_for_children.parent_matrix = combined_transform;
+
+ // The root surface does not contribute to any other surface, it has no
+ // target.
+ layer->render_surface()->set_contributes_to_drawn_surface(false);
+ } else {
+ // The owning layer's draw transform has a scale from content to layer
+ // space which we do not want; so here we use the combined_transform
+ // instead of the draw_transform. However, we do need to add a different
+ // scale factor that accounts for the surface's pixel dimensions.
+ combined_transform.Scale(1.0 / render_surface_sublayer_scale.x(),
+ 1.0 / render_surface_sublayer_scale.y());
+ render_surface->SetDrawTransform(combined_transform);
+
+ // The owning layer's transform was re-parented by the surface, so the
+ // layer's new draw_transform only needs to scale the layer to surface
+ // space.
+ layer_draw_properties.target_space_transform.MakeIdentity();
+ layer_draw_properties.target_space_transform.
+ Scale(render_surface_sublayer_scale.x() / layer->contents_scale_x(),
+ render_surface_sublayer_scale.y() / layer->contents_scale_y());
+
+ // Inside the surface's subtree, we scale everything to the owning layer's
+ // scale. The sublayer matrix transforms layer rects into target surface
+ // content space. Conceptually, all layers in the subtree inherit the
+ // scale at the point of the render surface in the transform hierarchy,
+ // but we apply it explicitly to the owning layer and the remainder of the
+ // subtree independently.
+ DCHECK(data_for_children.parent_matrix.IsIdentity());
+ data_for_children.parent_matrix.Scale(render_surface_sublayer_scale.x(),
+ render_surface_sublayer_scale.y());
+
+ layer->render_surface()->set_contributes_to_drawn_surface(
+ data_from_ancestor.subtree_is_visible_from_ancestor &&
+ layer_is_visible);
+ }
+
+ // The opacity value is moved from the layer to its surface, so that the
+ // entire subtree properly inherits opacity.
+ render_surface->SetDrawOpacity(accumulated_draw_opacity);
+ render_surface->SetDrawOpacityIsAnimating(animating_opacity_to_target);
+ animating_opacity_to_target = false;
+ layer_draw_properties.opacity = 1.f;
+ layer_draw_properties.opacity_is_animating = animating_opacity_to_target;
+ layer_draw_properties.screen_space_opacity_is_animating =
+ animating_opacity_to_screen;
+
+ render_surface->SetTargetSurfaceTransformsAreAnimating(
+ animating_transform_to_target);
+ render_surface->SetScreenSpaceTransformsAreAnimating(
+ animating_transform_to_screen);
+ animating_transform_to_target = false;
+ layer_draw_properties.target_space_transform_is_animating =
+ animating_transform_to_target;
+ layer_draw_properties.screen_space_transform_is_animating =
+ animating_transform_to_screen;
+
+ // Update the aggregate hierarchy matrix to include the transform of the
+ // newly created RenderSurfaceImpl.
+ data_for_children.full_hierarchy_matrix.PreconcatTransform(
+ render_surface->draw_transform());
+
+ // The new render_surface here will correctly clip the entire subtree. So,
+ // we do not need to continue propagating the clipping state further down
+ // the tree. This way, we can avoid transforming clip rects from ancestor
+ // target surface space to current target surface space that could cause
+ // more w < 0 headaches.
+ layer_or_ancestor_clips_descendants = false;
+
+ if (layer->mask_layer()) {
+ DrawProperties<LayerType, RenderSurfaceType>& mask_layer_draw_properties =
+ layer->mask_layer()->draw_properties();
+ mask_layer_draw_properties.render_target = layer;
+ mask_layer_draw_properties.visible_content_rect =
+ gfx::Rect(layer->content_bounds());
+ }
+
+ if (layer->replica_layer() && layer->replica_layer()->mask_layer()) {
+ DrawProperties<LayerType, RenderSurfaceType>&
+ replica_mask_draw_properties =
+ layer->replica_layer()->mask_layer()->draw_properties();
+ replica_mask_draw_properties.render_target = layer;
+ replica_mask_draw_properties.visible_content_rect =
+ gfx::Rect(layer->content_bounds());
+ }
+
+ // TODO(senorblanco): make this smarter for the SkImageFilter case (check
+ // for pixel-moving filters)
+ if (layer->filters().HasFilterThatMovesPixels() || layer->filter())
+ nearest_ancestor_surface_that_moves_pixels = render_surface;
+
+ // The render surface clip rect is expressed in the space where this surface
+ // draws, i.e. the same space as
+ // data_from_ancestor.clip_rect_in_target_space.
+ render_surface->SetIsClipped(data_from_ancestor.ancestor_clips_subtree);
+ if (data_from_ancestor.ancestor_clips_subtree) {
+ render_surface->SetClipRect(
+ data_from_ancestor.clip_rect_in_target_space);
+
+ gfx::Transform inverse_surface_draw_transform(
+ gfx::Transform::kSkipInitialization);
+ if (!render_surface->draw_transform().GetInverse(
+ &inverse_surface_draw_transform)) {
+ // TODO(shawnsingh): Either we need to handle uninvertible transforms
+ // here, or DCHECK that the transform is invertible.
+ }
+ clip_rect_of_target_surface_in_target_space =
+ gfx::ToEnclosingRect(MathUtil::ProjectClippedRect(
+ inverse_surface_draw_transform, render_surface->clip_rect()));
+ } else {
+ render_surface->SetClipRect(gfx::Rect());
+ clip_rect_of_target_surface_in_target_space =
+ data_from_ancestor.clip_rect_of_target_surface_in_target_space;
+ }
+
+ render_surface->SetNearestAncestorThatMovesPixels(
+ nearest_ancestor_surface_that_moves_pixels);
+
+ // If the new render surface is drawn translucent or with a non-integral
+ // translation then the subtree that gets drawn on this render surface
+ // cannot use LCD text.
+ data_for_children.subtree_can_use_lcd_text = layer_can_use_lcd_text;
+
+ render_surface_layer_list->push_back(layer);
+ } else {
+ DCHECK(layer->parent());
+
+ // Note: layer_draw_properties.target_space_transform is computed above,
+ // before this if-else statement.
+ layer_draw_properties.target_space_transform_is_animating =
+ animating_transform_to_target;
+ layer_draw_properties.screen_space_transform_is_animating =
+ animating_transform_to_screen;
+ layer_draw_properties.opacity = accumulated_draw_opacity;
+ layer_draw_properties.opacity_is_animating = animating_opacity_to_target;
+ layer_draw_properties.screen_space_opacity_is_animating =
+ animating_opacity_to_screen;
+ data_for_children.parent_matrix = combined_transform;
+
+ layer->ClearRenderSurface();
+
+ // Layers without render_surfaces directly inherit the ancestor's clip
+ // status.
+ layer_or_ancestor_clips_descendants =
+ data_from_ancestor.ancestor_clips_subtree;
+ if (data_from_ancestor.ancestor_clips_subtree) {
+ clip_rect_in_target_space =
+ data_from_ancestor.clip_rect_in_target_space;
+ }
+
+ // The surface's cached clip rect value propagates regardless of what
+ // clipping goes on between layers here.
+ clip_rect_of_target_surface_in_target_space =
+ data_from_ancestor.clip_rect_of_target_surface_in_target_space;
+
+ // Layers that are not their own render_target will render into the target
+ // of their nearest ancestor.
+ layer_draw_properties.render_target = layer->parent()->render_target();
+ }
+
+ // Mark whether a layer could be drawn directly to the back buffer, for
+ // example when it could use LCD text even though it's in a non-contents
+ // opaque layer. This means that it can't be drawn to an intermediate
+ // render target and also that no blending is applied to the layer as a whole
+ // (meaning that its contents don't have to be pre-composited into a bitmap or
+ // a render target).
+ //
+ // Ignoring animations is an optimization,
+ // as it means that we're going to need some retained resources for this
+ // layer in the near future even if its opacity is 1 now.
+ layer_draw_properties.can_draw_directly_to_backbuffer =
+ IsRootLayer(layer_draw_properties.render_target) &&
+ layer->draw_properties().opacity == 1.f &&
+ !animating_opacity_to_screen;
+
+ if (adjust_text_aa)
+ layer_draw_properties.can_use_lcd_text = layer_can_use_lcd_text;
+
+ gfx::Rect rect_in_target_space = ToEnclosingRect(
+ MathUtil::MapClippedRect(layer->draw_transform(), content_rect));
+
+ if (LayerClipsSubtree(layer)) {
+ layer_or_ancestor_clips_descendants = true;
+ if (data_from_ancestor.ancestor_clips_subtree && !layer->render_surface()) {
+ // A layer without render surface shares the same target as its ancestor.
+ clip_rect_in_target_space =
+ data_from_ancestor.clip_rect_in_target_space;
+ clip_rect_in_target_space.Intersect(rect_in_target_space);
+ } else {
+ clip_rect_in_target_space = rect_in_target_space;
+ }
+ }
+
+ if (layer == globals.page_scale_application_layer) {
+ data_for_children.parent_matrix.Scale(
+ globals.page_scale_factor,
+ globals.page_scale_factor);
+ data_for_children.in_subtree_of_page_scale_application_layer = true;
+ }
+
+ // Flatten to 2D if the layer doesn't preserve 3D.
+ if (!layer->preserves_3d())
+ data_for_children.parent_matrix.FlattenTo2d();
+
+ // Apply the sublayer transform at the anchor point of the layer.
+ if (!layer->sublayer_transform().IsIdentity()) {
+ data_for_children.parent_matrix.Translate(
+ layer->anchor_point().x() * bounds.width(),
+ layer->anchor_point().y() * bounds.height());
+ data_for_children.parent_matrix.PreconcatTransform(
+ layer->sublayer_transform());
+ data_for_children.parent_matrix.Translate(
+ -layer->anchor_point().x() * bounds.width(),
+ -layer->anchor_point().y() * bounds.height());
+ }
+
+ LayerListType& descendants =
+ (layer->render_surface() ? layer->render_surface()->layer_list()
+ : *layer_list);
+
+ // Any layers that are appended after this point are in the layer's subtree
+ // and should be included in the sorting process.
+ size_t sorting_start_index = descendants.size();
+
+ if (!LayerShouldBeSkipped(layer, layer_is_visible))
+ descendants.push_back(layer);
+
+ data_for_children.scroll_compensation_matrix =
+ ComputeScrollCompensationMatrixForChildren(
+ layer,
+ data_from_ancestor.parent_matrix,
+ data_from_ancestor.scroll_compensation_matrix);
+ data_for_children.fixed_container =
+ layer->IsContainerForFixedPositionLayers() ?
+ layer : data_from_ancestor.fixed_container;
+
+ data_for_children.clip_rect_in_target_space = clip_rect_in_target_space;
+ data_for_children.clip_rect_of_target_surface_in_target_space =
+ clip_rect_of_target_surface_in_target_space;
+ data_for_children.ancestor_clips_subtree =
+ layer_or_ancestor_clips_descendants;
+ data_for_children.nearest_ancestor_surface_that_moves_pixels =
+ nearest_ancestor_surface_that_moves_pixels;
+ data_for_children.subtree_is_visible_from_ancestor = layer_is_visible;
+
+ gfx::Rect accumulated_drawable_content_rect_of_children;
+ for (size_t i = 0; i < layer->children().size(); ++i) {
+ LayerType* child =
+ LayerTreeHostCommon::get_child_as_raw_ptr(layer->children(), i);
+ gfx::Rect drawable_content_rect_of_child_subtree;
+ gfx::Transform identity_matrix;
+ CalculateDrawPropertiesInternal<LayerType,
+ LayerListType,
+ RenderSurfaceType>(
+ child,
+ globals,
+ data_for_children,
+ render_surface_layer_list,
+ &descendants,
+ &drawable_content_rect_of_child_subtree);
+ if (!drawable_content_rect_of_child_subtree.IsEmpty()) {
+ accumulated_drawable_content_rect_of_children.Union(
+ drawable_content_rect_of_child_subtree);
+ if (child->render_surface())
+ descendants.push_back(child);
+ }
+ }
+
+ if (layer->render_surface() && !IsRootLayer(layer) &&
+ layer->render_surface()->layer_list().empty()) {
+ RemoveSurfaceForEarlyExit(layer, render_surface_layer_list);
+ return;
+ }
+
+ // Compute the total drawable_content_rect for this subtree (the rect is in
+ // target surface space).
+ gfx::Rect local_drawable_content_rect_of_subtree =
+ accumulated_drawable_content_rect_of_children;
+ if (layer->DrawsContent())
+ local_drawable_content_rect_of_subtree.Union(rect_in_target_space);
+ if (layer_or_ancestor_clips_descendants)
+ local_drawable_content_rect_of_subtree.Intersect(clip_rect_in_target_space);
+
+ // Compute the layer's drawable content rect (the rect is in target surface
+ // space).
+ layer_draw_properties.drawable_content_rect = rect_in_target_space;
+ if (layer_or_ancestor_clips_descendants) {
+ layer_draw_properties.drawable_content_rect.
+ Intersect(clip_rect_in_target_space);
+ }
+
+ // Tell the layer the rect that is clipped by. In theory we could use a
+ // tighter clip rect here (drawable_content_rect), but that actually does not
+ // reduce how much would be drawn, and instead it would create unnecessary
+ // changes to scissor state affecting GPU performance.
+ layer_draw_properties.is_clipped = layer_or_ancestor_clips_descendants;
+ if (layer_or_ancestor_clips_descendants) {
+ layer_draw_properties.clip_rect = clip_rect_in_target_space;
+ } else {
+ // Initialize the clip rect to a safe value that will not clip the
+ // layer, just in case clipping is still accidentally used.
+ layer_draw_properties.clip_rect = rect_in_target_space;
+ }
+
+ // Compute the layer's visible content rect (the rect is in content space).
+ layer_draw_properties.visible_content_rect = CalculateVisibleContentRect(
+ layer, clip_rect_of_target_surface_in_target_space, rect_in_target_space);
+
+ // Compute the remaining properties for the render surface, if the layer has
+ // one.
+ if (IsRootLayer(layer)) {
+ // The root layer's surface's content_rect is always the entire viewport.
+ DCHECK(layer->render_surface());
+ layer->render_surface()->SetContentRect(
+ data_from_ancestor.clip_rect_in_target_space);
+ } else if (layer->render_surface() && !IsRootLayer(layer)) {
+ RenderSurfaceType* render_surface = layer->render_surface();
+ gfx::Rect clipped_content_rect = local_drawable_content_rect_of_subtree;
+
+ // Don't clip if the layer is reflected as the reflection shouldn't be
+ // clipped. If the layer is animating, then the surface's transform to
+ // its target is not known on the main thread, and we should not use it
+ // to clip.
+ if (!layer->replica_layer() && TransformToParentIsKnown(layer)) {
+ // Note, it is correct to use data_from_ancestor.ancestor_clips_subtree
+ // here, because we are looking at this layer's render_surface, not the
+ // layer itself.
+ if (data_from_ancestor.ancestor_clips_subtree &&
+ !clipped_content_rect.IsEmpty()) {
+ gfx::Rect surface_clip_rect = LayerTreeHostCommon::CalculateVisibleRect(
+ render_surface->clip_rect(),
+ clipped_content_rect,
+ render_surface->draw_transform());
+ clipped_content_rect.Intersect(surface_clip_rect);
+ }
+ }
+
+ // The RenderSurfaceImpl backing texture cannot exceed the maximum supported
+ // texture size.
+ clipped_content_rect.set_width(
+ std::min(clipped_content_rect.width(), globals.max_texture_size));
+ clipped_content_rect.set_height(
+ std::min(clipped_content_rect.height(), globals.max_texture_size));
+
+ if (clipped_content_rect.IsEmpty()) {
+ RemoveSurfaceForEarlyExit(layer, render_surface_layer_list);
+ return;
+ }
+
+ render_surface->SetContentRect(clipped_content_rect);
+
+ // The owning layer's screen_space_transform has a scale from content to
+ // layer space which we need to undo and replace with a scale from the
+ // surface's subtree into layer space.
+ gfx::Transform screen_space_transform = layer->screen_space_transform();
+ screen_space_transform.Scale(
+ layer->contents_scale_x() / render_surface_sublayer_scale.x(),
+ layer->contents_scale_y() / render_surface_sublayer_scale.y());
+ render_surface->SetScreenSpaceTransform(screen_space_transform);
+
+ if (layer->replica_layer()) {
+ gfx::Transform surface_origin_to_replica_origin_transform;
+ surface_origin_to_replica_origin_transform.Scale(
+ render_surface_sublayer_scale.x(), render_surface_sublayer_scale.y());
+ surface_origin_to_replica_origin_transform.Translate(
+ layer->replica_layer()->position().x() +
+ layer->replica_layer()->anchor_point().x() * bounds.width(),
+ layer->replica_layer()->position().y() +
+ layer->replica_layer()->anchor_point().y() * bounds.height());
+ surface_origin_to_replica_origin_transform.PreconcatTransform(
+ layer->replica_layer()->transform());
+ surface_origin_to_replica_origin_transform.Translate(
+ -layer->replica_layer()->anchor_point().x() * bounds.width(),
+ -layer->replica_layer()->anchor_point().y() * bounds.height());
+ surface_origin_to_replica_origin_transform.Scale(
+ 1.0 / render_surface_sublayer_scale.x(),
+ 1.0 / render_surface_sublayer_scale.y());
+
+ // Compute the replica's "originTransform" that maps from the replica's
+ // origin space to the target surface origin space.
+ gfx::Transform replica_origin_transform =
+ layer->render_surface()->draw_transform() *
+ surface_origin_to_replica_origin_transform;
+ render_surface->SetReplicaDrawTransform(replica_origin_transform);
+
+ // Compute the replica's "screen_space_transform" that maps from the
+ // replica's origin space to the screen's origin space.
+ gfx::Transform replica_screen_space_transform =
+ layer->render_surface()->screen_space_transform() *
+ surface_origin_to_replica_origin_transform;
+ render_surface->SetReplicaScreenSpaceTransform(
+ replica_screen_space_transform);
+ }
+ }
+
+ UpdateTilePrioritiesForLayer(layer);
+ SavePaintPropertiesLayer(layer);
+
+ // If neither this layer nor any of its children were added, early out.
+ if (sorting_start_index == descendants.size())
+ return;
+
+ // If preserves-3d then sort all the descendants in 3D so that they can be
+ // drawn from back to front. If the preserves-3d property is also set on the
+ // parent then skip the sorting as the parent will sort all the descendants
+ // anyway.
+ if (globals.layer_sorter && descendants.size() && layer->preserves_3d() &&
+ (!layer->parent() || !layer->parent()->preserves_3d())) {
+ SortLayers(descendants.begin() + sorting_start_index,
+ descendants.end(),
+ globals.layer_sorter);
+ }
+
+ if (layer->render_surface()) {
+ *drawable_content_rect_of_subtree =
+ gfx::ToEnclosingRect(layer->render_surface()->DrawableContentRect());
+ } else {
+ *drawable_content_rect_of_subtree = local_drawable_content_rect_of_subtree;
+ }
+
+ if (layer->HasContributingDelegatedRenderPasses()) {
+ layer->render_target()->render_surface()->
+ AddContributingDelegatedRenderPassLayer(layer);
+ }
+}
+
+void LayerTreeHostCommon::CalculateDrawProperties(
+ CalcDrawPropsMainInputs* inputs) {
+ DCHECK(inputs->root_layer);
+ DCHECK(IsRootLayer(inputs->root_layer));
+ DCHECK(inputs->render_surface_layer_list);
+ gfx::Rect total_drawable_content_rect;
+ gfx::Transform identity_matrix;
+ gfx::Transform scaled_device_transform = inputs->device_transform;
+ scaled_device_transform.Scale(inputs->device_scale_factor,
+ inputs->device_scale_factor);
+ RenderSurfaceLayerList dummy_layer_list;
+
+ // The root layer's render_surface should receive the device viewport as the
+ // initial clip rect.
+ gfx::Rect device_viewport_rect(inputs->device_viewport_size);
+
+ SubtreeGlobals<Layer> globals;
+ globals.layer_sorter = NULL;
+ globals.max_texture_size = inputs->max_texture_size;
+ globals.device_scale_factor = inputs->device_scale_factor;
+ globals.page_scale_factor = inputs->page_scale_factor;
+ globals.page_scale_application_layer = inputs->page_scale_application_layer;
+ globals.can_adjust_raster_scales = inputs->can_adjust_raster_scales;
+
+ DataForRecursion<Layer, RenderSurface> data_for_recursion;
+ data_for_recursion.parent_matrix = scaled_device_transform;
+ data_for_recursion.full_hierarchy_matrix = identity_matrix;
+ data_for_recursion.scroll_compensation_matrix = identity_matrix;
+ data_for_recursion.fixed_container = inputs->root_layer;
+ data_for_recursion.clip_rect_in_target_space = device_viewport_rect;
+ data_for_recursion.clip_rect_of_target_surface_in_target_space =
+ device_viewport_rect;
+ data_for_recursion.ancestor_clips_subtree = true;
+ data_for_recursion.nearest_ancestor_surface_that_moves_pixels = NULL;
+ data_for_recursion.in_subtree_of_page_scale_application_layer = false;
+ data_for_recursion.subtree_can_use_lcd_text = inputs->can_use_lcd_text;
+ data_for_recursion.subtree_is_visible_from_ancestor = true;
+
+ PreCalculateMetaInformationRecursiveData recursive_data;
+ PreCalculateMetaInformation(inputs->root_layer, &recursive_data);
+
+ CalculateDrawPropertiesInternal<Layer, RenderSurfaceLayerList, RenderSurface>(
+ inputs->root_layer,
+ globals,
+ data_for_recursion,
+ inputs->render_surface_layer_list,
+ &dummy_layer_list,
+ &total_drawable_content_rect);
+
+ // The dummy layer list should not have been used.
+ DCHECK_EQ(0u, dummy_layer_list.size());
+ // A root layer render_surface should always exist after
+ // CalculateDrawProperties.
+ DCHECK(inputs->root_layer->render_surface());
+}
+
+void LayerTreeHostCommon::CalculateDrawProperties(
+ CalcDrawPropsImplInputs* inputs) {
+ DCHECK(inputs->root_layer);
+ DCHECK(IsRootLayer(inputs->root_layer));
+ DCHECK(inputs->render_surface_layer_list);
+
+ gfx::Rect total_drawable_content_rect;
+ gfx::Transform identity_matrix;
+ gfx::Transform scaled_device_transform = inputs->device_transform;
+ scaled_device_transform.Scale(inputs->device_scale_factor,
+ inputs->device_scale_factor);
+ LayerImplList dummy_layer_list;
+ LayerSorter layer_sorter;
+
+ // The root layer's render_surface should receive the device viewport as the
+ // initial clip rect.
+ gfx::Rect device_viewport_rect(inputs->device_viewport_size);
+
+ SubtreeGlobals<LayerImpl> globals;
+ globals.layer_sorter = &layer_sorter;
+ globals.max_texture_size = inputs->max_texture_size;
+ globals.device_scale_factor = inputs->device_scale_factor;
+ globals.page_scale_factor = inputs->page_scale_factor;
+ globals.page_scale_application_layer = inputs->page_scale_application_layer;
+ globals.can_adjust_raster_scales = inputs->can_adjust_raster_scales;
+
+ DataForRecursion<LayerImpl, RenderSurfaceImpl> data_for_recursion;
+ data_for_recursion.parent_matrix = scaled_device_transform;
+ data_for_recursion.full_hierarchy_matrix = identity_matrix;
+ data_for_recursion.scroll_compensation_matrix = identity_matrix;
+ data_for_recursion.fixed_container = inputs->root_layer;
+ data_for_recursion.clip_rect_in_target_space = device_viewport_rect;
+ data_for_recursion.clip_rect_of_target_surface_in_target_space =
+ device_viewport_rect;
+ data_for_recursion.ancestor_clips_subtree = true;
+ data_for_recursion.nearest_ancestor_surface_that_moves_pixels = NULL;
+ data_for_recursion.in_subtree_of_page_scale_application_layer = false;
+ data_for_recursion.subtree_can_use_lcd_text = inputs->can_use_lcd_text;
+ data_for_recursion.subtree_is_visible_from_ancestor = true;
+
+ PreCalculateMetaInformationRecursiveData recursive_data;
+ PreCalculateMetaInformation(inputs->root_layer, &recursive_data);
+
+ CalculateDrawPropertiesInternal<LayerImpl, LayerImplList, RenderSurfaceImpl>(
+ inputs->root_layer,
+ globals,
+ data_for_recursion,
+ inputs->render_surface_layer_list,
+ &dummy_layer_list,
+ &total_drawable_content_rect);
+
+ // The dummy layer list should not have been used.
+ DCHECK_EQ(0u, dummy_layer_list.size());
+ // A root layer render_surface should always exist after
+ // CalculateDrawProperties.
+ DCHECK(inputs->root_layer->render_surface());
+}
+
+static bool PointHitsRect(
+ gfx::PointF screen_space_point,
+ const gfx::Transform& local_space_to_screen_space_transform,
+ gfx::RectF local_space_rect) {
+ // If the transform is not invertible, then assume that this point doesn't hit
+ // this rect.
+ gfx::Transform inverse_local_space_to_screen_space(
+ gfx::Transform::kSkipInitialization);
+ if (!local_space_to_screen_space_transform.GetInverse(
+ &inverse_local_space_to_screen_space))
+ return false;
+
+ // Transform the hit test point from screen space to the local space of the
+ // given rect.
+ bool clipped = false;
+ gfx::PointF hit_test_point_in_local_space = MathUtil::ProjectPoint(
+ inverse_local_space_to_screen_space, screen_space_point, &clipped);
+
+ // If ProjectPoint could not project to a valid value, then we assume that
+ // this point doesn't hit this rect.
+ if (clipped)
+ return false;
+
+ return local_space_rect.Contains(hit_test_point_in_local_space);
+}
+
+static bool PointHitsRegion(gfx::PointF screen_space_point,
+ const gfx::Transform& screen_space_transform,
+ const Region& layer_space_region,
+ float layer_content_scale_x,
+ float layer_content_scale_y) {
+ // If the transform is not invertible, then assume that this point doesn't hit
+ // this region.
+ gfx::Transform inverse_screen_space_transform(
+ gfx::Transform::kSkipInitialization);
+ if (!screen_space_transform.GetInverse(&inverse_screen_space_transform))
+ return false;
+
+ // Transform the hit test point from screen space to the local space of the
+ // given region.
+ bool clipped = false;
+ gfx::PointF hit_test_point_in_content_space = MathUtil::ProjectPoint(
+ inverse_screen_space_transform, screen_space_point, &clipped);
+ gfx::PointF hit_test_point_in_layer_space =
+ gfx::ScalePoint(hit_test_point_in_content_space,
+ 1.f / layer_content_scale_x,
+ 1.f / layer_content_scale_y);
+
+ // If ProjectPoint could not project to a valid value, then we assume that
+ // this point doesn't hit this region.
+ if (clipped)
+ return false;
+
+ return layer_space_region.Contains(
+ gfx::ToRoundedPoint(hit_test_point_in_layer_space));
+}
+
+static bool PointIsClippedBySurfaceOrClipRect(gfx::PointF screen_space_point,
+ LayerImpl* layer) {
+ LayerImpl* current_layer = layer;
+
+ // Walk up the layer tree and hit-test any render_surfaces and any layer
+ // clip rects that are active.
+ while (current_layer) {
+ if (current_layer->render_surface() &&
+ !PointHitsRect(
+ screen_space_point,
+ current_layer->render_surface()->screen_space_transform(),
+ current_layer->render_surface()->content_rect()))
+ return true;
+
+ // Note that drawable content rects are actually in target surface space, so
+ // the transform we have to provide is the target surface's
+ // screen_space_transform.
+ LayerImpl* render_target = current_layer->render_target();
+ if (LayerClipsSubtree(current_layer) &&
+ !PointHitsRect(
+ screen_space_point,
+ render_target->render_surface()->screen_space_transform(),
+ current_layer->drawable_content_rect()))
+ return true;
+
+ current_layer = current_layer->parent();
+ }
+
+ // If we have finished walking all ancestors without having already exited,
+ // then the point is not clipped by any ancestors.
+ return false;
+}
+
+LayerImpl* LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ gfx::PointF screen_space_point,
+ const LayerImplList& render_surface_layer_list) {
+ LayerImpl* found_layer = NULL;
+
+ typedef LayerIterator<LayerImpl,
+ LayerImplList,
+ RenderSurfaceImpl,
+ LayerIteratorActions::FrontToBack> LayerIteratorType;
+ LayerIteratorType end = LayerIteratorType::End(&render_surface_layer_list);
+
+ for (LayerIteratorType
+ it = LayerIteratorType::Begin(&render_surface_layer_list);
+ it != end;
+ ++it) {
+ // We don't want to consider render_surfaces for hit testing.
+ if (!it.represents_itself())
+ continue;
+
+ LayerImpl* current_layer = (*it);
+
+ gfx::RectF content_rect(current_layer->content_bounds());
+ if (!PointHitsRect(screen_space_point,
+ current_layer->screen_space_transform(),
+ content_rect))
+ continue;
+
+ // At this point, we think the point does hit the layer, but we need to walk
+ // up the parents to ensure that the layer was not clipped in such a way
+ // that the hit point actually should not hit the layer.
+ if (PointIsClippedBySurfaceOrClipRect(screen_space_point, current_layer))
+ continue;
+
+ // Skip the HUD layer.
+ if (current_layer == current_layer->layer_tree_impl()->hud_layer())
+ continue;
+
+ found_layer = current_layer;
+ break;
+ }
+
+ // This can potentially return NULL, which means the screen_space_point did
+ // not successfully hit test any layers, not even the root layer.
+ return found_layer;
+}
+
+LayerImpl* LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ gfx::PointF screen_space_point,
+ const LayerImplList& render_surface_layer_list) {
+ LayerImpl* found_layer = NULL;
+
+ typedef LayerIterator<LayerImpl,
+ LayerImplList,
+ RenderSurfaceImpl,
+ LayerIteratorActions::FrontToBack> LayerIteratorType;
+ LayerIteratorType end = LayerIteratorType::End(&render_surface_layer_list);
+
+ for (LayerIteratorType
+ it = LayerIteratorType::Begin(&render_surface_layer_list);
+ it != end;
+ ++it) {
+ // We don't want to consider render_surfaces for hit testing.
+ if (!it.represents_itself())
+ continue;
+
+ LayerImpl* current_layer = (*it);
+
+ if (!LayerHasTouchEventHandlersAt(screen_space_point, current_layer))
+ continue;
+
+ found_layer = current_layer;
+ break;
+ }
+
+ // This can potentially return NULL, which means the screen_space_point did
+ // not successfully hit test any layers, not even the root layer.
+ return found_layer;
+}
+
+bool LayerTreeHostCommon::LayerHasTouchEventHandlersAt(
+ gfx::PointF screen_space_point,
+ LayerImpl* layer_impl) {
+ if (layer_impl->touch_event_handler_region().IsEmpty())
+ return false;
+
+ if (!PointHitsRegion(screen_space_point,
+ layer_impl->screen_space_transform(),
+ layer_impl->touch_event_handler_region(),
+ layer_impl->contents_scale_x(),
+ layer_impl->contents_scale_y()))
+ return false;
+
+ // At this point, we think the point does hit the touch event handler region
+ // on the layer, but we need to walk up the parents to ensure that the layer
+ // was not clipped in such a way that the hit point actually should not hit
+ // the layer.
+ if (PointIsClippedBySurfaceOrClipRect(screen_space_point, layer_impl))
+ return false;
+
+ return true;
+}
+} // namespace cc
diff --git a/chromium/cc/trees/layer_tree_host_common.h b/chromium/cc/trees/layer_tree_host_common.h
new file mode 100644
index 00000000000..05affee8f31
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_host_common.h
@@ -0,0 +1,253 @@
+// Copyright 2011 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.
+
+#ifndef CC_TREES_LAYER_TREE_HOST_COMMON_H_
+#define CC_TREES_LAYER_TREE_HOST_COMMON_H_
+
+#include <limits>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/memory/ref_counted.h"
+#include "cc/base/cc_export.h"
+#include "cc/base/scoped_ptr_vector.h"
+#include "cc/layers/layer_lists.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/transform.h"
+#include "ui/gfx/vector2d.h"
+
+namespace cc {
+
+class LayerImpl;
+class Layer;
+
+class CC_EXPORT LayerTreeHostCommon {
+ public:
+ static gfx::Rect CalculateVisibleRect(gfx::Rect target_surface_rect,
+ gfx::Rect layer_bound_rect,
+ const gfx::Transform& transform);
+
+ template <typename LayerType, typename RenderSurfaceLayerListType>
+ struct CalcDrawPropsInputs {
+ public:
+ CalcDrawPropsInputs(LayerType* root_layer,
+ gfx::Size device_viewport_size,
+ const gfx::Transform& device_transform,
+ float device_scale_factor,
+ float page_scale_factor,
+ LayerType* page_scale_application_layer,
+ int max_texture_size,
+ bool can_use_lcd_text,
+ bool can_adjust_raster_scales,
+ RenderSurfaceLayerListType* render_surface_layer_list)
+ : root_layer(root_layer),
+ device_viewport_size(device_viewport_size),
+ device_transform(device_transform),
+ device_scale_factor(device_scale_factor),
+ page_scale_factor(page_scale_factor),
+ page_scale_application_layer(page_scale_application_layer),
+ max_texture_size(max_texture_size),
+ can_use_lcd_text(can_use_lcd_text),
+ can_adjust_raster_scales(can_adjust_raster_scales),
+ render_surface_layer_list(render_surface_layer_list) {}
+
+ LayerType* root_layer;
+ gfx::Size device_viewport_size;
+ const gfx::Transform& device_transform;
+ float device_scale_factor;
+ float page_scale_factor;
+ LayerType* page_scale_application_layer;
+ int max_texture_size;
+ bool can_use_lcd_text;
+ bool can_adjust_raster_scales;
+ RenderSurfaceLayerListType* render_surface_layer_list;
+ };
+
+ template <typename LayerType, typename RenderSurfaceLayerListType>
+ struct CalcDrawPropsInputsForTesting
+ : public CalcDrawPropsInputs<LayerType, RenderSurfaceLayerListType> {
+ CalcDrawPropsInputsForTesting(
+ LayerType* root_layer,
+ gfx::Size device_viewport_size,
+ const gfx::Transform& device_transform,
+ RenderSurfaceLayerListType* render_surface_layer_list);
+ CalcDrawPropsInputsForTesting(
+ LayerType* root_layer,
+ gfx::Size device_viewport_size,
+ RenderSurfaceLayerListType* render_surface_layer_list);
+
+ private:
+ const gfx::Transform identity_transform_;
+ };
+
+ typedef CalcDrawPropsInputs<Layer, RenderSurfaceLayerList>
+ CalcDrawPropsMainInputs;
+ typedef CalcDrawPropsInputsForTesting<Layer, RenderSurfaceLayerList>
+ CalcDrawPropsMainInputsForTesting;
+ static void CalculateDrawProperties(CalcDrawPropsMainInputs* inputs);
+
+ typedef CalcDrawPropsInputs<LayerImpl, LayerImplList> CalcDrawPropsImplInputs;
+ typedef CalcDrawPropsInputsForTesting<LayerImpl, LayerImplList>
+ CalcDrawPropsImplInputsForTesting;
+ static void CalculateDrawProperties(CalcDrawPropsImplInputs* inputs);
+
+ // Performs hit testing for a given render_surface_layer_list.
+ static LayerImpl* FindLayerThatIsHitByPoint(
+ gfx::PointF screen_space_point,
+ const LayerImplList& render_surface_layer_list);
+
+ static LayerImpl* FindLayerThatIsHitByPointInTouchHandlerRegion(
+ gfx::PointF screen_space_point,
+ const LayerImplList& render_surface_layer_list);
+
+ static bool LayerHasTouchEventHandlersAt(gfx::PointF screen_space_point,
+ LayerImpl* layer_impl);
+
+ template <typename LayerType>
+ static bool RenderSurfaceContributesToTarget(LayerType*,
+ int target_surface_layer_id);
+
+ template <typename LayerType>
+ static void CallFunctionForSubtree(
+ LayerType* root_layer,
+ const base::Callback<void(LayerType* layer)>& function);
+
+ // Returns a layer with the given id if one exists in the subtree starting
+ // from the given root layer (including mask and replica layers).
+ template <typename LayerType>
+ static LayerType* FindLayerInSubtree(LayerType* root_layer, int layer_id);
+
+ static Layer* get_child_as_raw_ptr(
+ const LayerList& children,
+ size_t index) {
+ return children[index].get();
+ }
+
+ static LayerImpl* get_child_as_raw_ptr(
+ const OwnedLayerImplList& children,
+ size_t index) {
+ return children[index];
+ }
+
+ struct ScrollUpdateInfo {
+ int layer_id;
+ gfx::Vector2d scroll_delta;
+ };
+};
+
+struct CC_EXPORT ScrollAndScaleSet {
+ ScrollAndScaleSet();
+ ~ScrollAndScaleSet();
+
+ std::vector<LayerTreeHostCommon::ScrollUpdateInfo> scrolls;
+ float page_scale_delta;
+};
+
+template <typename LayerType>
+bool LayerTreeHostCommon::RenderSurfaceContributesToTarget(
+ LayerType* layer,
+ int target_surface_layer_id) {
+ // A layer will either contribute its own content, or its render surface's
+ // content, to the target surface. The layer contributes its surface's content
+ // when both the following are true:
+ // (1) The layer actually has a render surface, and
+ // (2) The layer's render surface is not the same as the target surface.
+ //
+ // Otherwise, the layer just contributes itself to the target surface.
+
+ return layer->render_surface() && layer->id() != target_surface_layer_id;
+}
+
+template <typename LayerType>
+LayerType* LayerTreeHostCommon::FindLayerInSubtree(LayerType* root_layer,
+ int layer_id) {
+ if (!root_layer)
+ return NULL;
+
+ if (root_layer->id() == layer_id)
+ return root_layer;
+
+ if (root_layer->mask_layer() && root_layer->mask_layer()->id() == layer_id)
+ return root_layer->mask_layer();
+
+ if (root_layer->replica_layer() &&
+ root_layer->replica_layer()->id() == layer_id)
+ return root_layer->replica_layer();
+
+ for (size_t i = 0; i < root_layer->children().size(); ++i) {
+ if (LayerType* found = FindLayerInSubtree(
+ get_child_as_raw_ptr(root_layer->children(), i), layer_id))
+ return found;
+ }
+ return NULL;
+}
+
+template <typename LayerType>
+void LayerTreeHostCommon::CallFunctionForSubtree(
+ LayerType* root_layer,
+ const base::Callback<void(LayerType* layer)>& function) {
+ function.Run(root_layer);
+
+ if (LayerType* mask_layer = root_layer->mask_layer())
+ function.Run(mask_layer);
+ if (LayerType* replica_layer = root_layer->replica_layer()) {
+ function.Run(replica_layer);
+ if (LayerType* mask_layer = replica_layer->mask_layer())
+ function.Run(mask_layer);
+ }
+
+ for (size_t i = 0; i < root_layer->children().size(); ++i) {
+ CallFunctionForSubtree(get_child_as_raw_ptr(root_layer->children(), i),
+ function);
+ }
+}
+
+template <typename LayerType, typename RenderSurfaceLayerListType>
+LayerTreeHostCommon::CalcDrawPropsInputsForTesting<LayerType,
+ RenderSurfaceLayerListType>::
+ CalcDrawPropsInputsForTesting(
+ LayerType* root_layer,
+ gfx::Size device_viewport_size,
+ const gfx::Transform& device_transform,
+ RenderSurfaceLayerListType* render_surface_layer_list)
+ : CalcDrawPropsInputs<LayerType, RenderSurfaceLayerListType>(
+ root_layer,
+ device_viewport_size,
+ device_transform,
+ 1.f,
+ 1.f,
+ NULL,
+ std::numeric_limits<int>::max() / 2,
+ false,
+ false,
+ render_surface_layer_list) {
+ DCHECK(root_layer);
+ DCHECK(render_surface_layer_list);
+}
+
+template <typename LayerType, typename RenderSurfaceLayerListType>
+LayerTreeHostCommon::CalcDrawPropsInputsForTesting<LayerType,
+ RenderSurfaceLayerListType>::
+ CalcDrawPropsInputsForTesting(
+ LayerType* root_layer,
+ gfx::Size device_viewport_size,
+ RenderSurfaceLayerListType* render_surface_layer_list)
+ : CalcDrawPropsInputs<LayerType, RenderSurfaceLayerListType>(
+ root_layer,
+ device_viewport_size,
+ identity_transform_,
+ 1.f,
+ 1.f,
+ NULL,
+ std::numeric_limits<int>::max() / 2,
+ false,
+ false,
+ render_surface_layer_list) {
+ DCHECK(root_layer);
+ DCHECK(render_surface_layer_list);
+}
+
+} // namespace cc
+
+#endif // CC_TREES_LAYER_TREE_HOST_COMMON_H_
diff --git a/chromium/cc/trees/layer_tree_host_common_unittest.cc b/chromium/cc/trees/layer_tree_host_common_unittest.cc
new file mode 100644
index 00000000000..fa5450d45f4
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_host_common_unittest.cc
@@ -0,0 +1,8310 @@
+// Copyright 2011 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 "cc/trees/layer_tree_host_common.h"
+
+#include "cc/animation/layer_animation_controller.h"
+#include "cc/base/math_util.h"
+#include "cc/layers/content_layer.h"
+#include "cc/layers/content_layer_client.h"
+#include "cc/layers/heads_up_display_layer_impl.h"
+#include "cc/layers/layer.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/layers/render_surface.h"
+#include "cc/layers/render_surface_impl.h"
+#include "cc/output/copy_output_request.h"
+#include "cc/output/copy_output_result.h"
+#include "cc/test/animation_test_common.h"
+#include "cc/test/fake_impl_proxy.h"
+#include "cc/test/fake_layer_tree_host.h"
+#include "cc/test/fake_layer_tree_host_impl.h"
+#include "cc/test/geometry_test_utils.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "cc/trees/proxy.h"
+#include "cc/trees/single_thread_proxy.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/quad_f.h"
+#include "ui/gfx/size_conversions.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+namespace {
+
+class LayerTreeHostCommonTestBase {
+ protected:
+ template <typename LayerType>
+ void SetLayerPropertiesForTestingInternal(
+ LayerType* layer,
+ const gfx::Transform& transform,
+ const gfx::Transform& sublayer_transform,
+ gfx::PointF anchor,
+ gfx::PointF position,
+ gfx::Size bounds,
+ bool preserves3d) {
+ layer->SetTransform(transform);
+ layer->SetSublayerTransform(sublayer_transform);
+ layer->SetAnchorPoint(anchor);
+ layer->SetPosition(position);
+ layer->SetBounds(bounds);
+ layer->SetPreserves3d(preserves3d);
+ }
+
+ void SetLayerPropertiesForTesting(Layer* layer,
+ const gfx::Transform& transform,
+ const gfx::Transform& sublayer_transform,
+ gfx::PointF anchor,
+ gfx::PointF position,
+ gfx::Size bounds,
+ bool preserves3d) {
+ SetLayerPropertiesForTestingInternal<Layer>(layer,
+ transform,
+ sublayer_transform,
+ anchor,
+ position,
+ bounds,
+ preserves3d);
+ }
+
+ void SetLayerPropertiesForTesting(LayerImpl* layer,
+ const gfx::Transform& transform,
+ const gfx::Transform& sublayer_transform,
+ gfx::PointF anchor,
+ gfx::PointF position,
+ gfx::Size bounds,
+ bool preserves3d) {
+ SetLayerPropertiesForTestingInternal<LayerImpl>(layer,
+ transform,
+ sublayer_transform,
+ anchor,
+ position,
+ bounds,
+ preserves3d);
+ layer->SetContentBounds(bounds);
+ }
+
+ void ExecuteCalculateDrawProperties(Layer* root_layer,
+ float device_scale_factor,
+ float page_scale_factor,
+ Layer* page_scale_application_layer,
+ bool can_use_lcd_text) {
+ EXPECT_TRUE(page_scale_application_layer || (page_scale_factor == 1.f));
+ gfx::Transform identity_matrix;
+ gfx::Size device_viewport_size =
+ gfx::Size(root_layer->bounds().width() * device_scale_factor,
+ root_layer->bounds().height() * device_scale_factor);
+
+ render_surface_layer_list_.reset(new RenderSurfaceLayerList);
+
+ // We are probably not testing what is intended if the root_layer bounds are
+ // empty.
+ DCHECK(!root_layer->bounds().IsEmpty());
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root_layer, device_viewport_size, render_surface_layer_list_.get());
+ inputs.device_scale_factor = device_scale_factor;
+ inputs.page_scale_factor = page_scale_factor;
+ inputs.page_scale_application_layer = page_scale_application_layer;
+ inputs.can_use_lcd_text = can_use_lcd_text;
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+ }
+
+ void ExecuteCalculateDrawProperties(LayerImpl* root_layer,
+ float device_scale_factor,
+ float page_scale_factor,
+ LayerImpl* page_scale_application_layer,
+ bool can_use_lcd_text) {
+ gfx::Transform identity_matrix;
+ LayerImplList dummy_render_surface_layer_list;
+ gfx::Size device_viewport_size =
+ gfx::Size(root_layer->bounds().width() * device_scale_factor,
+ root_layer->bounds().height() * device_scale_factor);
+
+ // We are probably not testing what is intended if the root_layer bounds are
+ // empty.
+ DCHECK(!root_layer->bounds().IsEmpty());
+ LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
+ root_layer, device_viewport_size, &dummy_render_surface_layer_list);
+ inputs.device_scale_factor = device_scale_factor;
+ inputs.page_scale_factor = page_scale_factor;
+ inputs.page_scale_application_layer = page_scale_application_layer;
+ inputs.can_use_lcd_text = can_use_lcd_text;
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+ }
+
+ template <class LayerType>
+ void ExecuteCalculateDrawProperties(LayerType* root_layer) {
+ LayerType* page_scale_application_layer = NULL;
+ ExecuteCalculateDrawProperties(
+ root_layer, 1.f, 1.f, page_scale_application_layer, false);
+ }
+
+ template <class LayerType>
+ void ExecuteCalculateDrawProperties(LayerType* root_layer,
+ float device_scale_factor) {
+ LayerType* page_scale_application_layer = NULL;
+ ExecuteCalculateDrawProperties(root_layer,
+ device_scale_factor,
+ 1.f,
+ page_scale_application_layer,
+ false);
+ }
+
+ template <class LayerType>
+ void ExecuteCalculateDrawProperties(LayerType* root_layer,
+ float device_scale_factor,
+ float page_scale_factor,
+ LayerType* page_scale_application_layer) {
+ ExecuteCalculateDrawProperties(root_layer,
+ device_scale_factor,
+ page_scale_factor,
+ page_scale_application_layer,
+ false);
+ }
+
+ private:
+ scoped_ptr<RenderSurfaceLayerList> render_surface_layer_list_;
+};
+
+class LayerTreeHostCommonTest : public LayerTreeHostCommonTestBase,
+ public testing::Test {
+};
+
+class LayerWithForcedDrawsContent : public Layer {
+ public:
+ LayerWithForcedDrawsContent() : Layer() {}
+
+ virtual bool DrawsContent() const OVERRIDE;
+
+ private:
+ virtual ~LayerWithForcedDrawsContent() {}
+};
+
+class LayerCanClipSelf : public Layer {
+ public:
+ LayerCanClipSelf() : Layer() {}
+
+ virtual bool DrawsContent() const OVERRIDE;
+ virtual bool CanClipSelf() const OVERRIDE;
+
+ private:
+ virtual ~LayerCanClipSelf() {}
+};
+
+bool LayerWithForcedDrawsContent::DrawsContent() const { return true; }
+
+bool LayerCanClipSelf::DrawsContent() const { return true; }
+
+bool LayerCanClipSelf::CanClipSelf() const { return true; }
+
+class MockContentLayerClient : public ContentLayerClient {
+ public:
+ MockContentLayerClient() {}
+ virtual ~MockContentLayerClient() {}
+ virtual void PaintContents(SkCanvas* canvas,
+ gfx::Rect clip,
+ gfx::RectF* opaque) OVERRIDE {}
+ virtual void DidChangeLayerCanUseLCDText() OVERRIDE {}
+};
+
+scoped_refptr<ContentLayer> CreateDrawableContentLayer(
+ ContentLayerClient* delegate) {
+ scoped_refptr<ContentLayer> to_return = ContentLayer::Create(delegate);
+ to_return->SetIsDrawable(true);
+ return to_return;
+}
+
+#define EXPECT_CONTENTS_SCALE_EQ(expected, layer) \
+ do { \
+ EXPECT_FLOAT_EQ(expected, layer->contents_scale_x()); \
+ EXPECT_FLOAT_EQ(expected, layer->contents_scale_y()); \
+ } while (false)
+
+TEST_F(LayerTreeHostCommonTest, TransformsForNoOpLayer) {
+ // Sanity check: For layers positioned at zero, with zero size,
+ // and with identity transforms, then the draw transform,
+ // screen space transform, and the hierarchy passed on to children
+ // layers should also be identity transforms.
+
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<Layer> child = Layer::Create();
+ scoped_refptr<Layer> grand_child = Layer::Create();
+ parent->AddChild(child);
+ child->AddChild(grand_child);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(parent);
+
+ gfx::Transform identity_matrix;
+ SetLayerPropertiesForTesting(parent.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(),
+ false);
+ SetLayerPropertiesForTesting(grand_child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(),
+ false);
+
+ ExecuteCalculateDrawProperties(parent.get());
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(identity_matrix, child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(identity_matrix,
+ child->screen_space_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(identity_matrix,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(identity_matrix,
+ grand_child->screen_space_transform());
+}
+
+TEST_F(LayerTreeHostCommonTest, TransformsForSingleLayer) {
+ gfx::Transform identity_matrix;
+ scoped_refptr<Layer> layer = Layer::Create();
+
+ scoped_refptr<Layer> root = Layer::Create();
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(1, 2),
+ false);
+ root->AddChild(layer);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ // Case 1: setting the sublayer transform should not affect this layer's draw
+ // transform or screen-space transform.
+ gfx::Transform arbitrary_translation;
+ arbitrary_translation.Translate(10.0, 20.0);
+ SetLayerPropertiesForTesting(layer.get(),
+ identity_matrix,
+ arbitrary_translation,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ ExecuteCalculateDrawProperties(root.get());
+ gfx::Transform expected_draw_transform = identity_matrix;
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_draw_transform,
+ layer->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(identity_matrix,
+ layer->screen_space_transform());
+
+ // Case 2: Setting the bounds of the layer should not affect either the draw
+ // transform or the screenspace transform.
+ gfx::Transform translation_to_center;
+ translation_to_center.Translate(5.0, 6.0);
+ SetLayerPropertiesForTesting(layer.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 12),
+ false);
+ ExecuteCalculateDrawProperties(root.get());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(identity_matrix, layer->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(identity_matrix,
+ layer->screen_space_transform());
+
+ // Case 3: The anchor point by itself (without a layer transform) should have
+ // no effect on the transforms.
+ SetLayerPropertiesForTesting(layer.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(0.25f, 0.25f),
+ gfx::PointF(),
+ gfx::Size(10, 12),
+ false);
+ ExecuteCalculateDrawProperties(root.get());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(identity_matrix, layer->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(identity_matrix,
+ layer->screen_space_transform());
+
+ // Case 4: A change in actual position affects both the draw transform and
+ // screen space transform.
+ gfx::Transform position_transform;
+ position_transform.Translate(0.0, 1.2);
+ SetLayerPropertiesForTesting(layer.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(0.25f, 0.25f),
+ gfx::PointF(0.f, 1.2f),
+ gfx::Size(10, 12),
+ false);
+ ExecuteCalculateDrawProperties(root.get());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(position_transform, layer->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(position_transform,
+ layer->screen_space_transform());
+
+ // Case 5: In the correct sequence of transforms, the layer transform should
+ // pre-multiply the translation_to_center. This is easily tested by using a
+ // scale transform, because scale and translation are not commutative.
+ gfx::Transform layer_transform;
+ layer_transform.Scale3d(2.0, 2.0, 1.0);
+ SetLayerPropertiesForTesting(layer.get(),
+ layer_transform,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 12),
+ false);
+ ExecuteCalculateDrawProperties(root.get());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(layer_transform, layer->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(layer_transform,
+ layer->screen_space_transform());
+
+ // Case 6: The layer transform should occur with respect to the anchor point.
+ gfx::Transform translation_to_anchor;
+ translation_to_anchor.Translate(5.0, 0.0);
+ gfx::Transform expected_result =
+ translation_to_anchor * layer_transform * Inverse(translation_to_anchor);
+ SetLayerPropertiesForTesting(layer.get(),
+ layer_transform,
+ identity_matrix,
+ gfx::PointF(0.5f, 0.f),
+ gfx::PointF(),
+ gfx::Size(10, 12),
+ false);
+ ExecuteCalculateDrawProperties(root.get());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_result, layer->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_result,
+ layer->screen_space_transform());
+
+ // Case 7: Verify that position pre-multiplies the layer transform. The
+ // current implementation of CalculateDrawProperties does this implicitly, but
+ // it is still worth testing to detect accidental regressions.
+ expected_result = position_transform * translation_to_anchor *
+ layer_transform * Inverse(translation_to_anchor);
+ SetLayerPropertiesForTesting(layer.get(),
+ layer_transform,
+ identity_matrix,
+ gfx::PointF(0.5f, 0.f),
+ gfx::PointF(0.f, 1.2f),
+ gfx::Size(10, 12),
+ false);
+ ExecuteCalculateDrawProperties(root.get());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_result, layer->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_result,
+ layer->screen_space_transform());
+}
+
+TEST_F(LayerTreeHostCommonTest, TransformsAboutScrollOffset) {
+ const gfx::Vector2d kScrollOffset(50, 100);
+ const gfx::Vector2dF kScrollDelta(2.34f, 5.67f);
+ const gfx::Vector2d kMaxScrollOffset(200, 200);
+ const gfx::PointF kScrollLayerPosition(-kScrollOffset.x(),
+ -kScrollOffset.y());
+ const float kPageScale = 0.888f;
+ const float kDeviceScale = 1.666f;
+
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+
+ gfx::Transform identity_matrix;
+ scoped_ptr<LayerImpl> sublayer_scoped_ptr(
+ LayerImpl::Create(host_impl.active_tree(), 1));
+ LayerImpl* sublayer = sublayer_scoped_ptr.get();
+ sublayer->SetContentsScale(kPageScale * kDeviceScale,
+ kPageScale * kDeviceScale);
+ SetLayerPropertiesForTesting(sublayer,
+ identity_matrix,
+ identity_matrix,
+ gfx::Point(),
+ gfx::PointF(),
+ gfx::Size(500, 500),
+ false);
+
+ scoped_ptr<LayerImpl> scroll_layerScopedPtr(
+ LayerImpl::Create(host_impl.active_tree(), 2));
+ LayerImpl* scroll_layer = scroll_layerScopedPtr.get();
+ SetLayerPropertiesForTesting(scroll_layer,
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 20),
+ false);
+ scroll_layer->SetScrollable(true);
+ scroll_layer->SetMaxScrollOffset(kMaxScrollOffset);
+ scroll_layer->SetScrollOffset(kScrollOffset);
+ scroll_layer->SetScrollDelta(kScrollDelta);
+ gfx::Transform impl_transform;
+ scroll_layer->AddChild(sublayer_scoped_ptr.Pass());
+
+ scoped_ptr<LayerImpl> root(LayerImpl::Create(host_impl.active_tree(), 3));
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(3, 4),
+ false);
+ root->AddChild(scroll_layerScopedPtr.Pass());
+
+ ExecuteCalculateDrawProperties(
+ root.get(), kDeviceScale, kPageScale, scroll_layer->parent());
+ gfx::Transform expected_transform = identity_matrix;
+ gfx::PointF sub_layer_screen_position = kScrollLayerPosition - kScrollDelta;
+ sub_layer_screen_position.Scale(kPageScale * kDeviceScale);
+ expected_transform.Translate(MathUtil::Round(sub_layer_screen_position.x()),
+ MathUtil::Round(sub_layer_screen_position.y()));
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_transform,
+ sublayer->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_transform,
+ sublayer->screen_space_transform());
+
+ gfx::Transform arbitrary_translate;
+ const float kTranslateX = 10.6f;
+ const float kTranslateY = 20.6f;
+ arbitrary_translate.Translate(kTranslateX, kTranslateY);
+ SetLayerPropertiesForTesting(scroll_layer,
+ arbitrary_translate,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 20),
+ false);
+ ExecuteCalculateDrawProperties(
+ root.get(), kDeviceScale, kPageScale, scroll_layer->parent());
+ expected_transform.MakeIdentity();
+ expected_transform.Translate(
+ MathUtil::Round(kTranslateX * kPageScale * kDeviceScale +
+ sub_layer_screen_position.x()),
+ MathUtil::Round(kTranslateY * kPageScale * kDeviceScale +
+ sub_layer_screen_position.y()));
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_transform,
+ sublayer->draw_transform());
+}
+
+TEST_F(LayerTreeHostCommonTest, TransformsForSimpleHierarchy) {
+ gfx::Transform identity_matrix;
+ scoped_refptr<Layer> root = Layer::Create();
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<Layer> child = Layer::Create();
+ scoped_refptr<Layer> grand_child = Layer::Create();
+ root->AddChild(parent);
+ parent->AddChild(child);
+ child->AddChild(grand_child);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ // One-time setup of root layer
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(1, 2),
+ false);
+
+ // Case 1: parent's anchor point should not affect child or grand_child.
+ SetLayerPropertiesForTesting(parent.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(0.25f, 0.25f),
+ gfx::PointF(),
+ gfx::Size(10, 12),
+ false);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(16, 18),
+ false);
+ SetLayerPropertiesForTesting(grand_child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(76, 78),
+ false);
+ ExecuteCalculateDrawProperties(root.get());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(identity_matrix, child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(identity_matrix,
+ child->screen_space_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(identity_matrix,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(identity_matrix,
+ grand_child->screen_space_transform());
+
+ // Case 2: parent's position affects child and grand_child.
+ gfx::Transform parent_position_transform;
+ parent_position_transform.Translate(0.0, 1.2);
+ SetLayerPropertiesForTesting(parent.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(0.25f, 0.25f),
+ gfx::PointF(0.f, 1.2f),
+ gfx::Size(10, 12),
+ false);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(16, 18),
+ false);
+ SetLayerPropertiesForTesting(grand_child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(76, 78),
+ false);
+ ExecuteCalculateDrawProperties(root.get());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(parent_position_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(parent_position_transform,
+ child->screen_space_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(parent_position_transform,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(parent_position_transform,
+ grand_child->screen_space_transform());
+
+ // Case 3: parent's local transform affects child and grandchild
+ gfx::Transform parent_layer_transform;
+ parent_layer_transform.Scale3d(2.0, 2.0, 1.0);
+ gfx::Transform parent_translation_to_anchor;
+ parent_translation_to_anchor.Translate(2.5, 3.0);
+ gfx::Transform parent_composite_transform =
+ parent_translation_to_anchor * parent_layer_transform *
+ Inverse(parent_translation_to_anchor);
+ SetLayerPropertiesForTesting(parent.get(),
+ parent_layer_transform,
+ identity_matrix,
+ gfx::PointF(0.25f, 0.25f),
+ gfx::PointF(),
+ gfx::Size(10, 12),
+ false);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(16, 18),
+ false);
+ SetLayerPropertiesForTesting(grand_child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(76, 78),
+ false);
+ ExecuteCalculateDrawProperties(root.get());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(parent_composite_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(parent_composite_transform,
+ child->screen_space_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(parent_composite_transform,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(parent_composite_transform,
+ grand_child->screen_space_transform());
+
+ // Case 4: parent's sublayer matrix affects child and grandchild scaling is
+ // used here again so that the correct sequence of transforms is properly
+ // tested. Note that preserves3d is false, but the sublayer matrix should
+ // retain its 3D properties when given to child. But then, the child also
+ // does not preserve3D. When it gives its hierarchy to the grand_child, it
+ // should be flattened to 2D.
+ gfx::Transform parent_sublayer_matrix;
+ parent_sublayer_matrix.Scale3d(10.0, 10.0, 3.3);
+ // Sublayer matrix is applied to the anchor point of the parent layer.
+ parent_composite_transform =
+ parent_translation_to_anchor * parent_layer_transform *
+ Inverse(parent_translation_to_anchor) * parent_translation_to_anchor *
+ parent_sublayer_matrix * Inverse(parent_translation_to_anchor);
+ gfx::Transform flattened_composite_transform = parent_composite_transform;
+ flattened_composite_transform.FlattenTo2d();
+ SetLayerPropertiesForTesting(parent.get(),
+ parent_layer_transform,
+ parent_sublayer_matrix,
+ gfx::PointF(0.25f, 0.25f),
+ gfx::PointF(),
+ gfx::Size(10, 12),
+ false);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(16, 18),
+ false);
+ SetLayerPropertiesForTesting(grand_child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(76, 78),
+ false);
+ ExecuteCalculateDrawProperties(root.get());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(parent_composite_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(parent_composite_transform,
+ child->screen_space_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(flattened_composite_transform,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(flattened_composite_transform,
+ grand_child->screen_space_transform());
+
+ // Case 5: same as Case 4, except that child does preserve 3D, so the
+ // grand_child should receive the non-flattened composite transform.
+ SetLayerPropertiesForTesting(parent.get(),
+ parent_layer_transform,
+ parent_sublayer_matrix,
+ gfx::PointF(0.25f, 0.25f),
+ gfx::PointF(),
+ gfx::Size(10, 12),
+ false);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(16, 18),
+ true);
+ SetLayerPropertiesForTesting(grand_child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(76, 78),
+ false);
+ ExecuteCalculateDrawProperties(root.get());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(parent_composite_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(parent_composite_transform,
+ child->screen_space_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(parent_composite_transform,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(parent_composite_transform,
+ grand_child->screen_space_transform());
+}
+
+TEST_F(LayerTreeHostCommonTest, TransformsForSingleRenderSurface) {
+ scoped_refptr<Layer> root = Layer::Create();
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<Layer> child = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> grand_child =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ root->AddChild(parent);
+ parent->AddChild(child);
+ child->AddChild(grand_child);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ // One-time setup of root layer
+ gfx::Transform identity_matrix;
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(1, 2),
+ false);
+
+ // Child is set up so that a new render surface should be created.
+ child->SetOpacity(0.5f);
+ child->SetForceRenderSurface(true);
+
+ gfx::Transform parent_layer_transform;
+ parent_layer_transform.Scale3d(1.0, 0.9, 1.0);
+ gfx::Transform parent_translation_to_anchor;
+ parent_translation_to_anchor.Translate(25.0, 30.0);
+ gfx::Transform parent_sublayer_matrix;
+ parent_sublayer_matrix.Scale3d(0.9, 1.0, 3.3);
+
+ gfx::Transform parent_composite_transform =
+ parent_translation_to_anchor * parent_layer_transform *
+ Inverse(parent_translation_to_anchor) * parent_translation_to_anchor *
+ parent_sublayer_matrix * Inverse(parent_translation_to_anchor);
+ gfx::Vector2dF parent_composite_scale =
+ MathUtil::ComputeTransform2dScaleComponents(parent_composite_transform,
+ 1.f);
+ gfx::Transform surface_sublayer_transform;
+ surface_sublayer_transform.Scale(parent_composite_scale.x(),
+ parent_composite_scale.y());
+ gfx::Transform surface_sublayer_composite_transform =
+ parent_composite_transform * Inverse(surface_sublayer_transform);
+
+ // Child's render surface should not exist yet.
+ ASSERT_FALSE(child->render_surface());
+
+ SetLayerPropertiesForTesting(parent.get(),
+ parent_layer_transform,
+ parent_sublayer_matrix,
+ gfx::PointF(0.25f, 0.25f),
+ gfx::PointF(),
+ gfx::Size(100, 120),
+ false);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(16, 18),
+ false);
+ SetLayerPropertiesForTesting(grand_child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(8, 10),
+ false);
+ ExecuteCalculateDrawProperties(root.get());
+
+ // Render surface should have been created now.
+ ASSERT_TRUE(child->render_surface());
+ ASSERT_EQ(child, child->render_target());
+
+ // The child layer's draw transform should refer to its new render surface.
+ // The screen-space transform, however, should still refer to the root.
+ EXPECT_TRANSFORMATION_MATRIX_EQ(surface_sublayer_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(parent_composite_transform,
+ child->screen_space_transform());
+
+ // Because the grand_child is the only drawable content, the child's render
+ // surface will tighten its bounds to the grand_child. The scale at which the
+ // surface's subtree is drawn must be removed from the composite transform.
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ surface_sublayer_composite_transform,
+ child->render_target()->render_surface()->draw_transform());
+
+ // The screen space is the same as the target since the child surface draws
+ // into the root.
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ surface_sublayer_composite_transform,
+ child->render_target()->render_surface()->screen_space_transform());
+}
+
+TEST_F(LayerTreeHostCommonTest, SublayerTransformWithAnchorPoint) {
+ // crbug.com/157961 - we were always applying the sublayer transform about
+ // the center of the layer, rather than the anchor point.
+
+ scoped_refptr<Layer> root = Layer::Create();
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> child =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ root->AddChild(parent);
+ parent->AddChild(child);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ gfx::Transform identity_matrix;
+ gfx::Transform parent_sublayer_matrix;
+ parent_sublayer_matrix.ApplyPerspectiveDepth(2.0);
+ gfx::PointF parent_anchor_point(0.2f, 0.8f);
+
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(1, 2),
+ false);
+ SetLayerPropertiesForTesting(parent.get(),
+ identity_matrix,
+ parent_sublayer_matrix,
+ parent_anchor_point,
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+ ExecuteCalculateDrawProperties(root.get());
+
+ gfx::Transform expected_child_draw_transform;
+ expected_child_draw_transform.Translate(20.0, 80.0);
+ expected_child_draw_transform.ApplyPerspectiveDepth(2.0);
+ expected_child_draw_transform.Translate(-20.0, -80.0);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_draw_transform,
+ child->draw_transform());
+}
+
+TEST_F(LayerTreeHostCommonTest, SeparateRenderTargetRequirementWithClipping) {
+ scoped_refptr<Layer> root = Layer::Create();
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<Layer> child = Layer::Create();
+ scoped_refptr<Layer> grand_child = make_scoped_refptr(new LayerCanClipSelf());
+ root->AddChild(parent);
+ parent->AddChild(child);
+ child->AddChild(grand_child);
+ parent->SetMasksToBounds(true);
+ child->SetMasksToBounds(true);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ gfx::Transform identity_matrix;
+ gfx::Transform parent_layer_transform;
+ gfx::Transform parent_sublayer_matrix;
+ gfx::Transform child_layer_matrix;
+
+ // No render surface should exist yet.
+ EXPECT_FALSE(root->render_surface());
+ EXPECT_FALSE(parent->render_surface());
+ EXPECT_FALSE(child->render_surface());
+ EXPECT_FALSE(grand_child->render_surface());
+
+ // One-time setup of root layer
+ parent_layer_transform.Scale3d(1.0, 0.9, 1.0);
+ parent_sublayer_matrix.Scale3d(0.9, 1.0, 3.3);
+ child_layer_matrix.Rotate(20.0);
+
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(1, 2),
+ false);
+ SetLayerPropertiesForTesting(parent.get(),
+ parent_layer_transform,
+ parent_sublayer_matrix,
+ gfx::PointF(0.25f, 0.25f),
+ gfx::PointF(),
+ gfx::Size(100, 120),
+ false);
+ SetLayerPropertiesForTesting(child.get(),
+ child_layer_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(16, 18),
+ false);
+ SetLayerPropertiesForTesting(grand_child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(8, 10),
+ false);
+
+ ExecuteCalculateDrawProperties(root.get());
+
+ // Render surfaces should have been created according to clipping rules now
+ // (grandchild can clip self).
+ EXPECT_TRUE(root->render_surface());
+ EXPECT_FALSE(parent->render_surface());
+ EXPECT_FALSE(child->render_surface());
+ EXPECT_FALSE(grand_child->render_surface());
+}
+
+TEST_F(LayerTreeHostCommonTest,
+ SeparateRenderTargetRequirementWithoutClipping) {
+ scoped_refptr<Layer> root = Layer::Create();
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<Layer> child = Layer::Create();
+ // This layer cannot clip itself, a feature we are testing here.
+ scoped_refptr<Layer> grand_child =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ root->AddChild(parent);
+ parent->AddChild(child);
+ child->AddChild(grand_child);
+ parent->SetMasksToBounds(true);
+ child->SetMasksToBounds(true);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ gfx::Transform identity_matrix;
+ gfx::Transform parent_layer_transform;
+ gfx::Transform parent_sublayer_matrix;
+ gfx::Transform child_layer_matrix;
+
+ // No render surface should exist yet.
+ EXPECT_FALSE(root->render_surface());
+ EXPECT_FALSE(parent->render_surface());
+ EXPECT_FALSE(child->render_surface());
+ EXPECT_FALSE(grand_child->render_surface());
+
+ // One-time setup of root layer
+ parent_layer_transform.Scale3d(1.0, 0.9, 1.0);
+ parent_sublayer_matrix.Scale3d(0.9, 1.0, 3.3);
+ child_layer_matrix.Rotate(20.0);
+
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(1, 2),
+ false);
+ SetLayerPropertiesForTesting(parent.get(),
+ parent_layer_transform,
+ parent_sublayer_matrix,
+ gfx::PointF(0.25f, 0.25f),
+ gfx::PointF(),
+ gfx::Size(100, 120),
+ false);
+ SetLayerPropertiesForTesting(child.get(),
+ child_layer_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(16, 18),
+ false);
+ SetLayerPropertiesForTesting(grand_child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(8, 10),
+ false);
+
+ ExecuteCalculateDrawProperties(root.get());
+
+ // Render surfaces should have been created according to clipping rules now
+ // (grandchild can't clip self).
+ EXPECT_TRUE(root->render_surface());
+ EXPECT_FALSE(parent->render_surface());
+ EXPECT_TRUE(child->render_surface());
+ EXPECT_FALSE(grand_child->render_surface());
+}
+
+TEST_F(LayerTreeHostCommonTest, TransformsForReplica) {
+ scoped_refptr<Layer> root = Layer::Create();
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<Layer> child = Layer::Create();
+ scoped_refptr<Layer> child_replica = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> grand_child =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ root->AddChild(parent);
+ parent->AddChild(child);
+ child->AddChild(grand_child);
+ child->SetReplicaLayer(child_replica.get());
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ // One-time setup of root layer
+ gfx::Transform identity_matrix;
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(1, 2),
+ false);
+
+ // Child is set up so that a new render surface should be created.
+ child->SetOpacity(0.5f);
+
+ gfx::Transform parent_layer_transform;
+ parent_layer_transform.Scale3d(2.0, 2.0, 1.0);
+ gfx::Transform parent_translation_to_anchor;
+ parent_translation_to_anchor.Translate(2.5, 3.0);
+ gfx::Transform parent_sublayer_matrix;
+ parent_sublayer_matrix.Scale3d(10.0, 10.0, 3.3);
+ gfx::Transform parent_composite_transform =
+ parent_translation_to_anchor * parent_layer_transform *
+ Inverse(parent_translation_to_anchor) * parent_translation_to_anchor *
+ parent_sublayer_matrix * Inverse(parent_translation_to_anchor);
+ gfx::Transform replica_layer_transform;
+ replica_layer_transform.Scale3d(3.0, 3.0, 1.0);
+ gfx::Vector2dF parent_composite_scale =
+ MathUtil::ComputeTransform2dScaleComponents(parent_composite_transform,
+ 1.f);
+ gfx::Transform surface_sublayer_transform;
+ surface_sublayer_transform.Scale(parent_composite_scale.x(),
+ parent_composite_scale.y());
+ gfx::Transform replica_composite_transform =
+ parent_composite_transform * replica_layer_transform *
+ Inverse(surface_sublayer_transform);
+
+ // Child's render surface should not exist yet.
+ ASSERT_FALSE(child->render_surface());
+
+ SetLayerPropertiesForTesting(parent.get(),
+ parent_layer_transform,
+ parent_sublayer_matrix,
+ gfx::PointF(0.25f, 0.25f),
+ gfx::PointF(),
+ gfx::Size(10, 12),
+ false);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(16, 18),
+ false);
+ SetLayerPropertiesForTesting(grand_child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(-0.5f, -0.5f),
+ gfx::Size(1, 1),
+ false);
+ SetLayerPropertiesForTesting(child_replica.get(),
+ replica_layer_transform,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(),
+ false);
+ ExecuteCalculateDrawProperties(root.get());
+
+ // Render surface should have been created now.
+ ASSERT_TRUE(child->render_surface());
+ ASSERT_EQ(child, child->render_target());
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ replica_composite_transform,
+ child->render_target()->render_surface()->replica_draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(replica_composite_transform,
+ child->render_target()->render_surface()
+ ->replica_screen_space_transform());
+}
+
+TEST_F(LayerTreeHostCommonTest, TransformsForRenderSurfaceHierarchy) {
+ // This test creates a more complex tree and verifies it all at once. This
+ // covers the following cases:
+ // - layers that are described w.r.t. a render surface: should have draw
+ // transforms described w.r.t. that surface
+ // - A render surface described w.r.t. an ancestor render surface: should
+ // have a draw transform described w.r.t. that ancestor surface
+ // - Replicas of a render surface are described w.r.t. the replica's
+ // transform around its anchor, along with the surface itself.
+ // - Sanity check on recursion: verify transforms of layers described w.r.t.
+ // a render surface that is described w.r.t. an ancestor render surface.
+ // - verifying that each layer has a reference to the correct render surface
+ // and render target values.
+
+ scoped_refptr<Layer> root = Layer::Create();
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<Layer> render_surface1 = Layer::Create();
+ scoped_refptr<Layer> render_surface2 = Layer::Create();
+ scoped_refptr<Layer> child_of_root = Layer::Create();
+ scoped_refptr<Layer> child_of_rs1 = Layer::Create();
+ scoped_refptr<Layer> child_of_rs2 = Layer::Create();
+ scoped_refptr<Layer> replica_of_rs1 = Layer::Create();
+ scoped_refptr<Layer> replica_of_rs2 = Layer::Create();
+ scoped_refptr<Layer> grand_child_of_root = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> grand_child_of_rs1 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> grand_child_of_rs2 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ root->AddChild(parent);
+ parent->AddChild(render_surface1);
+ parent->AddChild(child_of_root);
+ render_surface1->AddChild(child_of_rs1);
+ render_surface1->AddChild(render_surface2);
+ render_surface2->AddChild(child_of_rs2);
+ child_of_root->AddChild(grand_child_of_root);
+ child_of_rs1->AddChild(grand_child_of_rs1);
+ child_of_rs2->AddChild(grand_child_of_rs2);
+ render_surface1->SetReplicaLayer(replica_of_rs1.get());
+ render_surface2->SetReplicaLayer(replica_of_rs2.get());
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ // In combination with descendant draws content, opacity != 1 forces the layer
+ // to have a new render surface.
+ render_surface1->SetOpacity(0.5f);
+ render_surface2->SetOpacity(0.33f);
+
+ // One-time setup of root layer
+ gfx::Transform identity_matrix;
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(1, 2),
+ false);
+
+ // All layers in the tree are initialized with an anchor at .25 and a size of
+ // (10,10). matrix "A" is the composite layer transform used in all layers,
+ // centered about the anchor point. matrix "B" is the sublayer transform used
+ // in all layers, centered about the center position of the layer. matrix "R"
+ // is the composite replica transform used in all replica layers.
+ //
+ // x component tests that layer_transform and sublayer_transform are done in
+ // the right order (translation and scale are noncommutative). y component
+ // has a translation by 1 for every ancestor, which indicates the "depth" of
+ // the layer in the hierarchy.
+ gfx::Transform translation_to_anchor;
+ translation_to_anchor.Translate(2.5, 0.0);
+ gfx::Transform layer_transform;
+ layer_transform.Translate(1.0, 1.0);
+ gfx::Transform sublayer_transform;
+ sublayer_transform.Scale3d(10.0, 1.0, 1.0);
+ gfx::Transform replica_layer_transform;
+ replica_layer_transform.Scale3d(-2.0, 5.0, 1.0);
+
+ gfx::Transform A =
+ translation_to_anchor * layer_transform * Inverse(translation_to_anchor);
+ gfx::Transform B = translation_to_anchor * sublayer_transform *
+ Inverse(translation_to_anchor);
+ gfx::Transform R = A * translation_to_anchor * replica_layer_transform *
+ Inverse(translation_to_anchor);
+
+ gfx::Vector2dF surface1_parent_transform_scale =
+ MathUtil::ComputeTransform2dScaleComponents(A * B, 1.f);
+ gfx::Transform surface1_sublayer_transform;
+ surface1_sublayer_transform.Scale(surface1_parent_transform_scale.x(),
+ surface1_parent_transform_scale.y());
+
+ // SS1 = transform given to the subtree of render_surface1
+ gfx::Transform SS1 = surface1_sublayer_transform;
+ // S1 = transform to move from render_surface1 pixels to the layer space of
+ // the owning layer
+ gfx::Transform S1 = Inverse(surface1_sublayer_transform);
+
+ gfx::Vector2dF surface2_parent_transform_scale =
+ MathUtil::ComputeTransform2dScaleComponents(SS1 * A * B, 1.f);
+ gfx::Transform surface2_sublayer_transform;
+ surface2_sublayer_transform.Scale(surface2_parent_transform_scale.x(),
+ surface2_parent_transform_scale.y());
+
+ // SS2 = transform given to the subtree of render_surface2
+ gfx::Transform SS2 = surface2_sublayer_transform;
+ // S2 = transform to move from render_surface2 pixels to the layer space of
+ // the owning layer
+ gfx::Transform S2 = Inverse(surface2_sublayer_transform);
+
+ SetLayerPropertiesForTesting(parent.get(),
+ layer_transform,
+ sublayer_transform,
+ gfx::PointF(0.25f, 0.f),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(render_surface1.get(),
+ layer_transform,
+ sublayer_transform,
+ gfx::PointF(0.25f, 0.f),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(render_surface2.get(),
+ layer_transform,
+ sublayer_transform,
+ gfx::PointF(0.25f, 0.f),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(child_of_root.get(),
+ layer_transform,
+ sublayer_transform,
+ gfx::PointF(0.25f, 0.f),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(child_of_rs1.get(),
+ layer_transform,
+ sublayer_transform,
+ gfx::PointF(0.25f, 0.f),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(child_of_rs2.get(),
+ layer_transform,
+ sublayer_transform,
+ gfx::PointF(0.25f, 0.f),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(grand_child_of_root.get(),
+ layer_transform,
+ sublayer_transform,
+ gfx::PointF(0.25f, 0.f),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(grand_child_of_rs1.get(),
+ layer_transform,
+ sublayer_transform,
+ gfx::PointF(0.25f, 0.f),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(grand_child_of_rs2.get(),
+ layer_transform,
+ sublayer_transform,
+ gfx::PointF(0.25f, 0.f),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(replica_of_rs1.get(),
+ replica_layer_transform,
+ sublayer_transform,
+ gfx::PointF(0.25f, 0.f),
+ gfx::PointF(),
+ gfx::Size(),
+ false);
+ SetLayerPropertiesForTesting(replica_of_rs2.get(),
+ replica_layer_transform,
+ sublayer_transform,
+ gfx::PointF(0.25f, 0.f),
+ gfx::PointF(),
+ gfx::Size(),
+ false);
+
+ ExecuteCalculateDrawProperties(root.get());
+
+ // Only layers that are associated with render surfaces should have an actual
+ // RenderSurface() value.
+ ASSERT_TRUE(root->render_surface());
+ ASSERT_FALSE(child_of_root->render_surface());
+ ASSERT_FALSE(grand_child_of_root->render_surface());
+
+ ASSERT_TRUE(render_surface1->render_surface());
+ ASSERT_FALSE(child_of_rs1->render_surface());
+ ASSERT_FALSE(grand_child_of_rs1->render_surface());
+
+ ASSERT_TRUE(render_surface2->render_surface());
+ ASSERT_FALSE(child_of_rs2->render_surface());
+ ASSERT_FALSE(grand_child_of_rs2->render_surface());
+
+ // Verify all render target accessors
+ EXPECT_EQ(root, parent->render_target());
+ EXPECT_EQ(root, child_of_root->render_target());
+ EXPECT_EQ(root, grand_child_of_root->render_target());
+
+ EXPECT_EQ(render_surface1, render_surface1->render_target());
+ EXPECT_EQ(render_surface1, child_of_rs1->render_target());
+ EXPECT_EQ(render_surface1, grand_child_of_rs1->render_target());
+
+ EXPECT_EQ(render_surface2, render_surface2->render_target());
+ EXPECT_EQ(render_surface2, child_of_rs2->render_target());
+ EXPECT_EQ(render_surface2, grand_child_of_rs2->render_target());
+
+ // Verify layer draw transforms note that draw transforms are described with
+ // respect to the nearest ancestor render surface but screen space transforms
+ // are described with respect to the root.
+ EXPECT_TRANSFORMATION_MATRIX_EQ(A, parent->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(A * B * A, child_of_root->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(A * B * A * B * A,
+ grand_child_of_root->draw_transform());
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(SS1, render_surface1->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(SS1 * B * A, child_of_rs1->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(SS1 * B * A * B * A,
+ grand_child_of_rs1->draw_transform());
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(SS2, render_surface2->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(SS2 * B * A, child_of_rs2->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(SS2 * B * A * B * A,
+ grand_child_of_rs2->draw_transform());
+
+ // Verify layer screen-space transforms
+ //
+ EXPECT_TRANSFORMATION_MATRIX_EQ(A, parent->screen_space_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(A * B * A,
+ child_of_root->screen_space_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ A * B * A * B * A, grand_child_of_root->screen_space_transform());
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(A * B * A,
+ render_surface1->screen_space_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(A * B * A * B * A,
+ child_of_rs1->screen_space_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(A * B * A * B * A * B * A,
+ grand_child_of_rs1->screen_space_transform());
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(A * B * A * B * A,
+ render_surface2->screen_space_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(A * B * A * B * A * B * A,
+ child_of_rs2->screen_space_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(A * B * A * B * A * B * A * B * A,
+ grand_child_of_rs2->screen_space_transform());
+
+ // Verify render surface transforms.
+ //
+ // Draw transform of render surface 1 is described with respect to root.
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ A * B * A * S1, render_surface1->render_surface()->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ A * B * R * S1,
+ render_surface1->render_surface()->replica_draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ A * B * A * S1,
+ render_surface1->render_surface()->screen_space_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ A * B * R * S1,
+ render_surface1->render_surface()->replica_screen_space_transform());
+ // Draw transform of render surface 2 is described with respect to render
+ // surface 1.
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ SS1 * B * A * S2, render_surface2->render_surface()->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ SS1 * B * R * S2,
+ render_surface2->render_surface()->replica_draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ A * B * A * B * A * S2,
+ render_surface2->render_surface()->screen_space_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ A * B * A * B * R * S2,
+ render_surface2->render_surface()->replica_screen_space_transform());
+
+ // Sanity check. If these fail there is probably a bug in the test itself. It
+ // is expected that we correctly set up transforms so that the y-component of
+ // the screen-space transform encodes the "depth" of the layer in the tree.
+ EXPECT_FLOAT_EQ(1.0,
+ parent->screen_space_transform().matrix().getDouble(1, 3));
+ EXPECT_FLOAT_EQ(
+ 2.0, child_of_root->screen_space_transform().matrix().getDouble(1, 3));
+ EXPECT_FLOAT_EQ(
+ 3.0,
+ grand_child_of_root->screen_space_transform().matrix().getDouble(1, 3));
+
+ EXPECT_FLOAT_EQ(
+ 2.0, render_surface1->screen_space_transform().matrix().getDouble(1, 3));
+ EXPECT_FLOAT_EQ(
+ 3.0, child_of_rs1->screen_space_transform().matrix().getDouble(1, 3));
+ EXPECT_FLOAT_EQ(
+ 4.0,
+ grand_child_of_rs1->screen_space_transform().matrix().getDouble(1, 3));
+
+ EXPECT_FLOAT_EQ(
+ 3.0, render_surface2->screen_space_transform().matrix().getDouble(1, 3));
+ EXPECT_FLOAT_EQ(
+ 4.0, child_of_rs2->screen_space_transform().matrix().getDouble(1, 3));
+ EXPECT_FLOAT_EQ(
+ 5.0,
+ grand_child_of_rs2->screen_space_transform().matrix().getDouble(1, 3));
+}
+
+TEST_F(LayerTreeHostCommonTest, TransformsForFlatteningLayer) {
+ // For layers that flatten their subtree, there should be an orthographic
+ // projection (for x and y values) in the middle of the transform sequence.
+ // Note that the way the code is currently implemented, it is not expected to
+ // use a canonical orthographic projection.
+
+ scoped_refptr<Layer> root = Layer::Create();
+ scoped_refptr<Layer> child = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> grand_child =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+
+ gfx::Transform rotation_about_y_axis;
+ rotation_about_y_axis.RotateAboutYAxis(30.0);
+
+ const gfx::Transform identity_matrix;
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(child.get(),
+ rotation_about_y_axis,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(grand_child.get(),
+ rotation_about_y_axis,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+
+ root->AddChild(child);
+ child->AddChild(grand_child);
+ child->SetForceRenderSurface(true);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ // No layers in this test should preserve 3d.
+ ASSERT_FALSE(root->preserves_3d());
+ ASSERT_FALSE(child->preserves_3d());
+ ASSERT_FALSE(grand_child->preserves_3d());
+
+ gfx::Transform expected_child_draw_transform = rotation_about_y_axis;
+ gfx::Transform expected_child_screen_space_transform = rotation_about_y_axis;
+ gfx::Transform expected_grand_child_draw_transform =
+ rotation_about_y_axis; // draws onto child's render surface
+ gfx::Transform flattened_rotation_about_y = rotation_about_y_axis;
+ flattened_rotation_about_y.FlattenTo2d();
+ gfx::Transform expected_grand_child_screen_space_transform =
+ flattened_rotation_about_y * rotation_about_y_axis;
+
+ ExecuteCalculateDrawProperties(root.get());
+
+ // The child's draw transform should have been taken by its surface.
+ ASSERT_TRUE(child->render_surface());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_draw_transform,
+ child->render_surface()->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected_child_screen_space_transform,
+ child->render_surface()->screen_space_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(identity_matrix, child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_screen_space_transform,
+ child->screen_space_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_draw_transform,
+ grand_child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_grand_child_screen_space_transform,
+ grand_child->screen_space_transform());
+}
+
+TEST_F(LayerTreeHostCommonTest, TransformsForDegenerateIntermediateLayer) {
+ // A layer that is empty in one axis, but not the other, was accidentally
+ // skipping a necessary translation. Without that translation, the coordinate
+ // space of the layer's draw transform is incorrect.
+ //
+ // Normally this isn't a problem, because the layer wouldn't be drawn anyway,
+ // but if that layer becomes a render surface, then its draw transform is
+ // implicitly inherited by the rest of the subtree, which then is positioned
+ // incorrectly as a result.
+
+ scoped_refptr<Layer> root = Layer::Create();
+ scoped_refptr<Layer> child = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> grand_child =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+
+ // The child height is zero, but has non-zero width that should be accounted
+ // for while computing draw transforms.
+ const gfx::Transform identity_matrix;
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 0),
+ false);
+ SetLayerPropertiesForTesting(grand_child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+
+ root->AddChild(child);
+ child->AddChild(grand_child);
+ child->SetForceRenderSurface(true);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ ExecuteCalculateDrawProperties(root.get());
+
+ ASSERT_TRUE(child->render_surface());
+ // This is the real test, the rest are sanity checks.
+ EXPECT_TRANSFORMATION_MATRIX_EQ(identity_matrix,
+ child->render_surface()->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(identity_matrix, child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(identity_matrix,
+ grand_child->draw_transform());
+}
+
+TEST_F(LayerTreeHostCommonTest, TransformAboveRootLayer) {
+ // Transformations applied at the root of the tree should be forwarded
+ // to child layers instead of applied to the root RenderSurface.
+ const gfx::Transform identity_matrix;
+ scoped_refptr<Layer> root = Layer::Create();
+ scoped_refptr<Layer> child = Layer::Create();
+ child->SetScrollable(true);
+ root->AddChild(child);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(20, 20),
+ false);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(20, 20),
+ false);
+
+ gfx::Transform translate;
+ translate.Translate(50, 50);
+ {
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), root->bounds(), translate, &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+ EXPECT_EQ(translate, root->draw_properties().target_space_transform);
+ EXPECT_EQ(translate, child->draw_properties().target_space_transform);
+ EXPECT_EQ(identity_matrix, root->render_surface()->draw_transform());
+ }
+
+ gfx::Transform scale;
+ scale.Scale(2, 2);
+ {
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), root->bounds(), scale, &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+ EXPECT_EQ(scale, root->draw_properties().target_space_transform);
+ EXPECT_EQ(scale, child->draw_properties().target_space_transform);
+ EXPECT_EQ(identity_matrix, root->render_surface()->draw_transform());
+ }
+
+ gfx::Transform rotate;
+ rotate.Rotate(2);
+ {
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), root->bounds(), rotate, &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+ EXPECT_EQ(rotate, root->draw_properties().target_space_transform);
+ EXPECT_EQ(rotate, child->draw_properties().target_space_transform);
+ EXPECT_EQ(identity_matrix, root->render_surface()->draw_transform());
+ }
+
+ gfx::Transform composite;
+ composite.ConcatTransform(translate);
+ composite.ConcatTransform(scale);
+ composite.ConcatTransform(rotate);
+ {
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), root->bounds(), composite, &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+ EXPECT_EQ(composite, root->draw_properties().target_space_transform);
+ EXPECT_EQ(composite, child->draw_properties().target_space_transform);
+ EXPECT_EQ(identity_matrix, root->render_surface()->draw_transform());
+ }
+
+ // Verify it composes correctly with device scale.
+ float device_scale_factor = 1.5f;
+
+ {
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), root->bounds(), translate, &render_surface_layer_list);
+ inputs.device_scale_factor = device_scale_factor;
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+ gfx::Transform device_scaled_translate = translate;
+ device_scaled_translate.Scale(device_scale_factor, device_scale_factor);
+ EXPECT_EQ(device_scaled_translate,
+ root->draw_properties().target_space_transform);
+ EXPECT_EQ(device_scaled_translate,
+ child->draw_properties().target_space_transform);
+ EXPECT_EQ(identity_matrix, root->render_surface()->draw_transform());
+ }
+
+ // Verify it composes correctly with page scale.
+ float page_scale_factor = 2.f;
+
+ {
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), root->bounds(), translate, &render_surface_layer_list);
+ inputs.page_scale_factor = page_scale_factor;
+ inputs.page_scale_application_layer = root.get();
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+ gfx::Transform page_scaled_translate = translate;
+ page_scaled_translate.Scale(page_scale_factor, page_scale_factor);
+ EXPECT_EQ(translate, root->draw_properties().target_space_transform);
+ EXPECT_EQ(page_scaled_translate,
+ child->draw_properties().target_space_transform);
+ EXPECT_EQ(identity_matrix, root->render_surface()->draw_transform());
+ }
+
+ // Verify that it composes correctly with transforms directly on root layer.
+ root->SetTransform(composite);
+ root->SetSublayerTransform(composite);
+
+ {
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), root->bounds(), composite, &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+ gfx::Transform compositeSquared = composite;
+ compositeSquared.ConcatTransform(composite);
+ gfx::Transform compositeCubed = compositeSquared;
+ compositeCubed.ConcatTransform(composite);
+ EXPECT_EQ(compositeSquared, root->draw_properties().target_space_transform);
+ EXPECT_EQ(compositeCubed, child->draw_properties().target_space_transform);
+ EXPECT_EQ(identity_matrix, root->render_surface()->draw_transform());
+ }
+}
+
+TEST_F(LayerTreeHostCommonTest,
+ RenderSurfaceListForRenderSurfaceWithClippedLayer) {
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<Layer> render_surface1 = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> child =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(parent);
+
+
+ const gfx::Transform identity_matrix;
+ SetLayerPropertiesForTesting(parent.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(render_surface1.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(30.f, 30.f),
+ gfx::Size(10, 10),
+ false);
+
+ parent->AddChild(render_surface1);
+ parent->SetMasksToBounds(true);
+ render_surface1->AddChild(child);
+ render_surface1->SetForceRenderSurface(true);
+
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ parent.get(),
+ parent->bounds(),
+ gfx::Transform(),
+ &render_surface_layer_list);
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // The child layer's content is entirely outside the parent's clip rect, so
+ // the intermediate render surface should not be listed here, even if it was
+ // forced to be created. Render surfaces without children or visible content
+ // are unexpected at draw time (e.g. we might try to create a content texture
+ // of size 0).
+ ASSERT_TRUE(parent->render_surface());
+ ASSERT_FALSE(render_surface1->render_surface());
+ EXPECT_EQ(1U, render_surface_layer_list.size());
+}
+
+TEST_F(LayerTreeHostCommonTest, RenderSurfaceListForTransparentChild) {
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<Layer> render_surface1 = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> child =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(parent);
+
+ const gfx::Transform identity_matrix;
+ SetLayerPropertiesForTesting(render_surface1.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+
+ parent->AddChild(render_surface1);
+ render_surface1->AddChild(child);
+ render_surface1->SetForceRenderSurface(true);
+ render_surface1->SetOpacity(0.f);
+
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ parent.get(), parent->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // Since the layer is transparent, render_surface1->render_surface() should
+ // not have gotten added anywhere. Also, the drawable content rect should not
+ // have been extended by the children.
+ ASSERT_TRUE(parent->render_surface());
+ EXPECT_EQ(0U, parent->render_surface()->layer_list().size());
+ EXPECT_EQ(1U, render_surface_layer_list.size());
+ EXPECT_EQ(parent->id(), render_surface_layer_list.at(0)->id());
+ EXPECT_EQ(gfx::Rect(), parent->drawable_content_rect());
+}
+
+TEST_F(LayerTreeHostCommonTest, ForceRenderSurface) {
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<Layer> render_surface1 = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> child =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ render_surface1->SetForceRenderSurface(true);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(parent);
+
+ const gfx::Transform identity_matrix;
+ SetLayerPropertiesForTesting(parent.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(render_surface1.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+
+ parent->AddChild(render_surface1);
+ render_surface1->AddChild(child);
+
+ // Sanity check before the actual test
+ EXPECT_FALSE(parent->render_surface());
+ EXPECT_FALSE(render_surface1->render_surface());
+
+ {
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ parent.get(), parent->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // The root layer always creates a render surface
+ EXPECT_TRUE(parent->render_surface());
+ EXPECT_TRUE(render_surface1->render_surface());
+ EXPECT_EQ(2U, render_surface_layer_list.size());
+ }
+
+ {
+ RenderSurfaceLayerList render_surface_layer_list;
+ render_surface1->SetForceRenderSurface(false);
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ parent.get(), parent->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+ EXPECT_TRUE(parent->render_surface());
+ EXPECT_FALSE(render_surface1->render_surface());
+ EXPECT_EQ(1U, render_surface_layer_list.size());
+ }
+}
+
+TEST_F(LayerTreeHostCommonTest, ClipRectCullsRenderSurfaces) {
+ // The entire subtree of layers that are outside the clip rect should be
+ // culled away, and should not affect the render_surface_layer_list.
+ //
+ // The test tree is set up as follows:
+ // - all layers except the leaf_nodes are forced to be a new render surface
+ // that have something to draw.
+ // - parent is a large container layer.
+ // - child has masksToBounds=true to cause clipping.
+ // - grand_child is positioned outside of the child's bounds
+ // - great_grand_child is also kept outside child's bounds.
+ //
+ // In this configuration, grand_child and great_grand_child are completely
+ // outside the clip rect, and they should never get scheduled on the list of
+ // render surfaces.
+ //
+
+ const gfx::Transform identity_matrix;
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<Layer> child = Layer::Create();
+ scoped_refptr<Layer> grand_child = Layer::Create();
+ scoped_refptr<Layer> great_grand_child = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> leaf_node1 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> leaf_node2 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ parent->AddChild(child);
+ child->AddChild(grand_child);
+ grand_child->AddChild(great_grand_child);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(parent);
+
+ // leaf_node1 ensures that parent and child are kept on the
+ // render_surface_layer_list, even though grand_child and great_grand_child
+ // should be clipped.
+ child->AddChild(leaf_node1);
+ great_grand_child->AddChild(leaf_node2);
+
+ SetLayerPropertiesForTesting(parent.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(500, 500),
+ false);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(20, 20),
+ false);
+ SetLayerPropertiesForTesting(grand_child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(45.f, 45.f),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(great_grand_child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(leaf_node1.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(500, 500),
+ false);
+ SetLayerPropertiesForTesting(leaf_node2.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(20, 20),
+ false);
+
+ child->SetMasksToBounds(true);
+ child->SetOpacity(0.4f);
+ child->SetForceRenderSurface(true);
+ grand_child->SetOpacity(0.5f);
+ great_grand_child->SetOpacity(0.4f);
+
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ parent.get(), parent->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ ASSERT_EQ(2U, render_surface_layer_list.size());
+ EXPECT_EQ(parent->id(), render_surface_layer_list.at(0)->id());
+ EXPECT_EQ(child->id(), render_surface_layer_list.at(1)->id());
+}
+
+TEST_F(LayerTreeHostCommonTest, ClipRectCullsSurfaceWithoutVisibleContent) {
+ // When a render surface has a clip rect, it is used to clip the content rect
+ // of the surface. When the render surface is animating its transforms, then
+ // the content rect's position in the clip rect is not defined on the main
+ // thread, and its content rect should not be clipped.
+
+ // The test tree is set up as follows:
+ // - parent is a container layer that masksToBounds=true to cause clipping.
+ // - child is a render surface, which has a clip rect set to the bounds of
+ // the parent.
+ // - grand_child is a render surface, and the only visible content in child.
+ // It is positioned outside of the clip rect from parent.
+
+ // In this configuration, grand_child should be outside the clipped
+ // content rect of the child, making grand_child not appear in the
+ // render_surface_layer_list. However, when we place an animation on the
+ // child, this clipping should be avoided and we should keep the grand_child
+ // in the render_surface_layer_list.
+
+ const gfx::Transform identity_matrix;
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<Layer> child = Layer::Create();
+ scoped_refptr<Layer> grand_child = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> leaf_node =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ parent->AddChild(child);
+ child->AddChild(grand_child);
+ grand_child->AddChild(leaf_node);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(parent);
+
+ SetLayerPropertiesForTesting(parent.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(20, 20),
+ false);
+ SetLayerPropertiesForTesting(grand_child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(200.f, 200.f),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(leaf_node.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+
+ parent->SetMasksToBounds(true);
+ child->SetOpacity(0.4f);
+ child->SetForceRenderSurface(true);
+ grand_child->SetOpacity(0.4f);
+ grand_child->SetForceRenderSurface(true);
+
+ {
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ parent.get(), parent->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // Without an animation, we should cull child and grand_child from the
+ // render_surface_layer_list.
+ ASSERT_EQ(1U, render_surface_layer_list.size());
+ EXPECT_EQ(parent->id(), render_surface_layer_list.at(0)->id());
+ }
+
+ // Now put an animating transform on child.
+ AddAnimatedTransformToController(
+ child->layer_animation_controller(), 10.0, 30, 0);
+
+ {
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ parent.get(), parent->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // With an animating transform, we should keep child and grand_child in the
+ // render_surface_layer_list.
+ ASSERT_EQ(3U, render_surface_layer_list.size());
+ EXPECT_EQ(parent->id(), render_surface_layer_list.at(0)->id());
+ EXPECT_EQ(child->id(), render_surface_layer_list.at(1)->id());
+ EXPECT_EQ(grand_child->id(), render_surface_layer_list.at(2)->id());
+ }
+}
+
+TEST_F(LayerTreeHostCommonTest, IsClippedIsSetCorrectly) {
+ // Layer's IsClipped() property is set to true when:
+ // - the layer clips its subtree, e.g. masks to bounds,
+ // - the layer is clipped by an ancestor that contributes to the same
+ // render target,
+ // - a surface is clipped by an ancestor that contributes to the same
+ // render target.
+ //
+ // In particular, for a layer that owns a render surface:
+ // - the render surface inherits any clip from ancestors, and does NOT
+ // pass that clipped status to the layer itself.
+ // - but if the layer itself masks to bounds, it is considered clipped
+ // and propagates the clip to the subtree.
+
+ const gfx::Transform identity_matrix;
+ scoped_refptr<Layer> root = Layer::Create();
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<Layer> child1 = Layer::Create();
+ scoped_refptr<Layer> child2 = Layer::Create();
+ scoped_refptr<Layer> grand_child = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> leaf_node1 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> leaf_node2 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ root->AddChild(parent);
+ parent->AddChild(child1);
+ parent->AddChild(child2);
+ child1->AddChild(grand_child);
+ child2->AddChild(leaf_node2);
+ grand_child->AddChild(leaf_node1);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ child2->SetForceRenderSurface(true);
+
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(parent.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(child1.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(child2.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(grand_child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(leaf_node1.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(leaf_node2.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+
+ // Case 1: nothing is clipped except the root render surface.
+ {
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), parent->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ ASSERT_TRUE(root->render_surface());
+ ASSERT_TRUE(child2->render_surface());
+
+ EXPECT_FALSE(root->is_clipped());
+ EXPECT_TRUE(root->render_surface()->is_clipped());
+ EXPECT_FALSE(parent->is_clipped());
+ EXPECT_FALSE(child1->is_clipped());
+ EXPECT_FALSE(child2->is_clipped());
+ EXPECT_FALSE(child2->render_surface()->is_clipped());
+ EXPECT_FALSE(grand_child->is_clipped());
+ EXPECT_FALSE(leaf_node1->is_clipped());
+ EXPECT_FALSE(leaf_node2->is_clipped());
+ }
+
+ // Case 2: parent masksToBounds, so the parent, child1, and child2's
+ // surface are clipped. But layers that contribute to child2's surface are
+ // not clipped explicitly because child2's surface already accounts for
+ // that clip.
+ {
+ RenderSurfaceLayerList render_surface_layer_list;
+ parent->SetMasksToBounds(true);
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), parent->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ ASSERT_TRUE(root->render_surface());
+ ASSERT_TRUE(child2->render_surface());
+
+ EXPECT_FALSE(root->is_clipped());
+ EXPECT_TRUE(root->render_surface()->is_clipped());
+ EXPECT_TRUE(parent->is_clipped());
+ EXPECT_TRUE(child1->is_clipped());
+ EXPECT_FALSE(child2->is_clipped());
+ EXPECT_TRUE(child2->render_surface()->is_clipped());
+ EXPECT_TRUE(grand_child->is_clipped());
+ EXPECT_TRUE(leaf_node1->is_clipped());
+ EXPECT_FALSE(leaf_node2->is_clipped());
+ }
+
+ // Case 3: child2 masksToBounds. The layer and subtree are clipped, and
+ // child2's render surface is not clipped.
+ {
+ RenderSurfaceLayerList render_surface_layer_list;
+ parent->SetMasksToBounds(false);
+ child2->SetMasksToBounds(true);
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), parent->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ ASSERT_TRUE(root->render_surface());
+ ASSERT_TRUE(child2->render_surface());
+
+ EXPECT_FALSE(root->is_clipped());
+ EXPECT_TRUE(root->render_surface()->is_clipped());
+ EXPECT_FALSE(parent->is_clipped());
+ EXPECT_FALSE(child1->is_clipped());
+ EXPECT_TRUE(child2->is_clipped());
+ EXPECT_FALSE(child2->render_surface()->is_clipped());
+ EXPECT_FALSE(grand_child->is_clipped());
+ EXPECT_FALSE(leaf_node1->is_clipped());
+ EXPECT_TRUE(leaf_node2->is_clipped());
+ }
+}
+
+TEST_F(LayerTreeHostCommonTest, DrawableContentRectForLayers) {
+ // Verify that layers get the appropriate DrawableContentRect when their
+ // parent masksToBounds is true.
+ //
+ // grand_child1 - completely inside the region; DrawableContentRect should
+ // be the layer rect expressed in target space.
+ // grand_child2 - partially clipped but NOT masksToBounds; the clip rect
+ // will be the intersection of layer bounds and the mask region.
+ // grand_child3 - partially clipped and masksToBounds; the
+ // DrawableContentRect will still be the intersection of layer bounds and
+ // the mask region.
+ // grand_child4 - outside parent's clip rect; the DrawableContentRect should
+ // be empty.
+ //
+
+ const gfx::Transform identity_matrix;
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<Layer> child = Layer::Create();
+ scoped_refptr<Layer> grand_child1 = Layer::Create();
+ scoped_refptr<Layer> grand_child2 = Layer::Create();
+ scoped_refptr<Layer> grand_child3 = Layer::Create();
+ scoped_refptr<Layer> grand_child4 = Layer::Create();
+
+ parent->AddChild(child);
+ child->AddChild(grand_child1);
+ child->AddChild(grand_child2);
+ child->AddChild(grand_child3);
+ child->AddChild(grand_child4);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(parent);
+
+ SetLayerPropertiesForTesting(parent.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(500, 500),
+ false);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(20, 20),
+ false);
+ SetLayerPropertiesForTesting(grand_child1.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(5.f, 5.f),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(grand_child2.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(15.f, 15.f),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(grand_child3.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(15.f, 15.f),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(grand_child4.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(45.f, 45.f),
+ gfx::Size(10, 10),
+ false);
+
+ child->SetMasksToBounds(true);
+ grand_child3->SetMasksToBounds(true);
+
+ // Force everyone to be a render surface.
+ child->SetOpacity(0.4f);
+ grand_child1->SetOpacity(0.5f);
+ grand_child2->SetOpacity(0.5f);
+ grand_child3->SetOpacity(0.5f);
+ grand_child4->SetOpacity(0.5f);
+
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ parent.get(), parent->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ EXPECT_RECT_EQ(gfx::Rect(5, 5, 10, 10),
+ grand_child1->drawable_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(15, 15, 5, 5),
+ grand_child3->drawable_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(15, 15, 5, 5),
+ grand_child3->drawable_content_rect());
+ EXPECT_TRUE(grand_child4->drawable_content_rect().IsEmpty());
+}
+
+TEST_F(LayerTreeHostCommonTest, ClipRectIsPropagatedCorrectlyToSurfaces) {
+ // Verify that render surfaces (and their layers) get the appropriate
+ // clip rects when their parent masksToBounds is true.
+ //
+ // Layers that own render surfaces (at least for now) do not inherit any
+ // clipping; instead the surface will enforce the clip for the entire subtree.
+ // They may still have a clip rect of their own layer bounds, however, if
+ // masksToBounds was true.
+ const gfx::Transform identity_matrix;
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<Layer> child = Layer::Create();
+ scoped_refptr<Layer> grand_child1 = Layer::Create();
+ scoped_refptr<Layer> grand_child2 = Layer::Create();
+ scoped_refptr<Layer> grand_child3 = Layer::Create();
+ scoped_refptr<Layer> grand_child4 = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> leaf_node1 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> leaf_node2 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> leaf_node3 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> leaf_node4 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+
+ parent->AddChild(child);
+ child->AddChild(grand_child1);
+ child->AddChild(grand_child2);
+ child->AddChild(grand_child3);
+ child->AddChild(grand_child4);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(parent);
+
+ // the leaf nodes ensure that these grand_children become render surfaces for
+ // this test.
+ grand_child1->AddChild(leaf_node1);
+ grand_child2->AddChild(leaf_node2);
+ grand_child3->AddChild(leaf_node3);
+ grand_child4->AddChild(leaf_node4);
+
+ SetLayerPropertiesForTesting(parent.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(500, 500),
+ false);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(20, 20),
+ false);
+ SetLayerPropertiesForTesting(grand_child1.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(5.f, 5.f),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(grand_child2.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(15.f, 15.f),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(grand_child3.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(15.f, 15.f),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(grand_child4.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(45.f, 45.f),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(leaf_node1.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(leaf_node2.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(leaf_node3.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(leaf_node4.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+
+ child->SetMasksToBounds(true);
+ grand_child3->SetMasksToBounds(true);
+ grand_child4->SetMasksToBounds(true);
+
+ // Force everyone to be a render surface.
+ child->SetOpacity(0.4f);
+ child->SetForceRenderSurface(true);
+ grand_child1->SetOpacity(0.5f);
+ grand_child1->SetForceRenderSurface(true);
+ grand_child2->SetOpacity(0.5f);
+ grand_child2->SetForceRenderSurface(true);
+ grand_child3->SetOpacity(0.5f);
+ grand_child3->SetForceRenderSurface(true);
+ grand_child4->SetOpacity(0.5f);
+ grand_child4->SetForceRenderSurface(true);
+
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ parent.get(), parent->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+ ASSERT_TRUE(grand_child1->render_surface());
+ ASSERT_TRUE(grand_child2->render_surface());
+ ASSERT_TRUE(grand_child3->render_surface());
+ // Because grand_child4 is entirely clipped, it is expected to not have a
+ // render surface.
+ EXPECT_FALSE(grand_child4->render_surface());
+
+ // Surfaces are clipped by their parent, but un-affected by the owning layer's
+ // masksToBounds.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 20, 20),
+ grand_child1->render_surface()->clip_rect());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 20, 20),
+ grand_child2->render_surface()->clip_rect());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 20, 20),
+ grand_child3->render_surface()->clip_rect());
+}
+
+TEST_F(LayerTreeHostCommonTest, AnimationsForRenderSurfaceHierarchy) {
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<Layer> render_surface1 = Layer::Create();
+ scoped_refptr<Layer> render_surface2 = Layer::Create();
+ scoped_refptr<Layer> child_of_root = Layer::Create();
+ scoped_refptr<Layer> child_of_rs1 = Layer::Create();
+ scoped_refptr<Layer> child_of_rs2 = Layer::Create();
+ scoped_refptr<Layer> grand_child_of_root = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> grand_child_of_rs1 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> grand_child_of_rs2 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ parent->AddChild(render_surface1);
+ parent->AddChild(child_of_root);
+ render_surface1->AddChild(child_of_rs1);
+ render_surface1->AddChild(render_surface2);
+ render_surface2->AddChild(child_of_rs2);
+ child_of_root->AddChild(grand_child_of_root);
+ child_of_rs1->AddChild(grand_child_of_rs1);
+ child_of_rs2->AddChild(grand_child_of_rs2);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(parent);
+
+ // Make our render surfaces.
+ render_surface1->SetForceRenderSurface(true);
+ render_surface2->SetForceRenderSurface(true);
+
+ gfx::Transform layer_transform;
+ layer_transform.Translate(1.0, 1.0);
+ gfx::Transform sublayer_transform;
+ sublayer_transform.Scale3d(10.0, 1.0, 1.0);
+
+ SetLayerPropertiesForTesting(parent.get(),
+ layer_transform,
+ sublayer_transform,
+ gfx::PointF(0.25f, 0.f),
+ gfx::PointF(2.5f, 0.f),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(render_surface1.get(),
+ layer_transform,
+ sublayer_transform,
+ gfx::PointF(0.25f, 0.f),
+ gfx::PointF(2.5f, 0.f),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(render_surface2.get(),
+ layer_transform,
+ sublayer_transform,
+ gfx::PointF(0.25f, 0.f),
+ gfx::PointF(2.5f, 0.f),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(child_of_root.get(),
+ layer_transform,
+ sublayer_transform,
+ gfx::PointF(0.25f, 0.f),
+ gfx::PointF(2.5f, 0.f),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(child_of_rs1.get(),
+ layer_transform,
+ sublayer_transform,
+ gfx::PointF(0.25f, 0.f),
+ gfx::PointF(2.5f, 0.f),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(child_of_rs2.get(),
+ layer_transform,
+ sublayer_transform,
+ gfx::PointF(0.25f, 0.f),
+ gfx::PointF(2.5f, 0.f),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(grand_child_of_root.get(),
+ layer_transform,
+ sublayer_transform,
+ gfx::PointF(0.25f, 0.f),
+ gfx::PointF(2.5f, 0.f),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(grand_child_of_rs1.get(),
+ layer_transform,
+ sublayer_transform,
+ gfx::PointF(0.25f, 0.f),
+ gfx::PointF(2.5f, 0.f),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(grand_child_of_rs2.get(),
+ layer_transform,
+ sublayer_transform,
+ gfx::PointF(0.25f, 0.f),
+ gfx::PointF(2.5f, 0.f),
+ gfx::Size(10, 10),
+ false);
+
+ // Put an animated opacity on the render surface.
+ AddOpacityTransitionToController(
+ render_surface1->layer_animation_controller(), 10.0, 1.f, 0.f, false);
+
+ // Also put an animated opacity on a layer without descendants.
+ AddOpacityTransitionToController(
+ grand_child_of_root->layer_animation_controller(), 10.0, 1.f, 0.f, false);
+
+ // Put a transform animation on the render surface.
+ AddAnimatedTransformToController(
+ render_surface2->layer_animation_controller(), 10.0, 30, 0);
+
+ // Also put transform animations on grand_child_of_root, and
+ // grand_child_of_rs2
+ AddAnimatedTransformToController(
+ grand_child_of_root->layer_animation_controller(), 10.0, 30, 0);
+ AddAnimatedTransformToController(
+ grand_child_of_rs2->layer_animation_controller(), 10.0, 30, 0);
+
+ ExecuteCalculateDrawProperties(parent.get());
+
+ // Only layers that are associated with render surfaces should have an actual
+ // RenderSurface() value.
+ ASSERT_TRUE(parent->render_surface());
+ ASSERT_FALSE(child_of_root->render_surface());
+ ASSERT_FALSE(grand_child_of_root->render_surface());
+
+ ASSERT_TRUE(render_surface1->render_surface());
+ ASSERT_FALSE(child_of_rs1->render_surface());
+ ASSERT_FALSE(grand_child_of_rs1->render_surface());
+
+ ASSERT_TRUE(render_surface2->render_surface());
+ ASSERT_FALSE(child_of_rs2->render_surface());
+ ASSERT_FALSE(grand_child_of_rs2->render_surface());
+
+ // Verify all render target accessors
+ EXPECT_EQ(parent, parent->render_target());
+ EXPECT_EQ(parent, child_of_root->render_target());
+ EXPECT_EQ(parent, grand_child_of_root->render_target());
+
+ EXPECT_EQ(render_surface1, render_surface1->render_target());
+ EXPECT_EQ(render_surface1, child_of_rs1->render_target());
+ EXPECT_EQ(render_surface1, grand_child_of_rs1->render_target());
+
+ EXPECT_EQ(render_surface2, render_surface2->render_target());
+ EXPECT_EQ(render_surface2, child_of_rs2->render_target());
+ EXPECT_EQ(render_surface2, grand_child_of_rs2->render_target());
+
+ // Verify draw_opacity_is_animating values
+ EXPECT_FALSE(parent->draw_opacity_is_animating());
+ EXPECT_FALSE(child_of_root->draw_opacity_is_animating());
+ EXPECT_TRUE(grand_child_of_root->draw_opacity_is_animating());
+ EXPECT_FALSE(render_surface1->draw_opacity_is_animating());
+ EXPECT_TRUE(render_surface1->render_surface()->draw_opacity_is_animating());
+ EXPECT_FALSE(child_of_rs1->draw_opacity_is_animating());
+ EXPECT_FALSE(grand_child_of_rs1->draw_opacity_is_animating());
+ EXPECT_FALSE(render_surface2->draw_opacity_is_animating());
+ EXPECT_FALSE(render_surface2->render_surface()->draw_opacity_is_animating());
+ EXPECT_FALSE(child_of_rs2->draw_opacity_is_animating());
+ EXPECT_FALSE(grand_child_of_rs2->draw_opacity_is_animating());
+
+ // Verify draw_transform_is_animating values
+ EXPECT_FALSE(parent->draw_transform_is_animating());
+ EXPECT_FALSE(child_of_root->draw_transform_is_animating());
+ EXPECT_TRUE(grand_child_of_root->draw_transform_is_animating());
+ EXPECT_FALSE(render_surface1->draw_transform_is_animating());
+ EXPECT_FALSE(render_surface1->render_surface()
+ ->target_surface_transforms_are_animating());
+ EXPECT_FALSE(child_of_rs1->draw_transform_is_animating());
+ EXPECT_FALSE(grand_child_of_rs1->draw_transform_is_animating());
+ EXPECT_FALSE(render_surface2->draw_transform_is_animating());
+ EXPECT_TRUE(render_surface2->render_surface()
+ ->target_surface_transforms_are_animating());
+ EXPECT_FALSE(child_of_rs2->draw_transform_is_animating());
+ EXPECT_TRUE(grand_child_of_rs2->draw_transform_is_animating());
+
+ // Verify screen_space_transform_is_animating values
+ EXPECT_FALSE(parent->screen_space_transform_is_animating());
+ EXPECT_FALSE(child_of_root->screen_space_transform_is_animating());
+ EXPECT_TRUE(grand_child_of_root->screen_space_transform_is_animating());
+ EXPECT_FALSE(render_surface1->screen_space_transform_is_animating());
+ EXPECT_FALSE(render_surface1->render_surface()
+ ->screen_space_transforms_are_animating());
+ EXPECT_FALSE(child_of_rs1->screen_space_transform_is_animating());
+ EXPECT_FALSE(grand_child_of_rs1->screen_space_transform_is_animating());
+ EXPECT_TRUE(render_surface2->screen_space_transform_is_animating());
+ EXPECT_TRUE(render_surface2->render_surface()
+ ->screen_space_transforms_are_animating());
+ EXPECT_TRUE(child_of_rs2->screen_space_transform_is_animating());
+ EXPECT_TRUE(grand_child_of_rs2->screen_space_transform_is_animating());
+
+ // Sanity check. If these fail there is probably a bug in the test itself.
+ // It is expected that we correctly set up transforms so that the y-component
+ // of the screen-space transform encodes the "depth" of the layer in the tree.
+ EXPECT_FLOAT_EQ(1.0,
+ parent->screen_space_transform().matrix().getDouble(1, 3));
+ EXPECT_FLOAT_EQ(
+ 2.0, child_of_root->screen_space_transform().matrix().getDouble(1, 3));
+ EXPECT_FLOAT_EQ(
+ 3.0,
+ grand_child_of_root->screen_space_transform().matrix().getDouble(1, 3));
+
+ EXPECT_FLOAT_EQ(
+ 2.0, render_surface1->screen_space_transform().matrix().getDouble(1, 3));
+ EXPECT_FLOAT_EQ(
+ 3.0, child_of_rs1->screen_space_transform().matrix().getDouble(1, 3));
+ EXPECT_FLOAT_EQ(
+ 4.0,
+ grand_child_of_rs1->screen_space_transform().matrix().getDouble(1, 3));
+
+ EXPECT_FLOAT_EQ(
+ 3.0, render_surface2->screen_space_transform().matrix().getDouble(1, 3));
+ EXPECT_FLOAT_EQ(
+ 4.0, child_of_rs2->screen_space_transform().matrix().getDouble(1, 3));
+ EXPECT_FLOAT_EQ(
+ 5.0,
+ grand_child_of_rs2->screen_space_transform().matrix().getDouble(1, 3));
+}
+
+TEST_F(LayerTreeHostCommonTest, VisibleRectForIdentityTransform) {
+ // Test the calculateVisibleRect() function works correctly for identity
+ // transforms.
+
+ gfx::Rect target_surface_rect = gfx::Rect(0, 0, 100, 100);
+ gfx::Transform layer_to_surface_transform;
+
+ // Case 1: Layer is contained within the surface.
+ gfx::Rect layer_content_rect = gfx::Rect(10, 10, 30, 30);
+ gfx::Rect expected = gfx::Rect(10, 10, 30, 30);
+ gfx::Rect actual = LayerTreeHostCommon::CalculateVisibleRect(
+ target_surface_rect, layer_content_rect, layer_to_surface_transform);
+ EXPECT_RECT_EQ(expected, actual);
+
+ // Case 2: Layer is outside the surface rect.
+ layer_content_rect = gfx::Rect(120, 120, 30, 30);
+ actual = LayerTreeHostCommon::CalculateVisibleRect(
+ target_surface_rect, layer_content_rect, layer_to_surface_transform);
+ EXPECT_TRUE(actual.IsEmpty());
+
+ // Case 3: Layer is partially overlapping the surface rect.
+ layer_content_rect = gfx::Rect(80, 80, 30, 30);
+ expected = gfx::Rect(80, 80, 20, 20);
+ actual = LayerTreeHostCommon::CalculateVisibleRect(
+ target_surface_rect, layer_content_rect, layer_to_surface_transform);
+ EXPECT_RECT_EQ(expected, actual);
+}
+
+TEST_F(LayerTreeHostCommonTest, VisibleRectForTranslations) {
+ // Test the calculateVisibleRect() function works correctly for scaling
+ // transforms.
+
+ gfx::Rect target_surface_rect = gfx::Rect(0, 0, 100, 100);
+ gfx::Rect layer_content_rect = gfx::Rect(0, 0, 30, 30);
+ gfx::Transform layer_to_surface_transform;
+
+ // Case 1: Layer is contained within the surface.
+ layer_to_surface_transform.MakeIdentity();
+ layer_to_surface_transform.Translate(10.0, 10.0);
+ gfx::Rect expected = gfx::Rect(0, 0, 30, 30);
+ gfx::Rect actual = LayerTreeHostCommon::CalculateVisibleRect(
+ target_surface_rect, layer_content_rect, layer_to_surface_transform);
+ EXPECT_RECT_EQ(expected, actual);
+
+ // Case 2: Layer is outside the surface rect.
+ layer_to_surface_transform.MakeIdentity();
+ layer_to_surface_transform.Translate(120.0, 120.0);
+ actual = LayerTreeHostCommon::CalculateVisibleRect(
+ target_surface_rect, layer_content_rect, layer_to_surface_transform);
+ EXPECT_TRUE(actual.IsEmpty());
+
+ // Case 3: Layer is partially overlapping the surface rect.
+ layer_to_surface_transform.MakeIdentity();
+ layer_to_surface_transform.Translate(80.0, 80.0);
+ expected = gfx::Rect(0, 0, 20, 20);
+ actual = LayerTreeHostCommon::CalculateVisibleRect(
+ target_surface_rect, layer_content_rect, layer_to_surface_transform);
+ EXPECT_RECT_EQ(expected, actual);
+}
+
+TEST_F(LayerTreeHostCommonTest, VisibleRectFor2DRotations) {
+ // Test the calculateVisibleRect() function works correctly for rotations
+ // about z-axis (i.e. 2D rotations). Remember that calculateVisibleRect()
+ // should return the g in the layer's space.
+
+ gfx::Rect target_surface_rect = gfx::Rect(0, 0, 100, 100);
+ gfx::Rect layer_content_rect = gfx::Rect(0, 0, 30, 30);
+ gfx::Transform layer_to_surface_transform;
+
+ // Case 1: Layer is contained within the surface.
+ layer_to_surface_transform.MakeIdentity();
+ layer_to_surface_transform.Translate(50.0, 50.0);
+ layer_to_surface_transform.Rotate(45.0);
+ gfx::Rect expected = gfx::Rect(0, 0, 30, 30);
+ gfx::Rect actual = LayerTreeHostCommon::CalculateVisibleRect(
+ target_surface_rect, layer_content_rect, layer_to_surface_transform);
+ EXPECT_RECT_EQ(expected, actual);
+
+ // Case 2: Layer is outside the surface rect.
+ layer_to_surface_transform.MakeIdentity();
+ layer_to_surface_transform.Translate(-50.0, 0.0);
+ layer_to_surface_transform.Rotate(45.0);
+ actual = LayerTreeHostCommon::CalculateVisibleRect(
+ target_surface_rect, layer_content_rect, layer_to_surface_transform);
+ EXPECT_TRUE(actual.IsEmpty());
+
+ // Case 3: The layer is rotated about its top-left corner. In surface space,
+ // the layer is oriented diagonally, with the left half outside of the render
+ // surface. In this case, the g should still be the entire layer
+ // (remember the g is computed in layer space); both the top-left
+ // and bottom-right corners of the layer are still visible.
+ layer_to_surface_transform.MakeIdentity();
+ layer_to_surface_transform.Rotate(45.0);
+ expected = gfx::Rect(0, 0, 30, 30);
+ actual = LayerTreeHostCommon::CalculateVisibleRect(
+ target_surface_rect, layer_content_rect, layer_to_surface_transform);
+ EXPECT_RECT_EQ(expected, actual);
+
+ // Case 4: The layer is rotated about its top-left corner, and translated
+ // upwards. In surface space, the layer is oriented diagonally, with only the
+ // top corner of the surface overlapping the layer. In layer space, the render
+ // surface overlaps the right side of the layer. The g should be
+ // the layer's right half.
+ layer_to_surface_transform.MakeIdentity();
+ layer_to_surface_transform.Translate(0.0, -sqrt(2.0) * 15.0);
+ layer_to_surface_transform.Rotate(45.0);
+ expected = gfx::Rect(15, 0, 15, 30); // Right half of layer bounds.
+ actual = LayerTreeHostCommon::CalculateVisibleRect(
+ target_surface_rect, layer_content_rect, layer_to_surface_transform);
+ EXPECT_RECT_EQ(expected, actual);
+}
+
+TEST_F(LayerTreeHostCommonTest, VisibleRectFor3dOrthographicTransform) {
+ // Test that the calculateVisibleRect() function works correctly for 3d
+ // transforms.
+
+ gfx::Rect target_surface_rect = gfx::Rect(0, 0, 100, 100);
+ gfx::Rect layer_content_rect = gfx::Rect(0, 0, 100, 100);
+ gfx::Transform layer_to_surface_transform;
+
+ // Case 1: Orthographic projection of a layer rotated about y-axis by 45
+ // degrees, should be fully contained in the render surface.
+ layer_to_surface_transform.MakeIdentity();
+ layer_to_surface_transform.RotateAboutYAxis(45.0);
+ gfx::Rect expected = gfx::Rect(0, 0, 100, 100);
+ gfx::Rect actual = LayerTreeHostCommon::CalculateVisibleRect(
+ target_surface_rect, layer_content_rect, layer_to_surface_transform);
+ EXPECT_RECT_EQ(expected, actual);
+
+ // Case 2: Orthographic projection of a layer rotated about y-axis by 45
+ // degrees, but shifted to the side so only the right-half the layer would be
+ // visible on the surface.
+ // 100 is the un-rotated layer width; divided by sqrt(2) is the rotated width.
+ double half_width_of_rotated_layer = (100.0 / sqrt(2.0)) * 0.5;
+ layer_to_surface_transform.MakeIdentity();
+ layer_to_surface_transform.Translate(-half_width_of_rotated_layer, 0.0);
+ layer_to_surface_transform.RotateAboutYAxis(45.0); // Rotates about the left
+ // edge of the layer.
+ expected = gfx::Rect(50, 0, 50, 100); // Tight half of the layer.
+ actual = LayerTreeHostCommon::CalculateVisibleRect(
+ target_surface_rect, layer_content_rect, layer_to_surface_transform);
+ EXPECT_RECT_EQ(expected, actual);
+}
+
+TEST_F(LayerTreeHostCommonTest, VisibleRectFor3dPerspectiveTransform) {
+ // Test the calculateVisibleRect() function works correctly when the layer has
+ // a perspective projection onto the target surface.
+
+ gfx::Rect target_surface_rect = gfx::Rect(0, 0, 100, 100);
+ gfx::Rect layer_content_rect = gfx::Rect(-50, -50, 200, 200);
+ gfx::Transform layer_to_surface_transform;
+
+ // Case 1: Even though the layer is twice as large as the surface, due to
+ // perspective foreshortening, the layer will fit fully in the surface when
+ // its translated more than the perspective amount.
+ layer_to_surface_transform.MakeIdentity();
+
+ // The following sequence of transforms applies the perspective about the
+ // center of the surface.
+ layer_to_surface_transform.Translate(50.0, 50.0);
+ layer_to_surface_transform.ApplyPerspectiveDepth(9.0);
+ layer_to_surface_transform.Translate(-50.0, -50.0);
+
+ // This translate places the layer in front of the surface's projection plane.
+ layer_to_surface_transform.Translate3d(0.0, 0.0, -27.0);
+
+ gfx::Rect expected = gfx::Rect(-50, -50, 200, 200);
+ gfx::Rect actual = LayerTreeHostCommon::CalculateVisibleRect(
+ target_surface_rect, layer_content_rect, layer_to_surface_transform);
+ EXPECT_RECT_EQ(expected, actual);
+
+ // Case 2: same projection as before, except that the layer is also translated
+ // to the side, so that only the right half of the layer should be visible.
+ //
+ // Explanation of expected result: The perspective ratio is (z distance
+ // between layer and camera origin) / (z distance between projection plane and
+ // camera origin) == ((-27 - 9) / 9) Then, by similar triangles, if we want to
+ // move a layer by translating -50 units in projected surface units (so that
+ // only half of it is visible), then we would need to translate by (-36 / 9) *
+ // -50 == -200 in the layer's units.
+ layer_to_surface_transform.Translate3d(-200.0, 0.0, 0.0);
+ expected = gfx::Rect(gfx::Point(50, -50),
+ gfx::Size(100, 200)); // The right half of the layer's
+ // bounding rect.
+ actual = LayerTreeHostCommon::CalculateVisibleRect(
+ target_surface_rect, layer_content_rect, layer_to_surface_transform);
+ EXPECT_RECT_EQ(expected, actual);
+}
+
+TEST_F(LayerTreeHostCommonTest,
+ VisibleRectFor3dOrthographicIsNotClippedBehindSurface) {
+ // There is currently no explicit concept of an orthographic projection plane
+ // in our code (nor in the CSS spec to my knowledge). Therefore, layers that
+ // are technically behind the surface in an orthographic world should not be
+ // clipped when they are flattened to the surface.
+
+ gfx::Rect target_surface_rect = gfx::Rect(0, 0, 100, 100);
+ gfx::Rect layer_content_rect = gfx::Rect(0, 0, 100, 100);
+ gfx::Transform layer_to_surface_transform;
+
+ // This sequence of transforms effectively rotates the layer about the y-axis
+ // at the center of the layer.
+ layer_to_surface_transform.MakeIdentity();
+ layer_to_surface_transform.Translate(50.0, 0.0);
+ layer_to_surface_transform.RotateAboutYAxis(45.0);
+ layer_to_surface_transform.Translate(-50.0, 0.0);
+
+ gfx::Rect expected = gfx::Rect(0, 0, 100, 100);
+ gfx::Rect actual = LayerTreeHostCommon::CalculateVisibleRect(
+ target_surface_rect, layer_content_rect, layer_to_surface_transform);
+ EXPECT_RECT_EQ(expected, actual);
+}
+
+TEST_F(LayerTreeHostCommonTest, VisibleRectFor3dPerspectiveWhenClippedByW) {
+ // Test the calculateVisibleRect() function works correctly when projecting a
+ // surface onto a layer, but the layer is partially behind the camera (not
+ // just behind the projection plane). In this case, the cartesian coordinates
+ // may seem to be valid, but actually they are not. The visible rect needs to
+ // be properly clipped by the w = 0 plane in homogeneous coordinates before
+ // converting to cartesian coordinates.
+
+ gfx::Rect target_surface_rect = gfx::Rect(-50, -50, 100, 100);
+ gfx::Rect layer_content_rect = gfx::Rect(-10, -1, 20, 2);
+ gfx::Transform layer_to_surface_transform;
+
+ // The layer is positioned so that the right half of the layer should be in
+ // front of the camera, while the other half is behind the surface's
+ // projection plane. The following sequence of transforms applies the
+ // perspective and rotation about the center of the layer.
+ layer_to_surface_transform.MakeIdentity();
+ layer_to_surface_transform.ApplyPerspectiveDepth(1.0);
+ layer_to_surface_transform.Translate3d(-2.0, 0.0, 1.0);
+ layer_to_surface_transform.RotateAboutYAxis(45.0);
+
+ // Sanity check that this transform does indeed cause w < 0 when applying the
+ // transform, otherwise this code is not testing the intended scenario.
+ bool clipped;
+ MathUtil::MapQuad(layer_to_surface_transform,
+ gfx::QuadF(gfx::RectF(layer_content_rect)),
+ &clipped);
+ ASSERT_TRUE(clipped);
+
+ int expected_x_position = 0;
+ int expected_width = 10;
+ gfx::Rect actual = LayerTreeHostCommon::CalculateVisibleRect(
+ target_surface_rect, layer_content_rect, layer_to_surface_transform);
+ EXPECT_EQ(expected_x_position, actual.x());
+ EXPECT_EQ(expected_width, actual.width());
+}
+
+TEST_F(LayerTreeHostCommonTest, VisibleRectForPerspectiveUnprojection) {
+ // To determine visible rect in layer space, there needs to be an
+ // un-projection from surface space to layer space. When the original
+ // transform was a perspective projection that was clipped, it returns a rect
+ // that encloses the clipped bounds. Un-projecting this new rect may require
+ // clipping again.
+
+ // This sequence of transforms causes one corner of the layer to protrude
+ // across the w = 0 plane, and should be clipped.
+ gfx::Rect target_surface_rect = gfx::Rect(-50, -50, 100, 100);
+ gfx::Rect layer_content_rect = gfx::Rect(-10, -10, 20, 20);
+ gfx::Transform layer_to_surface_transform;
+ layer_to_surface_transform.MakeIdentity();
+ layer_to_surface_transform.ApplyPerspectiveDepth(1.0);
+ layer_to_surface_transform.Translate3d(0.0, 0.0, -5.0);
+ layer_to_surface_transform.RotateAboutYAxis(45.0);
+ layer_to_surface_transform.RotateAboutXAxis(80.0);
+
+ // Sanity check that un-projection does indeed cause w < 0, otherwise this
+ // code is not testing the intended scenario.
+ bool clipped;
+ gfx::RectF clipped_rect =
+ MathUtil::MapClippedRect(layer_to_surface_transform, layer_content_rect);
+ MathUtil::ProjectQuad(
+ Inverse(layer_to_surface_transform), gfx::QuadF(clipped_rect), &clipped);
+ ASSERT_TRUE(clipped);
+
+ // Only the corner of the layer is not visible on the surface because of being
+ // clipped. But, the net result of rounding visible region to an axis-aligned
+ // rect is that the entire layer should still be considered visible.
+ gfx::Rect expected = gfx::Rect(-10, -10, 20, 20);
+ gfx::Rect actual = LayerTreeHostCommon::CalculateVisibleRect(
+ target_surface_rect, layer_content_rect, layer_to_surface_transform);
+ EXPECT_RECT_EQ(expected, actual);
+}
+
+TEST_F(LayerTreeHostCommonTest, DrawableAndVisibleContentRectsForSimpleLayers) {
+ scoped_refptr<Layer> root = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> child1 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> child2 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> child3 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ root->AddChild(child1);
+ root->AddChild(child2);
+ root->AddChild(child3);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ gfx::Transform identity_matrix;
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(child1.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(50, 50),
+ false);
+ SetLayerPropertiesForTesting(child2.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(75.f, 75.f),
+ gfx::Size(50, 50),
+ false);
+ SetLayerPropertiesForTesting(child3.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(125.f, 125.f),
+ gfx::Size(50, 50),
+ false);
+
+ ExecuteCalculateDrawProperties(root.get());
+
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 100),
+ root->render_surface()->DrawableContentRect());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 100), root->drawable_content_rect());
+
+ // Layers that do not draw content should have empty visible_content_rects.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 0, 0), root->visible_content_rect());
+
+ // layer visible_content_rects are clipped by their target surface.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 50, 50), child1->visible_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 25, 25), child2->visible_content_rect());
+ EXPECT_TRUE(child3->visible_content_rect().IsEmpty());
+
+ // layer drawable_content_rects are not clipped.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 50, 50), child1->drawable_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(75, 75, 50, 50), child2->drawable_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(125, 125, 50, 50), child3->drawable_content_rect());
+}
+
+TEST_F(LayerTreeHostCommonTest,
+ DrawableAndVisibleContentRectsForLayersClippedByLayer) {
+ scoped_refptr<Layer> root = Layer::Create();
+ scoped_refptr<Layer> child = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> grand_child1 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> grand_child2 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> grand_child3 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ root->AddChild(child);
+ child->AddChild(grand_child1);
+ child->AddChild(grand_child2);
+ child->AddChild(grand_child3);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ gfx::Transform identity_matrix;
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(grand_child1.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(5.f, 5.f),
+ gfx::Size(50, 50),
+ false);
+ SetLayerPropertiesForTesting(grand_child2.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(75.f, 75.f),
+ gfx::Size(50, 50),
+ false);
+ SetLayerPropertiesForTesting(grand_child3.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(125.f, 125.f),
+ gfx::Size(50, 50),
+ false);
+
+ child->SetMasksToBounds(true);
+ ExecuteCalculateDrawProperties(root.get());
+
+ ASSERT_FALSE(child->render_surface());
+
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 100),
+ root->render_surface()->DrawableContentRect());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 100), root->drawable_content_rect());
+
+ // Layers that do not draw content should have empty visible content rects.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 0, 0), root->visible_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 0, 0), child->visible_content_rect());
+
+ // All grandchild visible content rects should be clipped by child.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 50, 50), grand_child1->visible_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 25, 25), grand_child2->visible_content_rect());
+ EXPECT_TRUE(grand_child3->visible_content_rect().IsEmpty());
+
+ // All grandchild DrawableContentRects should also be clipped by child.
+ EXPECT_RECT_EQ(gfx::Rect(5, 5, 50, 50),
+ grand_child1->drawable_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(75, 75, 25, 25),
+ grand_child2->drawable_content_rect());
+ EXPECT_TRUE(grand_child3->drawable_content_rect().IsEmpty());
+}
+
+TEST_F(LayerTreeHostCommonTest,
+ DrawableAndVisibleContentRectsForLayersInUnclippedRenderSurface) {
+ scoped_refptr<Layer> root = Layer::Create();
+ scoped_refptr<Layer> render_surface1 = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> child1 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> child2 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> child3 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ root->AddChild(render_surface1);
+ render_surface1->AddChild(child1);
+ render_surface1->AddChild(child2);
+ render_surface1->AddChild(child3);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ gfx::Transform identity_matrix;
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(render_surface1.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(3, 4),
+ false);
+ SetLayerPropertiesForTesting(child1.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(5.f, 5.f),
+ gfx::Size(50, 50),
+ false);
+ SetLayerPropertiesForTesting(child2.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(75.f, 75.f),
+ gfx::Size(50, 50),
+ false);
+ SetLayerPropertiesForTesting(child3.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(125.f, 125.f),
+ gfx::Size(50, 50),
+ false);
+
+ render_surface1->SetForceRenderSurface(true);
+ ExecuteCalculateDrawProperties(root.get());
+
+ ASSERT_TRUE(render_surface1->render_surface());
+
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 100),
+ root->render_surface()->DrawableContentRect());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 100), root->drawable_content_rect());
+
+ // Layers that do not draw content should have empty visible content rects.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 0, 0), root->visible_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 0, 0),
+ render_surface1->visible_content_rect());
+
+ // An unclipped surface grows its DrawableContentRect to include all drawable
+ // regions of the subtree.
+ EXPECT_RECT_EQ(gfx::Rect(5, 5, 170, 170),
+ render_surface1->render_surface()->DrawableContentRect());
+
+ // All layers that draw content into the unclipped surface are also unclipped.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 50, 50), child1->visible_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 50, 50), child2->visible_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 50, 50), child3->visible_content_rect());
+
+ EXPECT_RECT_EQ(gfx::Rect(5, 5, 50, 50), child1->drawable_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(75, 75, 50, 50), child2->drawable_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(125, 125, 50, 50), child3->drawable_content_rect());
+}
+
+TEST_F(LayerTreeHostCommonTest,
+ DrawableAndVisibleContentRectsForLayersWithUninvertibleTransform) {
+ scoped_refptr<Layer> root = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> child =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ root->AddChild(child);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ gfx::Transform identity_matrix;
+ gfx::Transform uninvertible_matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(child.get(),
+ uninvertible_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(5.f, 5.f),
+ gfx::Size(50, 50),
+ false);
+
+ ExecuteCalculateDrawProperties(root.get());
+
+ EXPECT_TRUE(child->visible_content_rect().IsEmpty());
+ EXPECT_TRUE(child->drawable_content_rect().IsEmpty());
+}
+
+TEST_F(LayerTreeHostCommonTest,
+ DrawableAndVisibleContentRectsForLayersInClippedRenderSurface) {
+ scoped_refptr<Layer> root = Layer::Create();
+ scoped_refptr<Layer> render_surface1 = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> child1 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> child2 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> child3 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ root->AddChild(render_surface1);
+ render_surface1->AddChild(child1);
+ render_surface1->AddChild(child2);
+ render_surface1->AddChild(child3);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ gfx::Transform identity_matrix;
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(render_surface1.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(3, 4),
+ false);
+ SetLayerPropertiesForTesting(child1.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(5.f, 5.f),
+ gfx::Size(50, 50),
+ false);
+ SetLayerPropertiesForTesting(child2.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(75.f, 75.f),
+ gfx::Size(50, 50),
+ false);
+ SetLayerPropertiesForTesting(child3.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(125.f, 125.f),
+ gfx::Size(50, 50),
+ false);
+
+ root->SetMasksToBounds(true);
+ render_surface1->SetForceRenderSurface(true);
+ ExecuteCalculateDrawProperties(root.get());
+
+ ASSERT_TRUE(render_surface1->render_surface());
+
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 100),
+ root->render_surface()->DrawableContentRect());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 100), root->drawable_content_rect());
+
+ // Layers that do not draw content should have empty visible content rects.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 0, 0), root->visible_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 0, 0),
+ render_surface1->visible_content_rect());
+
+ // A clipped surface grows its DrawableContentRect to include all drawable
+ // regions of the subtree, but also gets clamped by the ancestor's clip.
+ EXPECT_RECT_EQ(gfx::Rect(5, 5, 95, 95),
+ render_surface1->render_surface()->DrawableContentRect());
+
+ // All layers that draw content into the surface have their visible content
+ // rect clipped by the surface clip rect.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 50, 50), child1->visible_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 25, 25), child2->visible_content_rect());
+ EXPECT_TRUE(child3->visible_content_rect().IsEmpty());
+
+ // But the DrawableContentRects are unclipped.
+ EXPECT_RECT_EQ(gfx::Rect(5, 5, 50, 50), child1->drawable_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(75, 75, 50, 50), child2->drawable_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(125, 125, 50, 50), child3->drawable_content_rect());
+}
+
+TEST_F(LayerTreeHostCommonTest,
+ DrawableAndVisibleContentRectsForSurfaceHierarchy) {
+ // Check that clipping does not propagate down surfaces.
+ scoped_refptr<Layer> root = Layer::Create();
+ scoped_refptr<Layer> render_surface1 = Layer::Create();
+ scoped_refptr<Layer> render_surface2 = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> child1 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> child2 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> child3 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ root->AddChild(render_surface1);
+ render_surface1->AddChild(render_surface2);
+ render_surface2->AddChild(child1);
+ render_surface2->AddChild(child2);
+ render_surface2->AddChild(child3);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ gfx::Transform identity_matrix;
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(render_surface1.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(3, 4),
+ false);
+ SetLayerPropertiesForTesting(render_surface2.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(7, 13),
+ false);
+ SetLayerPropertiesForTesting(child1.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(5.f, 5.f),
+ gfx::Size(50, 50),
+ false);
+ SetLayerPropertiesForTesting(child2.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(75.f, 75.f),
+ gfx::Size(50, 50),
+ false);
+ SetLayerPropertiesForTesting(child3.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(125.f, 125.f),
+ gfx::Size(50, 50),
+ false);
+
+ root->SetMasksToBounds(true);
+ render_surface1->SetForceRenderSurface(true);
+ render_surface2->SetForceRenderSurface(true);
+ ExecuteCalculateDrawProperties(root.get());
+
+ ASSERT_TRUE(render_surface1->render_surface());
+ ASSERT_TRUE(render_surface2->render_surface());
+
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 100),
+ root->render_surface()->DrawableContentRect());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 100), root->drawable_content_rect());
+
+ // Layers that do not draw content should have empty visible content rects.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 0, 0), root->visible_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 0, 0),
+ render_surface1->visible_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 0, 0),
+ render_surface2->visible_content_rect());
+
+ // A clipped surface grows its DrawableContentRect to include all drawable
+ // regions of the subtree, but also gets clamped by the ancestor's clip.
+ EXPECT_RECT_EQ(gfx::Rect(5, 5, 95, 95),
+ render_surface1->render_surface()->DrawableContentRect());
+
+ // render_surface1 lives in the "unclipped universe" of render_surface1, and
+ // is only implicitly clipped by render_surface1's content rect. So,
+ // render_surface2 grows to enclose all drawable content of its subtree.
+ EXPECT_RECT_EQ(gfx::Rect(5, 5, 170, 170),
+ render_surface2->render_surface()->DrawableContentRect());
+
+ // All layers that draw content into render_surface2 think they are unclipped.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 50, 50), child1->visible_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 50, 50), child2->visible_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 50, 50), child3->visible_content_rect());
+
+ // DrawableContentRects are also unclipped.
+ EXPECT_RECT_EQ(gfx::Rect(5, 5, 50, 50), child1->drawable_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(75, 75, 50, 50), child2->drawable_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(125, 125, 50, 50), child3->drawable_content_rect());
+}
+
+TEST_F(LayerTreeHostCommonTest,
+ DrawableAndVisibleContentRectsWithTransformOnUnclippedSurface) {
+ // Layers that have non-axis aligned bounds (due to transforms) have an
+ // expanded, axis-aligned DrawableContentRect and visible content rect.
+
+ scoped_refptr<Layer> root = Layer::Create();
+ scoped_refptr<Layer> render_surface1 = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> child1 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ root->AddChild(render_surface1);
+ render_surface1->AddChild(child1);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ gfx::Transform identity_matrix;
+ gfx::Transform child_rotation;
+ child_rotation.Rotate(45.0);
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(render_surface1.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(3, 4),
+ false);
+ SetLayerPropertiesForTesting(child1.get(),
+ child_rotation,
+ identity_matrix,
+ gfx::PointF(0.5f, 0.5f),
+ gfx::PointF(25.f, 25.f),
+ gfx::Size(50, 50),
+ false);
+
+ render_surface1->SetForceRenderSurface(true);
+ ExecuteCalculateDrawProperties(root.get());
+
+ ASSERT_TRUE(render_surface1->render_surface());
+
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 100),
+ root->render_surface()->DrawableContentRect());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 100), root->drawable_content_rect());
+
+ // Layers that do not draw content should have empty visible content rects.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 0, 0), root->visible_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 0, 0),
+ render_surface1->visible_content_rect());
+
+ // The unclipped surface grows its DrawableContentRect to include all drawable
+ // regions of the subtree.
+ int diagonal_radius = ceil(sqrt(2.0) * 25.0);
+ gfx::Rect expected_surface_drawable_content =
+ gfx::Rect(50.0 - diagonal_radius,
+ 50.0 - diagonal_radius,
+ diagonal_radius * 2.0,
+ diagonal_radius * 2.0);
+ EXPECT_RECT_EQ(expected_surface_drawable_content,
+ render_surface1->render_surface()->DrawableContentRect());
+
+ // All layers that draw content into the unclipped surface are also unclipped.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 50, 50), child1->visible_content_rect());
+ EXPECT_RECT_EQ(expected_surface_drawable_content,
+ child1->drawable_content_rect());
+}
+
+TEST_F(LayerTreeHostCommonTest,
+ DrawableAndVisibleContentRectsWithTransformOnClippedSurface) {
+ // Layers that have non-axis aligned bounds (due to transforms) have an
+ // expanded, axis-aligned DrawableContentRect and visible content rect.
+
+ scoped_refptr<Layer> root = Layer::Create();
+ scoped_refptr<Layer> render_surface1 = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> child1 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ root->AddChild(render_surface1);
+ render_surface1->AddChild(child1);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ gfx::Transform identity_matrix;
+ gfx::Transform child_rotation;
+ child_rotation.Rotate(45.0);
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(50, 50),
+ false);
+ SetLayerPropertiesForTesting(render_surface1.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(3, 4),
+ false);
+ SetLayerPropertiesForTesting(child1.get(),
+ child_rotation,
+ identity_matrix,
+ gfx::PointF(0.5f, 0.5f),
+ gfx::PointF(25.f, 25.f),
+ gfx::Size(50, 50),
+ false);
+
+ root->SetMasksToBounds(true);
+ render_surface1->SetForceRenderSurface(true);
+ ExecuteCalculateDrawProperties(root.get());
+
+ ASSERT_TRUE(render_surface1->render_surface());
+
+ // The clipped surface clamps the DrawableContentRect that encloses the
+ // rotated layer.
+ int diagonal_radius = ceil(sqrt(2.0) * 25.0);
+ gfx::Rect unclipped_surface_content = gfx::Rect(50.0 - diagonal_radius,
+ 50.0 - diagonal_radius,
+ diagonal_radius * 2.0,
+ diagonal_radius * 2.0);
+ gfx::Rect expected_surface_drawable_content =
+ gfx::IntersectRects(unclipped_surface_content, gfx::Rect(0, 0, 50, 50));
+ EXPECT_RECT_EQ(expected_surface_drawable_content,
+ render_surface1->render_surface()->DrawableContentRect());
+
+ // On the clipped surface, only a quarter of the child1 is visible, but when
+ // rotating it back to child1's content space, the actual enclosing rect ends
+ // up covering the full left half of child1.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 26, 50), child1->visible_content_rect());
+
+ // The child's DrawableContentRect is unclipped.
+ EXPECT_RECT_EQ(unclipped_surface_content, child1->drawable_content_rect());
+}
+
+TEST_F(LayerTreeHostCommonTest, DrawableAndVisibleContentRectsInHighDPI) {
+ MockContentLayerClient client;
+
+ scoped_refptr<Layer> root = Layer::Create();
+ scoped_refptr<ContentLayer> render_surface1 =
+ CreateDrawableContentLayer(&client);
+ scoped_refptr<ContentLayer> render_surface2 =
+ CreateDrawableContentLayer(&client);
+ scoped_refptr<ContentLayer> child1 = CreateDrawableContentLayer(&client);
+ scoped_refptr<ContentLayer> child2 = CreateDrawableContentLayer(&client);
+ scoped_refptr<ContentLayer> child3 = CreateDrawableContentLayer(&client);
+ root->AddChild(render_surface1);
+ render_surface1->AddChild(render_surface2);
+ render_surface2->AddChild(child1);
+ render_surface2->AddChild(child2);
+ render_surface2->AddChild(child3);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ gfx::Transform identity_matrix;
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(render_surface1.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(5.f, 5.f),
+ gfx::Size(3, 4),
+ false);
+ SetLayerPropertiesForTesting(render_surface2.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(5.f, 5.f),
+ gfx::Size(7, 13),
+ false);
+ SetLayerPropertiesForTesting(child1.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(5.f, 5.f),
+ gfx::Size(50, 50),
+ false);
+ SetLayerPropertiesForTesting(child2.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(75.f, 75.f),
+ gfx::Size(50, 50),
+ false);
+ SetLayerPropertiesForTesting(child3.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(125.f, 125.f),
+ gfx::Size(50, 50),
+ false);
+
+ float device_scale_factor = 2.f;
+
+ root->SetMasksToBounds(true);
+ render_surface1->SetForceRenderSurface(true);
+ render_surface2->SetForceRenderSurface(true);
+ ExecuteCalculateDrawProperties(root.get(), device_scale_factor);
+
+ ASSERT_TRUE(render_surface1->render_surface());
+ ASSERT_TRUE(render_surface2->render_surface());
+
+ // drawable_content_rects for all layers and surfaces are scaled by
+ // device_scale_factor.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 200, 200),
+ root->render_surface()->DrawableContentRect());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 200, 200), root->drawable_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(10, 10, 190, 190),
+ render_surface1->render_surface()->DrawableContentRect());
+
+ // render_surface2 lives in the "unclipped universe" of render_surface1, and
+ // is only implicitly clipped by render_surface1.
+ EXPECT_RECT_EQ(gfx::Rect(10, 10, 350, 350),
+ render_surface2->render_surface()->DrawableContentRect());
+
+ EXPECT_RECT_EQ(gfx::Rect(10, 10, 100, 100), child1->drawable_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(150, 150, 100, 100),
+ child2->drawable_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(250, 250, 100, 100),
+ child3->drawable_content_rect());
+
+ // The root layer does not actually draw content of its own.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 0, 0), root->visible_content_rect());
+
+ // All layer visible content rects are expressed in content space of each
+ // layer, so they are also scaled by the device_scale_factor.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 6, 8),
+ render_surface1->visible_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 14, 26),
+ render_surface2->visible_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 100), child1->visible_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 100), child2->visible_content_rect());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 100), child3->visible_content_rect());
+}
+
+TEST_F(LayerTreeHostCommonTest, BackFaceCullingWithoutPreserves3d) {
+ // Verify the behavior of back-face culling when there are no preserve-3d
+ // layers. Note that 3d transforms still apply in this case, but they are
+ // "flattened" to each parent layer according to current W3C spec.
+
+ const gfx::Transform identity_matrix;
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> front_facing_child =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> back_facing_child =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> front_facing_surface =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> back_facing_surface =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent>
+ front_facing_child_of_front_facing_surface =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent>
+ back_facing_child_of_front_facing_surface =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent>
+ front_facing_child_of_back_facing_surface =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent>
+ back_facing_child_of_back_facing_surface =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+
+ parent->AddChild(front_facing_child);
+ parent->AddChild(back_facing_child);
+ parent->AddChild(front_facing_surface);
+ parent->AddChild(back_facing_surface);
+ front_facing_surface->AddChild(front_facing_child_of_front_facing_surface);
+ front_facing_surface->AddChild(back_facing_child_of_front_facing_surface);
+ back_facing_surface->AddChild(front_facing_child_of_back_facing_surface);
+ back_facing_surface->AddChild(back_facing_child_of_back_facing_surface);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(parent);
+
+ // Nothing is double-sided
+ front_facing_child->SetDoubleSided(false);
+ back_facing_child->SetDoubleSided(false);
+ front_facing_surface->SetDoubleSided(false);
+ back_facing_surface->SetDoubleSided(false);
+ front_facing_child_of_front_facing_surface->SetDoubleSided(false);
+ back_facing_child_of_front_facing_surface->SetDoubleSided(false);
+ front_facing_child_of_back_facing_surface->SetDoubleSided(false);
+ back_facing_child_of_back_facing_surface->SetDoubleSided(false);
+
+ gfx::Transform backface_matrix;
+ backface_matrix.Translate(50.0, 50.0);
+ backface_matrix.RotateAboutYAxis(180.0);
+ backface_matrix.Translate(-50.0, -50.0);
+
+ // Having a descendant and opacity will force these to have render surfaces.
+ front_facing_surface->SetOpacity(0.5f);
+ back_facing_surface->SetOpacity(0.5f);
+
+ // Nothing preserves 3d. According to current W3C CSS gfx::Transforms spec,
+ // these layers should blindly use their own local transforms to determine
+ // back-face culling.
+ SetLayerPropertiesForTesting(parent.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(front_facing_child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(back_facing_child.get(),
+ backface_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(front_facing_surface.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(back_facing_surface.get(),
+ backface_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(front_facing_child_of_front_facing_surface.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(back_facing_child_of_front_facing_surface.get(),
+ backface_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(front_facing_child_of_back_facing_surface.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(back_facing_child_of_back_facing_surface.get(),
+ backface_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ parent.get(), parent->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // Verify which render surfaces were created.
+ EXPECT_FALSE(front_facing_child->render_surface());
+ EXPECT_FALSE(back_facing_child->render_surface());
+ EXPECT_TRUE(front_facing_surface->render_surface());
+ EXPECT_TRUE(back_facing_surface->render_surface());
+ EXPECT_FALSE(front_facing_child_of_front_facing_surface->render_surface());
+ EXPECT_FALSE(back_facing_child_of_front_facing_surface->render_surface());
+ EXPECT_FALSE(front_facing_child_of_back_facing_surface->render_surface());
+ EXPECT_FALSE(back_facing_child_of_back_facing_surface->render_surface());
+
+ // Verify the render_surface_layer_list.
+ ASSERT_EQ(3u, render_surface_layer_list.size());
+ EXPECT_EQ(parent->id(), render_surface_layer_list.at(0)->id());
+ EXPECT_EQ(front_facing_surface->id(), render_surface_layer_list.at(1)->id());
+ // Even though the back facing surface LAYER gets culled, the other
+ // descendants should still be added, so the SURFACE should not be culled.
+ EXPECT_EQ(back_facing_surface->id(), render_surface_layer_list.at(2)->id());
+
+ // Verify root surface's layer list.
+ ASSERT_EQ(
+ 3u,
+ render_surface_layer_list.at(0)->render_surface()->layer_list().size());
+ EXPECT_EQ(front_facing_child->id(),
+ render_surface_layer_list.at(0)
+ ->render_surface()->layer_list().at(0)->id());
+ EXPECT_EQ(front_facing_surface->id(),
+ render_surface_layer_list.at(0)
+ ->render_surface()->layer_list().at(1)->id());
+ EXPECT_EQ(back_facing_surface->id(),
+ render_surface_layer_list.at(0)
+ ->render_surface()->layer_list().at(2)->id());
+
+ // Verify front_facing_surface's layer list.
+ ASSERT_EQ(
+ 2u,
+ render_surface_layer_list.at(1)->render_surface()->layer_list().size());
+ EXPECT_EQ(front_facing_surface->id(),
+ render_surface_layer_list.at(1)
+ ->render_surface()->layer_list().at(0)->id());
+ EXPECT_EQ(front_facing_child_of_front_facing_surface->id(),
+ render_surface_layer_list.at(1)
+ ->render_surface()->layer_list().at(1)->id());
+
+ // Verify back_facing_surface's layer list; its own layer should be culled
+ // from the surface list.
+ ASSERT_EQ(
+ 1u,
+ render_surface_layer_list.at(2)->render_surface()->layer_list().size());
+ EXPECT_EQ(front_facing_child_of_back_facing_surface->id(),
+ render_surface_layer_list.at(2)
+ ->render_surface()->layer_list().at(0)->id());
+}
+
+TEST_F(LayerTreeHostCommonTest, BackFaceCullingWithPreserves3d) {
+ // Verify the behavior of back-face culling when preserves-3d transform style
+ // is used.
+
+ const gfx::Transform identity_matrix;
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> front_facing_child =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> back_facing_child =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> front_facing_surface =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> back_facing_surface =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent>
+ front_facing_child_of_front_facing_surface =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent>
+ back_facing_child_of_front_facing_surface =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent>
+ front_facing_child_of_back_facing_surface =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent>
+ back_facing_child_of_back_facing_surface =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> dummy_replica_layer1 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> dummy_replica_layer2 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+
+ parent->AddChild(front_facing_child);
+ parent->AddChild(back_facing_child);
+ parent->AddChild(front_facing_surface);
+ parent->AddChild(back_facing_surface);
+ front_facing_surface->AddChild(front_facing_child_of_front_facing_surface);
+ front_facing_surface->AddChild(back_facing_child_of_front_facing_surface);
+ back_facing_surface->AddChild(front_facing_child_of_back_facing_surface);
+ back_facing_surface->AddChild(back_facing_child_of_back_facing_surface);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(parent);
+
+ // Nothing is double-sided
+ front_facing_child->SetDoubleSided(false);
+ back_facing_child->SetDoubleSided(false);
+ front_facing_surface->SetDoubleSided(false);
+ back_facing_surface->SetDoubleSided(false);
+ front_facing_child_of_front_facing_surface->SetDoubleSided(false);
+ back_facing_child_of_front_facing_surface->SetDoubleSided(false);
+ front_facing_child_of_back_facing_surface->SetDoubleSided(false);
+ back_facing_child_of_back_facing_surface->SetDoubleSided(false);
+
+ gfx::Transform backface_matrix;
+ backface_matrix.Translate(50.0, 50.0);
+ backface_matrix.RotateAboutYAxis(180.0);
+ backface_matrix.Translate(-50.0, -50.0);
+
+ // Opacity will not force creation of render surfaces in this case because of
+ // the preserve-3d transform style. Instead, an example of when a surface
+ // would be created with preserve-3d is when there is a replica layer.
+ front_facing_surface->SetReplicaLayer(dummy_replica_layer1.get());
+ back_facing_surface->SetReplicaLayer(dummy_replica_layer2.get());
+
+ // Each surface creates its own new 3d rendering context (as defined by W3C
+ // spec). According to current W3C CSS gfx::Transforms spec, layers in a 3d
+ // rendering context should use the transform with respect to that context.
+ // This 3d rendering context occurs when (a) parent's transform style is flat
+ // and (b) the layer's transform style is preserve-3d.
+ SetLayerPropertiesForTesting(parent.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false); // parent transform style is flat.
+ SetLayerPropertiesForTesting(front_facing_child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(back_facing_child.get(),
+ backface_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(
+ front_facing_surface.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ true); // surface transform style is preserve-3d.
+ SetLayerPropertiesForTesting(
+ back_facing_surface.get(),
+ backface_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ true); // surface transform style is preserve-3d.
+ SetLayerPropertiesForTesting(front_facing_child_of_front_facing_surface.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(back_facing_child_of_front_facing_surface.get(),
+ backface_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(front_facing_child_of_back_facing_surface.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(back_facing_child_of_back_facing_surface.get(),
+ backface_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ parent.get(), parent->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // Verify which render surfaces were created.
+ EXPECT_FALSE(front_facing_child->render_surface());
+ EXPECT_FALSE(back_facing_child->render_surface());
+ EXPECT_TRUE(front_facing_surface->render_surface());
+ EXPECT_FALSE(back_facing_surface->render_surface());
+ EXPECT_FALSE(front_facing_child_of_front_facing_surface->render_surface());
+ EXPECT_FALSE(back_facing_child_of_front_facing_surface->render_surface());
+ EXPECT_FALSE(front_facing_child_of_back_facing_surface->render_surface());
+ EXPECT_FALSE(back_facing_child_of_back_facing_surface->render_surface());
+
+ // Verify the render_surface_layer_list. The back-facing surface should be
+ // culled.
+ ASSERT_EQ(2u, render_surface_layer_list.size());
+ EXPECT_EQ(parent->id(), render_surface_layer_list.at(0)->id());
+ EXPECT_EQ(front_facing_surface->id(), render_surface_layer_list.at(1)->id());
+
+ // Verify root surface's layer list.
+ ASSERT_EQ(
+ 2u,
+ render_surface_layer_list.at(0)->render_surface()->layer_list().size());
+ EXPECT_EQ(front_facing_child->id(),
+ render_surface_layer_list.at(0)
+ ->render_surface()->layer_list().at(0)->id());
+ EXPECT_EQ(front_facing_surface->id(),
+ render_surface_layer_list.at(0)
+ ->render_surface()->layer_list().at(1)->id());
+
+ // Verify front_facing_surface's layer list.
+ ASSERT_EQ(
+ 2u,
+ render_surface_layer_list.at(1)->render_surface()->layer_list().size());
+ EXPECT_EQ(front_facing_surface->id(),
+ render_surface_layer_list.at(1)
+ ->render_surface()->layer_list().at(0)->id());
+ EXPECT_EQ(front_facing_child_of_front_facing_surface->id(),
+ render_surface_layer_list.at(1)
+ ->render_surface()->layer_list().at(1)->id());
+}
+
+TEST_F(LayerTreeHostCommonTest, BackFaceCullingWithAnimatingTransforms) {
+ // Verify that layers are appropriately culled when their back face is showing
+ // and they are not double sided, while animations are going on.
+ //
+ // Layers that are animating do not get culled on the main thread, as their
+ // transforms should be treated as "unknown" so we can not be sure that their
+ // back face is really showing.
+ const gfx::Transform identity_matrix;
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> child =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> animating_surface =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> child_of_animating_surface =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> animating_child =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> child2 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+
+ parent->AddChild(child);
+ parent->AddChild(animating_surface);
+ animating_surface->AddChild(child_of_animating_surface);
+ parent->AddChild(animating_child);
+ parent->AddChild(child2);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(parent);
+
+ // Nothing is double-sided
+ child->SetDoubleSided(false);
+ child2->SetDoubleSided(false);
+ animating_surface->SetDoubleSided(false);
+ child_of_animating_surface->SetDoubleSided(false);
+ animating_child->SetDoubleSided(false);
+
+ gfx::Transform backface_matrix;
+ backface_matrix.Translate(50.0, 50.0);
+ backface_matrix.RotateAboutYAxis(180.0);
+ backface_matrix.Translate(-50.0, -50.0);
+
+ // Make our render surface.
+ animating_surface->SetForceRenderSurface(true);
+
+ // Animate the transform on the render surface.
+ AddAnimatedTransformToController(
+ animating_surface->layer_animation_controller(), 10.0, 30, 0);
+ // This is just an animating layer, not a surface.
+ AddAnimatedTransformToController(
+ animating_child->layer_animation_controller(), 10.0, 30, 0);
+
+ SetLayerPropertiesForTesting(parent.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(child.get(),
+ backface_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(animating_surface.get(),
+ backface_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(child_of_animating_surface.get(),
+ backface_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(animating_child.get(),
+ backface_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(child2.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ parent.get(), parent->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ EXPECT_FALSE(child->render_surface());
+ EXPECT_TRUE(animating_surface->render_surface());
+ EXPECT_FALSE(child_of_animating_surface->render_surface());
+ EXPECT_FALSE(animating_child->render_surface());
+ EXPECT_FALSE(child2->render_surface());
+
+ // Verify that the animating_child and child_of_animating_surface were not
+ // culled, but that child was.
+ ASSERT_EQ(2u, render_surface_layer_list.size());
+ EXPECT_EQ(parent->id(), render_surface_layer_list.at(0)->id());
+ EXPECT_EQ(animating_surface->id(), render_surface_layer_list.at(1)->id());
+
+ // The non-animating child be culled from the layer list for the parent render
+ // surface.
+ ASSERT_EQ(
+ 3u,
+ render_surface_layer_list.at(0)->render_surface()->layer_list().size());
+ EXPECT_EQ(animating_surface->id(),
+ render_surface_layer_list.at(0)
+ ->render_surface()->layer_list().at(0)->id());
+ EXPECT_EQ(animating_child->id(),
+ render_surface_layer_list.at(0)
+ ->render_surface()->layer_list().at(1)->id());
+ EXPECT_EQ(child2->id(),
+ render_surface_layer_list.at(0)
+ ->render_surface()->layer_list().at(2)->id());
+
+ ASSERT_EQ(
+ 2u,
+ render_surface_layer_list.at(1)->render_surface()->layer_list().size());
+ EXPECT_EQ(animating_surface->id(),
+ render_surface_layer_list.at(1)
+ ->render_surface()->layer_list().at(0)->id());
+ EXPECT_EQ(child_of_animating_surface->id(),
+ render_surface_layer_list.at(1)
+ ->render_surface()->layer_list().at(1)->id());
+
+ EXPECT_FALSE(child2->visible_content_rect().IsEmpty());
+
+ // The animating layers should have a visible content rect that represents the
+ // area of the front face that is within the viewport.
+ EXPECT_EQ(animating_child->visible_content_rect(),
+ gfx::Rect(animating_child->content_bounds()));
+ EXPECT_EQ(animating_surface->visible_content_rect(),
+ gfx::Rect(animating_surface->content_bounds()));
+ // And layers in the subtree of the animating layer should have valid visible
+ // content rects also.
+ EXPECT_EQ(child_of_animating_surface->visible_content_rect(),
+ gfx::Rect(child_of_animating_surface->content_bounds()));
+}
+
+TEST_F(LayerTreeHostCommonTest,
+ BackFaceCullingWithPreserves3dForFlatteningSurface) {
+ // Verify the behavior of back-face culling for a render surface that is
+ // created when it flattens its subtree, and its parent has preserves-3d.
+
+ const gfx::Transform identity_matrix;
+ scoped_refptr<Layer> parent = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> front_facing_surface =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> back_facing_surface =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> child1 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+ scoped_refptr<LayerWithForcedDrawsContent> child2 =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+
+ parent->AddChild(front_facing_surface);
+ parent->AddChild(back_facing_surface);
+ front_facing_surface->AddChild(child1);
+ back_facing_surface->AddChild(child2);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(parent);
+
+ // RenderSurfaces are not double-sided
+ front_facing_surface->SetDoubleSided(false);
+ back_facing_surface->SetDoubleSided(false);
+
+ gfx::Transform backface_matrix;
+ backface_matrix.Translate(50.0, 50.0);
+ backface_matrix.RotateAboutYAxis(180.0);
+ backface_matrix.Translate(-50.0, -50.0);
+
+ SetLayerPropertiesForTesting(parent.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ true); // parent transform style is preserve3d.
+ SetLayerPropertiesForTesting(front_facing_surface.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false); // surface transform style is flat.
+ SetLayerPropertiesForTesting(back_facing_surface.get(),
+ backface_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false); // surface transform style is flat.
+ SetLayerPropertiesForTesting(child1.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(child2.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ parent.get(), parent->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // Verify which render surfaces were created.
+ EXPECT_TRUE(front_facing_surface->render_surface());
+ EXPECT_FALSE(
+ back_facing_surface->render_surface()); // because it should be culled
+ EXPECT_FALSE(child1->render_surface());
+ EXPECT_FALSE(child2->render_surface());
+
+ // Verify the render_surface_layer_list. The back-facing surface should be
+ // culled.
+ ASSERT_EQ(2u, render_surface_layer_list.size());
+ EXPECT_EQ(parent->id(), render_surface_layer_list.at(0)->id());
+ EXPECT_EQ(front_facing_surface->id(), render_surface_layer_list.at(1)->id());
+
+ // Verify root surface's layer list.
+ ASSERT_EQ(
+ 1u,
+ render_surface_layer_list.at(0)->render_surface()->layer_list().size());
+ EXPECT_EQ(front_facing_surface->id(),
+ render_surface_layer_list.at(0)
+ ->render_surface()->layer_list().at(0)->id());
+
+ // Verify front_facing_surface's layer list.
+ ASSERT_EQ(
+ 2u,
+ render_surface_layer_list.at(1)->render_surface()->layer_list().size());
+ EXPECT_EQ(front_facing_surface->id(),
+ render_surface_layer_list.at(1)
+ ->render_surface()->layer_list().at(0)->id());
+ EXPECT_EQ(child1->id(),
+ render_surface_layer_list.at(1)
+ ->render_surface()->layer_list().at(1)->id());
+}
+
+
+TEST_F(LayerTreeHostCommonTest, HitTestingForEmptyLayerList) {
+ // Hit testing on an empty render_surface_layer_list should return a null
+ // pointer.
+ LayerImplList render_surface_layer_list;
+
+ gfx::Point test_point(0, 0);
+ LayerImpl* result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(10, 20);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+}
+
+TEST_F(LayerTreeHostCommonTest, HitTestingForSingleLayer) {
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ scoped_ptr<LayerImpl> root =
+ LayerImpl::Create(host_impl.active_tree(), 12345);
+
+ gfx::Transform identity_matrix;
+ gfx::PointF anchor;
+ gfx::PointF position;
+ gfx::Size bounds(100, 100);
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ root->SetDrawsContent(true);
+
+ LayerImplList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // Sanity check the scenario we just created.
+ ASSERT_EQ(1u, render_surface_layer_list.size());
+ ASSERT_EQ(1u, root->render_surface()->layer_list().size());
+
+ // Hit testing for a point outside the layer should return a null pointer.
+ gfx::Point test_point(101, 101);
+ LayerImpl* result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(-1, -1);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ // Hit testing for a point inside should return the root layer.
+ test_point = gfx::Point(1, 1);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(12345, result_layer->id());
+
+ test_point = gfx::Point(99, 99);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(12345, result_layer->id());
+}
+
+TEST_F(LayerTreeHostCommonTest, HitTestingForSingleLayerAndHud) {
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ scoped_ptr<LayerImpl> root =
+ LayerImpl::Create(host_impl.active_tree(), 12345);
+ scoped_ptr<HeadsUpDisplayLayerImpl> hud =
+ HeadsUpDisplayLayerImpl::Create(host_impl.active_tree(), 11111);
+
+ gfx::Transform identity_matrix;
+ gfx::PointF anchor;
+ gfx::PointF position;
+ gfx::Size bounds(100, 100);
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ root->SetDrawsContent(true);
+
+ // Create hud and add it as a child of root.
+ gfx::Size hud_bounds(200, 200);
+ SetLayerPropertiesForTesting(hud.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ position,
+ hud_bounds,
+ false);
+ hud->SetDrawsContent(true);
+
+ host_impl.active_tree()->set_hud_layer(hud.get());
+ root->AddChild(hud.PassAs<LayerImpl>());
+
+ LayerImplList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
+ root.get(), hud_bounds, &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // Sanity check the scenario we just created.
+ ASSERT_EQ(1u, render_surface_layer_list.size());
+ ASSERT_EQ(2u, root->render_surface()->layer_list().size());
+
+ // Hit testing for a point inside HUD, but outside root should return null
+ gfx::Point test_point(101, 101);
+ LayerImpl* result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(-1, -1);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ // Hit testing for a point inside should return the root layer, never the HUD
+ // layer.
+ test_point = gfx::Point(1, 1);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(12345, result_layer->id());
+
+ test_point = gfx::Point(99, 99);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(12345, result_layer->id());
+}
+
+TEST_F(LayerTreeHostCommonTest, HitTestingForUninvertibleTransform) {
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ scoped_ptr<LayerImpl> root =
+ LayerImpl::Create(host_impl.active_tree(), 12345);
+
+ gfx::Transform uninvertible_transform;
+ uninvertible_transform.matrix().setDouble(0, 0, 0.0);
+ uninvertible_transform.matrix().setDouble(1, 1, 0.0);
+ uninvertible_transform.matrix().setDouble(2, 2, 0.0);
+ uninvertible_transform.matrix().setDouble(3, 3, 0.0);
+ ASSERT_FALSE(uninvertible_transform.IsInvertible());
+
+ gfx::Transform identity_matrix;
+ gfx::PointF anchor;
+ gfx::PointF position;
+ gfx::Size bounds(100, 100);
+ SetLayerPropertiesForTesting(root.get(),
+ uninvertible_transform,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ root->SetDrawsContent(true);
+
+ LayerImplList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // Sanity check the scenario we just created.
+ ASSERT_EQ(1u, render_surface_layer_list.size());
+ ASSERT_EQ(1u, root->render_surface()->layer_list().size());
+ ASSERT_FALSE(root->screen_space_transform().IsInvertible());
+
+ // Hit testing any point should not hit the layer. If the invertible matrix is
+ // accidentally ignored and treated like an identity, then the hit testing
+ // will incorrectly hit the layer when it shouldn't.
+ gfx::Point test_point(1, 1);
+ LayerImpl* result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(10, 10);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(10, 30);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(50, 50);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(67, 48);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(99, 99);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(-1, -1);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+}
+
+TEST_F(LayerTreeHostCommonTest, HitTestingForSinglePositionedLayer) {
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ scoped_ptr<LayerImpl> root =
+ LayerImpl::Create(host_impl.active_tree(), 12345);
+
+ gfx::Transform identity_matrix;
+ gfx::PointF anchor;
+ // this layer is positioned, and hit testing should correctly know where the
+ // layer is located.
+ gfx::PointF position(50.f, 50.f);
+ gfx::Size bounds(100, 100);
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ root->SetDrawsContent(true);
+
+ LayerImplList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // Sanity check the scenario we just created.
+ ASSERT_EQ(1u, render_surface_layer_list.size());
+ ASSERT_EQ(1u, root->render_surface()->layer_list().size());
+
+ // Hit testing for a point outside the layer should return a null pointer.
+ gfx::Point test_point(49, 49);
+ LayerImpl* result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ // Even though the layer exists at (101, 101), it should not be visible there
+ // since the root render surface would clamp it.
+ test_point = gfx::Point(101, 101);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ // Hit testing for a point inside should return the root layer.
+ test_point = gfx::Point(51, 51);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(12345, result_layer->id());
+
+ test_point = gfx::Point(99, 99);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(12345, result_layer->id());
+}
+
+TEST_F(LayerTreeHostCommonTest, HitTestingForSingleRotatedLayer) {
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ scoped_ptr<LayerImpl> root =
+ LayerImpl::Create(host_impl.active_tree(), 12345);
+
+ gfx::Transform identity_matrix;
+ gfx::Transform rotation45_degrees_about_center;
+ rotation45_degrees_about_center.Translate(50.0, 50.0);
+ rotation45_degrees_about_center.RotateAboutZAxis(45.0);
+ rotation45_degrees_about_center.Translate(-50.0, -50.0);
+ gfx::PointF anchor;
+ gfx::PointF position;
+ gfx::Size bounds(100, 100);
+ SetLayerPropertiesForTesting(root.get(),
+ rotation45_degrees_about_center,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ root->SetDrawsContent(true);
+
+ LayerImplList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // Sanity check the scenario we just created.
+ ASSERT_EQ(1u, render_surface_layer_list.size());
+ ASSERT_EQ(1u, root->render_surface()->layer_list().size());
+
+ // Hit testing for points outside the layer.
+ // These corners would have been inside the un-transformed layer, but they
+ // should not hit the correctly transformed layer.
+ gfx::Point test_point(99, 99);
+ LayerImpl* result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(1, 1);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ // Hit testing for a point inside should return the root layer.
+ test_point = gfx::Point(1, 50);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(12345, result_layer->id());
+
+ // Hit testing the corners that would overlap the unclipped layer, but are
+ // outside the clipped region.
+ test_point = gfx::Point(50, -1);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_FALSE(result_layer);
+
+ test_point = gfx::Point(-1, 50);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_FALSE(result_layer);
+}
+
+TEST_F(LayerTreeHostCommonTest, HitTestingForSinglePerspectiveLayer) {
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ scoped_ptr<LayerImpl> root =
+ LayerImpl::Create(host_impl.active_tree(), 12345);
+
+ gfx::Transform identity_matrix;
+
+ // perspective_projection_about_center * translation_by_z is designed so that
+ // the 100 x 100 layer becomes 50 x 50, and remains centered at (50, 50).
+ gfx::Transform perspective_projection_about_center;
+ perspective_projection_about_center.Translate(50.0, 50.0);
+ perspective_projection_about_center.ApplyPerspectiveDepth(1.0);
+ perspective_projection_about_center.Translate(-50.0, -50.0);
+ gfx::Transform translation_by_z;
+ translation_by_z.Translate3d(0.0, 0.0, -1.0);
+
+ gfx::PointF anchor;
+ gfx::PointF position;
+ gfx::Size bounds(100, 100);
+ SetLayerPropertiesForTesting(
+ root.get(),
+ perspective_projection_about_center * translation_by_z,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ root->SetDrawsContent(true);
+
+ LayerImplList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // Sanity check the scenario we just created.
+ ASSERT_EQ(1u, render_surface_layer_list.size());
+ ASSERT_EQ(1u, root->render_surface()->layer_list().size());
+
+ // Hit testing for points outside the layer.
+ // These corners would have been inside the un-transformed layer, but they
+ // should not hit the correctly transformed layer.
+ gfx::Point test_point(24, 24);
+ LayerImpl* result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(76, 76);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ // Hit testing for a point inside should return the root layer.
+ test_point = gfx::Point(26, 26);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(12345, result_layer->id());
+
+ test_point = gfx::Point(74, 74);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(12345, result_layer->id());
+}
+
+TEST_F(LayerTreeHostCommonTest, HitTestingForSingleLayerWithScaledContents) {
+ // A layer's visible content rect is actually in the layer's content space.
+ // The screen space transform converts from the layer's origin space to screen
+ // space. This test makes sure that hit testing works correctly accounts for
+ // the contents scale. A contents scale that is not 1 effectively forces a
+ // non-identity transform between layer's content space and layer's origin
+ // space. The hit testing code must take this into account.
+ //
+ // To test this, the layer is positioned at (25, 25), and is size (50, 50). If
+ // contents scale is ignored, then hit testing will mis-interpret the visible
+ // content rect as being larger than the actual bounds of the layer.
+ //
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl.active_tree(), 1);
+
+ gfx::Transform identity_matrix;
+ gfx::PointF anchor;
+
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ {
+ gfx::PointF position(25.f, 25.f);
+ gfx::Size bounds(50, 50);
+ scoped_ptr<LayerImpl> test_layer =
+ LayerImpl::Create(host_impl.active_tree(), 12345);
+ SetLayerPropertiesForTesting(test_layer.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+
+ // override content bounds and contents scale
+ test_layer->SetContentBounds(gfx::Size(100, 100));
+ test_layer->SetContentsScale(2, 2);
+
+ test_layer->SetDrawsContent(true);
+ root->AddChild(test_layer.Pass());
+ }
+
+ LayerImplList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // Sanity check the scenario we just created.
+ // The visible content rect for test_layer is actually 100x100, even though
+ // its layout size is 50x50, positioned at 25x25.
+ LayerImpl* test_layer = root->children()[0];
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 100),
+ test_layer->visible_content_rect());
+ ASSERT_EQ(1u, render_surface_layer_list.size());
+ ASSERT_EQ(1u, root->render_surface()->layer_list().size());
+
+ // Hit testing for a point outside the layer should return a null pointer (the
+ // root layer does not draw content, so it will not be hit tested either).
+ gfx::Point test_point(101, 101);
+ LayerImpl* result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(24, 24);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(76, 76);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ // Hit testing for a point inside should return the test layer.
+ test_point = gfx::Point(26, 26);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(12345, result_layer->id());
+
+ test_point = gfx::Point(74, 74);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(12345, result_layer->id());
+}
+
+TEST_F(LayerTreeHostCommonTest, HitTestingForSimpleClippedLayer) {
+ // Test that hit-testing will only work for the visible portion of a layer,
+ // and not the entire layer bounds. Here we just test the simple axis-aligned
+ // case.
+ gfx::Transform identity_matrix;
+ gfx::PointF anchor;
+
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl.active_tree(), 1);
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ {
+ scoped_ptr<LayerImpl> clipping_layer =
+ LayerImpl::Create(host_impl.active_tree(), 123);
+ // this layer is positioned, and hit testing should correctly know where the
+ // layer is located.
+ gfx::PointF position(25.f, 25.f);
+ gfx::Size bounds(50, 50);
+ SetLayerPropertiesForTesting(clipping_layer.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ clipping_layer->SetMasksToBounds(true);
+
+ scoped_ptr<LayerImpl> child =
+ LayerImpl::Create(host_impl.active_tree(), 456);
+ position = gfx::PointF(-50.f, -50.f);
+ bounds = gfx::Size(300, 300);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ child->SetDrawsContent(true);
+ clipping_layer->AddChild(child.Pass());
+ root->AddChild(clipping_layer.Pass());
+ }
+
+ LayerImplList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // Sanity check the scenario we just created.
+ ASSERT_EQ(1u, render_surface_layer_list.size());
+ ASSERT_EQ(1u, root->render_surface()->layer_list().size());
+ ASSERT_EQ(456, root->render_surface()->layer_list().at(0)->id());
+
+ // Hit testing for a point outside the layer should return a null pointer.
+ // Despite the child layer being very large, it should be clipped to the root
+ // layer's bounds.
+ gfx::Point test_point(24, 24);
+ LayerImpl* result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ // Even though the layer exists at (101, 101), it should not be visible there
+ // since the clipping_layer would clamp it.
+ test_point = gfx::Point(76, 76);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ // Hit testing for a point inside should return the child layer.
+ test_point = gfx::Point(26, 26);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(456, result_layer->id());
+
+ test_point = gfx::Point(74, 74);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(456, result_layer->id());
+}
+
+TEST_F(LayerTreeHostCommonTest, HitTestingForMultiClippedRotatedLayer) {
+ // This test checks whether hit testing correctly avoids hit testing with
+ // multiple ancestors that clip in non axis-aligned ways. To pass this test,
+ // the hit testing algorithm needs to recognize that multiple parent layers
+ // may clip the layer, and should not actually hit those clipped areas.
+ //
+ // The child and grand_child layers are both initialized to clip the
+ // rotated_leaf. The child layer is rotated about the top-left corner, so that
+ // the root + child clips combined create a triangle. The rotated_leaf will
+ // only be visible where it overlaps this triangle.
+ //
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl.active_tree(), 123);
+
+ gfx::Transform identity_matrix;
+ gfx::PointF anchor;
+ gfx::PointF position;
+ gfx::Size bounds(100, 100);
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ root->SetMasksToBounds(true);
+ {
+ scoped_ptr<LayerImpl> child =
+ LayerImpl::Create(host_impl.active_tree(), 456);
+ scoped_ptr<LayerImpl> grand_child =
+ LayerImpl::Create(host_impl.active_tree(), 789);
+ scoped_ptr<LayerImpl> rotated_leaf =
+ LayerImpl::Create(host_impl.active_tree(), 2468);
+
+ position = gfx::PointF(10.f, 10.f);
+ bounds = gfx::Size(80, 80);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ child->SetMasksToBounds(true);
+
+ gfx::Transform rotation45_degrees_about_corner;
+ rotation45_degrees_about_corner.RotateAboutZAxis(45.0);
+
+ // remember, positioned with respect to its parent which is already at 10,
+ // 10
+ position = gfx::PointF();
+ bounds =
+ gfx::Size(200, 200); // to ensure it covers at least sqrt(2) * 100.
+ SetLayerPropertiesForTesting(grand_child.get(),
+ rotation45_degrees_about_corner,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ grand_child->SetMasksToBounds(true);
+
+ // Rotates about the center of the layer
+ gfx::Transform rotated_leaf_transform;
+ rotated_leaf_transform.Translate(
+ -10.0, -10.0); // cancel out the grand_parent's position
+ rotated_leaf_transform.RotateAboutZAxis(
+ -45.0); // cancel out the corner 45-degree rotation of the parent.
+ rotated_leaf_transform.Translate(50.0, 50.0);
+ rotated_leaf_transform.RotateAboutZAxis(45.0);
+ rotated_leaf_transform.Translate(-50.0, -50.0);
+ position = gfx::PointF();
+ bounds = gfx::Size(100, 100);
+ SetLayerPropertiesForTesting(rotated_leaf.get(),
+ rotated_leaf_transform,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ rotated_leaf->SetDrawsContent(true);
+
+ grand_child->AddChild(rotated_leaf.Pass());
+ child->AddChild(grand_child.Pass());
+ root->AddChild(child.Pass());
+ }
+
+ LayerImplList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // Sanity check the scenario we just created.
+ // The grand_child is expected to create a render surface because it
+ // MasksToBounds and is not axis aligned.
+ ASSERT_EQ(2u, render_surface_layer_list.size());
+ ASSERT_EQ(
+ 1u,
+ render_surface_layer_list.at(0)->render_surface()->layer_list().size());
+ ASSERT_EQ(789,
+ render_surface_layer_list.at(0)->render_surface()->layer_list().at(
+ 0)->id()); // grand_child's surface.
+ ASSERT_EQ(
+ 1u,
+ render_surface_layer_list.at(1)->render_surface()->layer_list().size());
+ ASSERT_EQ(
+ 2468,
+ render_surface_layer_list[1]->render_surface()->layer_list().at(0)->id());
+
+ // (11, 89) is close to the the bottom left corner within the clip, but it is
+ // not inside the layer.
+ gfx::Point test_point(11, 89);
+ LayerImpl* result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ // Closer inwards from the bottom left will overlap the layer.
+ test_point = gfx::Point(25, 75);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(2468, result_layer->id());
+
+ // (4, 50) is inside the unclipped layer, but that corner of the layer should
+ // be clipped away by the grandparent and should not get hit. If hit testing
+ // blindly uses visible content rect without considering how parent may clip
+ // the layer, then hit testing would accidentally think that the point
+ // successfully hits the layer.
+ test_point = gfx::Point(4, 50);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ // (11, 50) is inside the layer and within the clipped area.
+ test_point = gfx::Point(11, 50);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(2468, result_layer->id());
+
+ // Around the middle, just to the right and up, would have hit the layer
+ // except that that area should be clipped away by the parent.
+ test_point = gfx::Point(51, 51);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ // Around the middle, just to the left and down, should successfully hit the
+ // layer.
+ test_point = gfx::Point(49, 51);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(2468, result_layer->id());
+}
+
+TEST_F(LayerTreeHostCommonTest, HitTestingForNonClippingIntermediateLayer) {
+ // This test checks that hit testing code does not accidentally clip to layer
+ // bounds for a layer that actually does not clip.
+ gfx::Transform identity_matrix;
+ gfx::PointF anchor;
+
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl.active_tree(), 1);
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ {
+ scoped_ptr<LayerImpl> intermediate_layer =
+ LayerImpl::Create(host_impl.active_tree(), 123);
+ // this layer is positioned, and hit testing should correctly know where the
+ // layer is located.
+ gfx::PointF position(10.f, 10.f);
+ gfx::Size bounds(50, 50);
+ SetLayerPropertiesForTesting(intermediate_layer.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ // Sanity check the intermediate layer should not clip.
+ ASSERT_FALSE(intermediate_layer->masks_to_bounds());
+ ASSERT_FALSE(intermediate_layer->mask_layer());
+
+ // The child of the intermediate_layer is translated so that it does not
+ // overlap intermediate_layer at all. If child is incorrectly clipped, we
+ // would not be able to hit it successfully.
+ scoped_ptr<LayerImpl> child =
+ LayerImpl::Create(host_impl.active_tree(), 456);
+ position = gfx::PointF(60.f, 60.f); // 70, 70 in screen space
+ bounds = gfx::Size(20, 20);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ child->SetDrawsContent(true);
+ intermediate_layer->AddChild(child.Pass());
+ root->AddChild(intermediate_layer.Pass());
+ }
+
+ LayerImplList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // Sanity check the scenario we just created.
+ ASSERT_EQ(1u, render_surface_layer_list.size());
+ ASSERT_EQ(1u, root->render_surface()->layer_list().size());
+ ASSERT_EQ(456, root->render_surface()->layer_list().at(0)->id());
+
+ // Hit testing for a point outside the layer should return a null pointer.
+ gfx::Point test_point(69, 69);
+ LayerImpl* result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(91, 91);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ // Hit testing for a point inside should return the child layer.
+ test_point = gfx::Point(71, 71);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(456, result_layer->id());
+
+ test_point = gfx::Point(89, 89);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(456, result_layer->id());
+}
+
+TEST_F(LayerTreeHostCommonTest, HitTestingForMultipleLayers) {
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl.active_tree(), 1);
+
+ gfx::Transform identity_matrix;
+ gfx::PointF anchor;
+ gfx::PointF position;
+ gfx::Size bounds(100, 100);
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ root->SetDrawsContent(true);
+ {
+ // child 1 and child2 are initialized to overlap between x=50 and x=60.
+ // grand_child is set to overlap both child1 and child2 between y=50 and
+ // y=60. The expected stacking order is: (front) child2, (second)
+ // grand_child, (third) child1, and (back) the root layer behind all other
+ // layers.
+
+ scoped_ptr<LayerImpl> child1 =
+ LayerImpl::Create(host_impl.active_tree(), 2);
+ scoped_ptr<LayerImpl> child2 =
+ LayerImpl::Create(host_impl.active_tree(), 3);
+ scoped_ptr<LayerImpl> grand_child1 =
+ LayerImpl::Create(host_impl.active_tree(), 4);
+
+ position = gfx::PointF(10.f, 10.f);
+ bounds = gfx::Size(50, 50);
+ SetLayerPropertiesForTesting(child1.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ child1->SetDrawsContent(true);
+
+ position = gfx::PointF(50.f, 10.f);
+ bounds = gfx::Size(50, 50);
+ SetLayerPropertiesForTesting(child2.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ child2->SetDrawsContent(true);
+
+ // Remember that grand_child is positioned with respect to its parent (i.e.
+ // child1). In screen space, the intended position is (10, 50), with size
+ // 100 x 50.
+ position = gfx::PointF(0.f, 40.f);
+ bounds = gfx::Size(100, 50);
+ SetLayerPropertiesForTesting(grand_child1.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ grand_child1->SetDrawsContent(true);
+
+ child1->AddChild(grand_child1.Pass());
+ root->AddChild(child1.Pass());
+ root->AddChild(child2.Pass());
+ }
+
+ LayerImpl* child1 = root->children()[0];
+ LayerImpl* child2 = root->children()[1];
+ LayerImpl* grand_child1 = child1->children()[0];
+
+ LayerImplList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // Sanity check the scenario we just created.
+ ASSERT_TRUE(child1);
+ ASSERT_TRUE(child2);
+ ASSERT_TRUE(grand_child1);
+ ASSERT_EQ(1u, render_surface_layer_list.size());
+
+ RenderSurfaceImpl* root_render_surface = root->render_surface();
+ ASSERT_EQ(4u, root_render_surface->layer_list().size());
+ ASSERT_EQ(1, root_render_surface->layer_list().at(0)->id()); // root layer
+ ASSERT_EQ(2, root_render_surface->layer_list().at(1)->id()); // child1
+ ASSERT_EQ(4, root_render_surface->layer_list().at(2)->id()); // grand_child1
+ ASSERT_EQ(3, root_render_surface->layer_list().at(3)->id()); // child2
+
+ // Nothing overlaps the root_layer at (1, 1), so hit testing there should find
+ // the root layer.
+ gfx::Point test_point = gfx::Point(1, 1);
+ LayerImpl* result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(1, result_layer->id());
+
+ // At (15, 15), child1 and root are the only layers. child1 is expected to be
+ // on top.
+ test_point = gfx::Point(15, 15);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(2, result_layer->id());
+
+ // At (51, 20), child1 and child2 overlap. child2 is expected to be on top.
+ test_point = gfx::Point(51, 20);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(3, result_layer->id());
+
+ // At (80, 51), child2 and grand_child1 overlap. child2 is expected to be on
+ // top.
+ test_point = gfx::Point(80, 51);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(3, result_layer->id());
+
+ // At (51, 51), all layers overlap each other. child2 is expected to be on top
+ // of all other layers.
+ test_point = gfx::Point(51, 51);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(3, result_layer->id());
+
+ // At (20, 51), child1 and grand_child1 overlap. grand_child1 is expected to
+ // be on top.
+ test_point = gfx::Point(20, 51);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(4, result_layer->id());
+}
+
+TEST_F(LayerTreeHostCommonTest, HitTestingForMultipleLayerLists) {
+ //
+ // The geometry is set up similarly to the previous case, but
+ // all layers are forced to be render surfaces now.
+ //
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl.active_tree(), 1);
+
+ gfx::Transform identity_matrix;
+ gfx::PointF anchor;
+ gfx::PointF position;
+ gfx::Size bounds(100, 100);
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ root->SetDrawsContent(true);
+ {
+ // child 1 and child2 are initialized to overlap between x=50 and x=60.
+ // grand_child is set to overlap both child1 and child2 between y=50 and
+ // y=60. The expected stacking order is: (front) child2, (second)
+ // grand_child, (third) child1, and (back) the root layer behind all other
+ // layers.
+
+ scoped_ptr<LayerImpl> child1 =
+ LayerImpl::Create(host_impl.active_tree(), 2);
+ scoped_ptr<LayerImpl> child2 =
+ LayerImpl::Create(host_impl.active_tree(), 3);
+ scoped_ptr<LayerImpl> grand_child1 =
+ LayerImpl::Create(host_impl.active_tree(), 4);
+
+ position = gfx::PointF(10.f, 10.f);
+ bounds = gfx::Size(50, 50);
+ SetLayerPropertiesForTesting(child1.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ child1->SetDrawsContent(true);
+ child1->SetForceRenderSurface(true);
+
+ position = gfx::PointF(50.f, 10.f);
+ bounds = gfx::Size(50, 50);
+ SetLayerPropertiesForTesting(child2.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ child2->SetDrawsContent(true);
+ child2->SetForceRenderSurface(true);
+
+ // Remember that grand_child is positioned with respect to its parent (i.e.
+ // child1). In screen space, the intended position is (10, 50), with size
+ // 100 x 50.
+ position = gfx::PointF(0.f, 40.f);
+ bounds = gfx::Size(100, 50);
+ SetLayerPropertiesForTesting(grand_child1.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ grand_child1->SetDrawsContent(true);
+ grand_child1->SetForceRenderSurface(true);
+
+ child1->AddChild(grand_child1.Pass());
+ root->AddChild(child1.Pass());
+ root->AddChild(child2.Pass());
+ }
+
+ LayerImpl* child1 = root->children()[0];
+ LayerImpl* child2 = root->children()[1];
+ LayerImpl* grand_child1 = child1->children()[0];
+
+ LayerImplList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // Sanity check the scenario we just created.
+ ASSERT_TRUE(child1);
+ ASSERT_TRUE(child2);
+ ASSERT_TRUE(grand_child1);
+ ASSERT_TRUE(child1->render_surface());
+ ASSERT_TRUE(child2->render_surface());
+ ASSERT_TRUE(grand_child1->render_surface());
+ ASSERT_EQ(4u, render_surface_layer_list.size());
+ // The root surface has the root layer, and child1's and child2's render
+ // surfaces.
+ ASSERT_EQ(3u, root->render_surface()->layer_list().size());
+ // The child1 surface has the child1 layer and grand_child1's render surface.
+ ASSERT_EQ(2u, child1->render_surface()->layer_list().size());
+ ASSERT_EQ(1u, child2->render_surface()->layer_list().size());
+ ASSERT_EQ(1u, grand_child1->render_surface()->layer_list().size());
+ ASSERT_EQ(1, render_surface_layer_list.at(0)->id()); // root layer
+ ASSERT_EQ(2, render_surface_layer_list[1]->id()); // child1
+ ASSERT_EQ(4, render_surface_layer_list.at(2)->id()); // grand_child1
+ ASSERT_EQ(3, render_surface_layer_list[3]->id()); // child2
+
+ // Nothing overlaps the root_layer at (1, 1), so hit testing there should find
+ // the root layer.
+ gfx::Point test_point = gfx::Point(1, 1);
+ LayerImpl* result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(1, result_layer->id());
+
+ // At (15, 15), child1 and root are the only layers. child1 is expected to be
+ // on top.
+ test_point = gfx::Point(15, 15);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(2, result_layer->id());
+
+ // At (51, 20), child1 and child2 overlap. child2 is expected to be on top.
+ test_point = gfx::Point(51, 20);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(3, result_layer->id());
+
+ // At (80, 51), child2 and grand_child1 overlap. child2 is expected to be on
+ // top.
+ test_point = gfx::Point(80, 51);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(3, result_layer->id());
+
+ // At (51, 51), all layers overlap each other. child2 is expected to be on top
+ // of all other layers.
+ test_point = gfx::Point(51, 51);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(3, result_layer->id());
+
+ // At (20, 51), child1 and grand_child1 overlap. grand_child1 is expected to
+ // be on top.
+ test_point = gfx::Point(20, 51);
+ result_layer = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(4, result_layer->id());
+}
+
+TEST_F(LayerTreeHostCommonTest,
+ HitCheckingTouchHandlerRegionsForEmptyLayerList) {
+ // Hit checking on an empty render_surface_layer_list should return a null
+ // pointer.
+ LayerImplList render_surface_layer_list;
+
+ gfx::Point test_point(0, 0);
+ LayerImpl* result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(10, 20);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+}
+
+TEST_F(LayerTreeHostCommonTest, HitCheckingTouchHandlerRegionsForSingleLayer) {
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ scoped_ptr<LayerImpl> root =
+ LayerImpl::Create(host_impl.active_tree(), 12345);
+
+ gfx::Transform identity_matrix;
+ Region touch_handler_region(gfx::Rect(10, 10, 50, 50));
+ gfx::PointF anchor;
+ gfx::PointF position;
+ gfx::Size bounds(100, 100);
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ root->SetDrawsContent(true);
+
+ LayerImplList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // Sanity check the scenario we just created.
+ ASSERT_EQ(1u, render_surface_layer_list.size());
+ ASSERT_EQ(1u, root->render_surface()->layer_list().size());
+
+ // Hit checking for any point should return a null pointer for a layer without
+ // any touch event handler regions.
+ gfx::Point test_point(11, 11);
+ LayerImpl* result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ root->SetTouchEventHandlerRegion(touch_handler_region);
+ // Hit checking for a point outside the layer should return a null pointer.
+ test_point = gfx::Point(101, 101);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(-1, -1);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ // Hit checking for a point inside the layer, but outside the touch handler
+ // region should return a null pointer.
+ test_point = gfx::Point(1, 1);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(99, 99);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ // Hit checking for a point inside the touch event handler region should
+ // return the root layer.
+ test_point = gfx::Point(11, 11);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(12345, result_layer->id());
+
+ test_point = gfx::Point(59, 59);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(12345, result_layer->id());
+}
+
+TEST_F(LayerTreeHostCommonTest,
+ HitCheckingTouchHandlerRegionsForUninvertibleTransform) {
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ scoped_ptr<LayerImpl> root =
+ LayerImpl::Create(host_impl.active_tree(), 12345);
+
+ gfx::Transform uninvertible_transform;
+ uninvertible_transform.matrix().setDouble(0, 0, 0.0);
+ uninvertible_transform.matrix().setDouble(1, 1, 0.0);
+ uninvertible_transform.matrix().setDouble(2, 2, 0.0);
+ uninvertible_transform.matrix().setDouble(3, 3, 0.0);
+ ASSERT_FALSE(uninvertible_transform.IsInvertible());
+
+ gfx::Transform identity_matrix;
+ Region touch_handler_region(gfx::Rect(10, 10, 50, 50));
+ gfx::PointF anchor;
+ gfx::PointF position;
+ gfx::Size bounds(100, 100);
+ SetLayerPropertiesForTesting(root.get(),
+ uninvertible_transform,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ root->SetDrawsContent(true);
+ root->SetTouchEventHandlerRegion(touch_handler_region);
+
+ LayerImplList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // Sanity check the scenario we just created.
+ ASSERT_EQ(1u, render_surface_layer_list.size());
+ ASSERT_EQ(1u, root->render_surface()->layer_list().size());
+ ASSERT_FALSE(root->screen_space_transform().IsInvertible());
+
+ // Hit checking any point should not hit the touch handler region on the
+ // layer. If the invertible matrix is accidentally ignored and treated like an
+ // identity, then the hit testing will incorrectly hit the layer when it
+ // shouldn't.
+ gfx::Point test_point(1, 1);
+ LayerImpl* result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(10, 10);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(10, 30);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(50, 50);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(67, 48);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(99, 99);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(-1, -1);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+}
+
+TEST_F(LayerTreeHostCommonTest,
+ HitCheckingTouchHandlerRegionsForSinglePositionedLayer) {
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ scoped_ptr<LayerImpl> root =
+ LayerImpl::Create(host_impl.active_tree(), 12345);
+
+ gfx::Transform identity_matrix;
+ Region touch_handler_region(gfx::Rect(10, 10, 50, 50));
+ gfx::PointF anchor;
+ // this layer is positioned, and hit testing should correctly know where the
+ // layer is located.
+ gfx::PointF position(50.f, 50.f);
+ gfx::Size bounds(100, 100);
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ root->SetDrawsContent(true);
+ root->SetTouchEventHandlerRegion(touch_handler_region);
+
+ LayerImplList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // Sanity check the scenario we just created.
+ ASSERT_EQ(1u, render_surface_layer_list.size());
+ ASSERT_EQ(1u, root->render_surface()->layer_list().size());
+
+ // Hit checking for a point outside the layer should return a null pointer.
+ gfx::Point test_point(49, 49);
+ LayerImpl* result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ // Even though the layer has a touch handler region containing (101, 101), it
+ // should not be visible there since the root render surface would clamp it.
+ test_point = gfx::Point(101, 101);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ // Hit checking for a point inside the layer, but outside the touch handler
+ // region should return a null pointer.
+ test_point = gfx::Point(51, 51);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ // Hit checking for a point inside the touch event handler region should
+ // return the root layer.
+ test_point = gfx::Point(61, 61);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(12345, result_layer->id());
+
+ test_point = gfx::Point(99, 99);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(12345, result_layer->id());
+}
+
+TEST_F(LayerTreeHostCommonTest,
+ HitCheckingTouchHandlerRegionsForSingleLayerWithScaledContents) {
+ // A layer's visible content rect is actually in the layer's content space.
+ // The screen space transform converts from the layer's origin space to screen
+ // space. This test makes sure that hit testing works correctly accounts for
+ // the contents scale. A contents scale that is not 1 effectively forces a
+ // non-identity transform between layer's content space and layer's origin
+ // space. The hit testing code must take this into account.
+ //
+ // To test this, the layer is positioned at (25, 25), and is size (50, 50). If
+ // contents scale is ignored, then hit checking will mis-interpret the visible
+ // content rect as being larger than the actual bounds of the layer.
+ //
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl.active_tree(), 1);
+
+ gfx::Transform identity_matrix;
+ gfx::PointF anchor;
+
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ {
+ Region touch_handler_region(gfx::Rect(10, 10, 30, 30));
+ gfx::PointF position(25.f, 25.f);
+ gfx::Size bounds(50, 50);
+ scoped_ptr<LayerImpl> test_layer =
+ LayerImpl::Create(host_impl.active_tree(), 12345);
+ SetLayerPropertiesForTesting(test_layer.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+
+ // override content bounds and contents scale
+ test_layer->SetContentBounds(gfx::Size(100, 100));
+ test_layer->SetContentsScale(2, 2);
+
+ test_layer->SetDrawsContent(true);
+ test_layer->SetTouchEventHandlerRegion(touch_handler_region);
+ root->AddChild(test_layer.Pass());
+ }
+
+ LayerImplList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // Sanity check the scenario we just created.
+ // The visible content rect for test_layer is actually 100x100, even though
+ // its layout size is 50x50, positioned at 25x25.
+ LayerImpl* test_layer = root->children()[0];
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 100), test_layer->visible_content_rect());
+ ASSERT_EQ(1u, render_surface_layer_list.size());
+ ASSERT_EQ(1u, root->render_surface()->layer_list().size());
+
+ // Hit checking for a point outside the layer should return a null pointer
+ // (the root layer does not draw content, so it will not be tested either).
+ gfx::Point test_point(76, 76);
+ LayerImpl* result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ // Hit checking for a point inside the layer, but outside the touch handler
+ // region should return a null pointer.
+ test_point = gfx::Point(26, 26);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(34, 34);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(65, 65);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(74, 74);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ // Hit checking for a point inside the touch event handler region should
+ // return the root layer.
+ test_point = gfx::Point(35, 35);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(12345, result_layer->id());
+
+ test_point = gfx::Point(64, 64);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(12345, result_layer->id());
+}
+
+TEST_F(LayerTreeHostCommonTest,
+ HitCheckingTouchHandlerRegionsForSingleLayerWithDeviceScale) {
+ // The layer's device_scale_factor and page_scale_factor should scale the
+ // content rect and we should be able to hit the touch handler region by
+ // scaling the points accordingly.
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl.active_tree(), 1);
+
+ gfx::Transform identity_matrix;
+ gfx::PointF anchor;
+ // Set the bounds of the root layer big enough to fit the child when scaled.
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ {
+ Region touch_handler_region(gfx::Rect(10, 10, 30, 30));
+ gfx::PointF position(25.f, 25.f);
+ gfx::Size bounds(50, 50);
+ scoped_ptr<LayerImpl> test_layer =
+ LayerImpl::Create(host_impl.active_tree(), 12345);
+ SetLayerPropertiesForTesting(test_layer.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+
+ test_layer->SetDrawsContent(true);
+ test_layer->SetTouchEventHandlerRegion(touch_handler_region);
+ root->AddChild(test_layer.Pass());
+ }
+
+ LayerImplList render_surface_layer_list;
+ float device_scale_factor = 3.f;
+ float page_scale_factor = 5.f;
+ gfx::Size scaled_bounds_for_root = gfx::ToCeiledSize(
+ gfx::ScaleSize(root->bounds(), device_scale_factor * page_scale_factor));
+
+ LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
+ root.get(), scaled_bounds_for_root, &render_surface_layer_list);
+ inputs.device_scale_factor = device_scale_factor;
+ inputs.page_scale_factor = page_scale_factor;
+ inputs.page_scale_application_layer = root.get();
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // Sanity check the scenario we just created.
+ // The visible content rect for test_layer is actually 100x100, even though
+ // its layout size is 50x50, positioned at 25x25.
+ LayerImpl* test_layer = root->children()[0];
+ ASSERT_EQ(1u, render_surface_layer_list.size());
+ ASSERT_EQ(1u, root->render_surface()->layer_list().size());
+
+ // Check whether the child layer fits into the root after scaled.
+ EXPECT_RECT_EQ(gfx::Rect(test_layer->content_bounds()),
+ test_layer->visible_content_rect());
+
+ // Hit checking for a point outside the layer should return a null pointer
+ // (the root layer does not draw content, so it will not be tested either).
+ gfx::PointF test_point(76.f, 76.f);
+ test_point =
+ gfx::ScalePoint(test_point, device_scale_factor * page_scale_factor);
+ LayerImpl* result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ // Hit checking for a point inside the layer, but outside the touch handler
+ // region should return a null pointer.
+ test_point = gfx::Point(26, 26);
+ test_point =
+ gfx::ScalePoint(test_point, device_scale_factor * page_scale_factor);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(34, 34);
+ test_point =
+ gfx::ScalePoint(test_point, device_scale_factor * page_scale_factor);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(65, 65);
+ test_point =
+ gfx::ScalePoint(test_point, device_scale_factor * page_scale_factor);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(74, 74);
+ test_point =
+ gfx::ScalePoint(test_point, device_scale_factor * page_scale_factor);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ // Hit checking for a point inside the touch event handler region should
+ // return the root layer.
+ test_point = gfx::Point(35, 35);
+ test_point =
+ gfx::ScalePoint(test_point, device_scale_factor * page_scale_factor);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(12345, result_layer->id());
+
+ test_point = gfx::Point(64, 64);
+ test_point =
+ gfx::ScalePoint(test_point, device_scale_factor * page_scale_factor);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(12345, result_layer->id());
+}
+
+TEST_F(LayerTreeHostCommonTest,
+ HitCheckingTouchHandlerRegionsForSimpleClippedLayer) {
+ // Test that hit-checking will only work for the visible portion of a layer,
+ // and not the entire layer bounds. Here we just test the simple axis-aligned
+ // case.
+ gfx::Transform identity_matrix;
+ gfx::PointF anchor;
+
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl.active_tree(), 1);
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ {
+ scoped_ptr<LayerImpl> clipping_layer =
+ LayerImpl::Create(host_impl.active_tree(), 123);
+ // this layer is positioned, and hit testing should correctly know where the
+ // layer is located.
+ gfx::PointF position(25.f, 25.f);
+ gfx::Size bounds(50, 50);
+ SetLayerPropertiesForTesting(clipping_layer.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ clipping_layer->SetMasksToBounds(true);
+
+ scoped_ptr<LayerImpl> child =
+ LayerImpl::Create(host_impl.active_tree(), 456);
+ Region touch_handler_region(gfx::Rect(10, 10, 50, 50));
+ position = gfx::PointF(-50.f, -50.f);
+ bounds = gfx::Size(300, 300);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ anchor,
+ position,
+ bounds,
+ false);
+ child->SetDrawsContent(true);
+ child->SetTouchEventHandlerRegion(touch_handler_region);
+ clipping_layer->AddChild(child.Pass());
+ root->AddChild(clipping_layer.Pass());
+ }
+
+ LayerImplList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // Sanity check the scenario we just created.
+ ASSERT_EQ(1u, render_surface_layer_list.size());
+ ASSERT_EQ(1u, root->render_surface()->layer_list().size());
+ ASSERT_EQ(456, root->render_surface()->layer_list().at(0)->id());
+
+ // Hit checking for a point outside the layer should return a null pointer.
+ // Despite the child layer being very large, it should be clipped to the root
+ // layer's bounds.
+ gfx::Point test_point(24, 24);
+ LayerImpl* result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ // Hit checking for a point inside the layer, but outside the touch handler
+ // region should return a null pointer.
+ test_point = gfx::Point(35, 35);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ test_point = gfx::Point(74, 74);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ EXPECT_FALSE(result_layer);
+
+ // Hit checking for a point inside the touch event handler region should
+ // return the root layer.
+ test_point = gfx::Point(25, 25);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(456, result_layer->id());
+
+ test_point = gfx::Point(34, 34);
+ result_layer =
+ LayerTreeHostCommon::FindLayerThatIsHitByPointInTouchHandlerRegion(
+ test_point, render_surface_layer_list);
+ ASSERT_TRUE(result_layer);
+ EXPECT_EQ(456, result_layer->id());
+}
+
+class NoScaleContentLayer : public ContentLayer {
+ public:
+ static scoped_refptr<NoScaleContentLayer> Create(ContentLayerClient* client) {
+ return make_scoped_refptr(new NoScaleContentLayer(client));
+ }
+
+ virtual void CalculateContentsScale(float ideal_contents_scale,
+ float device_scale_factor,
+ float page_scale_factor,
+ bool animating_transform_to_screen,
+ float* contents_scale_x,
+ float* contents_scale_y,
+ gfx::Size* content_bounds) OVERRIDE {
+ // Skip over the ContentLayer to the base Layer class.
+ Layer::CalculateContentsScale(ideal_contents_scale,
+ device_scale_factor,
+ page_scale_factor,
+ animating_transform_to_screen,
+ contents_scale_x,
+ contents_scale_y,
+ content_bounds);
+ }
+
+ protected:
+ explicit NoScaleContentLayer(ContentLayerClient* client)
+ : ContentLayer(client) {}
+ virtual ~NoScaleContentLayer() {}
+};
+
+scoped_refptr<NoScaleContentLayer> CreateNoScaleDrawableContentLayer(
+ ContentLayerClient* delegate) {
+ scoped_refptr<NoScaleContentLayer> to_return =
+ NoScaleContentLayer::Create(delegate);
+ to_return->SetIsDrawable(true);
+ return to_return;
+}
+
+TEST_F(LayerTreeHostCommonTest, LayerTransformsInHighDPI) {
+ // Verify draw and screen space transforms of layers not in a surface.
+ MockContentLayerClient delegate;
+ gfx::Transform identity_matrix;
+
+ scoped_refptr<ContentLayer> parent = CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(parent.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ true);
+
+ scoped_refptr<ContentLayer> child = CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(2.f, 2.f),
+ gfx::Size(10, 10),
+ true);
+
+ scoped_refptr<ContentLayer> child_empty =
+ CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(child_empty.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(2.f, 2.f),
+ gfx::Size(),
+ true);
+
+ scoped_refptr<NoScaleContentLayer> child_no_scale =
+ CreateNoScaleDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(child_no_scale.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(2.f, 2.f),
+ gfx::Size(10, 10),
+ true);
+
+ parent->AddChild(child);
+ parent->AddChild(child_empty);
+ parent->AddChild(child_no_scale);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(parent);
+
+ float device_scale_factor = 2.5f;
+ float page_scale_factor = 1.f;
+
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ parent.get(), parent->bounds(), &render_surface_layer_list);
+ inputs.device_scale_factor = device_scale_factor;
+ inputs.page_scale_factor = page_scale_factor;
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor, parent);
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor, child);
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor,
+ child_empty);
+ EXPECT_CONTENTS_SCALE_EQ(1, child_no_scale);
+
+ EXPECT_EQ(1u, render_surface_layer_list.size());
+
+ // Verify parent transforms
+ gfx::Transform expected_parent_transform;
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_parent_transform,
+ parent->screen_space_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_parent_transform,
+ parent->draw_transform());
+
+ // Verify results of transformed parent rects
+ gfx::RectF parent_content_bounds(parent->content_bounds());
+
+ gfx::RectF parent_draw_rect =
+ MathUtil::MapClippedRect(parent->draw_transform(), parent_content_bounds);
+ gfx::RectF parent_screen_space_rect = MathUtil::MapClippedRect(
+ parent->screen_space_transform(), parent_content_bounds);
+
+ gfx::RectF expected_parent_draw_rect(parent->bounds());
+ expected_parent_draw_rect.Scale(device_scale_factor);
+ EXPECT_FLOAT_RECT_EQ(expected_parent_draw_rect, parent_draw_rect);
+ EXPECT_FLOAT_RECT_EQ(expected_parent_draw_rect, parent_screen_space_rect);
+
+ // Verify child and child_empty transforms. They should match.
+ gfx::Transform expected_child_transform;
+ expected_child_transform.Translate(
+ device_scale_factor * child->position().x(),
+ device_scale_factor * child->position().y());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->screen_space_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child_empty->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child_empty->screen_space_transform());
+
+ // Verify results of transformed child and child_empty rects. They should
+ // match.
+ gfx::RectF child_content_bounds(child->content_bounds());
+
+ gfx::RectF child_draw_rect =
+ MathUtil::MapClippedRect(child->draw_transform(), child_content_bounds);
+ gfx::RectF child_screen_space_rect = MathUtil::MapClippedRect(
+ child->screen_space_transform(), child_content_bounds);
+
+ gfx::RectF child_empty_draw_rect = MathUtil::MapClippedRect(
+ child_empty->draw_transform(), child_content_bounds);
+ gfx::RectF child_empty_screen_space_rect = MathUtil::MapClippedRect(
+ child_empty->screen_space_transform(), child_content_bounds);
+
+ gfx::RectF expected_child_draw_rect(child->position(), child->bounds());
+ expected_child_draw_rect.Scale(device_scale_factor);
+ EXPECT_FLOAT_RECT_EQ(expected_child_draw_rect, child_draw_rect);
+ EXPECT_FLOAT_RECT_EQ(expected_child_draw_rect, child_screen_space_rect);
+ EXPECT_FLOAT_RECT_EQ(expected_child_draw_rect, child_empty_draw_rect);
+ EXPECT_FLOAT_RECT_EQ(expected_child_draw_rect, child_empty_screen_space_rect);
+
+ // Verify child_no_scale transforms
+ gfx::Transform expected_child_no_scale_transform = child->draw_transform();
+ // All transforms operate on content rects. The child's content rect
+ // incorporates device scale, but the child_no_scale does not; add it here.
+ expected_child_no_scale_transform.Scale(device_scale_factor,
+ device_scale_factor);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_no_scale_transform,
+ child_no_scale->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_no_scale_transform,
+ child_no_scale->screen_space_transform());
+}
+
+TEST_F(LayerTreeHostCommonTest, SurfaceLayerTransformsInHighDPI) {
+ // Verify draw and screen space transforms of layers in a surface.
+ MockContentLayerClient delegate;
+ gfx::Transform identity_matrix;
+
+ gfx::Transform perspective_matrix;
+ perspective_matrix.ApplyPerspectiveDepth(2.0);
+
+ gfx::Transform scale_small_matrix;
+ scale_small_matrix.Scale(1.0 / 10.0, 1.0 / 12.0);
+
+ scoped_refptr<Layer> root = Layer::Create();
+
+ scoped_refptr<ContentLayer> parent = CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(parent.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ true);
+
+ scoped_refptr<ContentLayer> perspective_surface =
+ CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(perspective_surface.get(),
+ perspective_matrix * scale_small_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(2.f, 2.f),
+ gfx::Size(10, 10),
+ true);
+
+ scoped_refptr<ContentLayer> scale_surface =
+ CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(scale_surface.get(),
+ scale_small_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(2.f, 2.f),
+ gfx::Size(10, 10),
+ true);
+
+ perspective_surface->SetForceRenderSurface(true);
+ scale_surface->SetForceRenderSurface(true);
+
+ parent->AddChild(perspective_surface);
+ parent->AddChild(scale_surface);
+ root->AddChild(parent);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ float device_scale_factor = 2.5f;
+ float page_scale_factor = 3.f;
+
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), parent->bounds(), &render_surface_layer_list);
+ inputs.device_scale_factor = device_scale_factor;
+ inputs.page_scale_factor = page_scale_factor;
+ inputs.page_scale_application_layer = root;
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor, parent);
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor,
+ perspective_surface);
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor,
+ scale_surface);
+
+ EXPECT_EQ(3u, render_surface_layer_list.size());
+
+ gfx::Transform expected_parent_draw_transform;
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_parent_draw_transform,
+ parent->draw_transform());
+
+ // The scaled surface is rendered at its appropriate scale, and drawn 1:1
+ // into its target.
+ gfx::Transform expected_scale_surface_draw_transform;
+ expected_scale_surface_draw_transform.Translate(
+ device_scale_factor * page_scale_factor * scale_surface->position().x(),
+ device_scale_factor * page_scale_factor * scale_surface->position().y());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected_scale_surface_draw_transform,
+ scale_surface->render_surface()->draw_transform());
+ gfx::Transform expected_scale_surface_layer_draw_transform =
+ scale_small_matrix;
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_scale_surface_layer_draw_transform,
+ scale_surface->draw_transform());
+
+ // The scale for the perspective surface is not known, so it is rendered 1:1
+ // with the screen, and then scaled during drawing.
+ gfx::Transform expected_perspective_surface_draw_transform;
+ expected_perspective_surface_draw_transform.Translate(
+ device_scale_factor * page_scale_factor *
+ perspective_surface->position().x(),
+ device_scale_factor * page_scale_factor *
+ perspective_surface->position().y());
+ expected_perspective_surface_draw_transform.PreconcatTransform(
+ perspective_matrix);
+ expected_perspective_surface_draw_transform.PreconcatTransform(
+ scale_small_matrix);
+ gfx::Transform expected_perspective_surface_layer_draw_transform;
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected_perspective_surface_draw_transform,
+ perspective_surface->render_surface()->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected_perspective_surface_layer_draw_transform,
+ perspective_surface->draw_transform());
+}
+
+TEST_F(LayerTreeHostCommonTest,
+ LayerTransformsInHighDPIAccurateScaleZeroChildPosition) {
+ // Verify draw and screen space transforms of layers not in a surface.
+ MockContentLayerClient delegate;
+ gfx::Transform identity_matrix;
+
+ scoped_refptr<ContentLayer> parent = CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(parent.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(133, 133),
+ true);
+
+ scoped_refptr<ContentLayer> child = CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(13, 13),
+ true);
+
+ scoped_refptr<NoScaleContentLayer> child_no_scale =
+ CreateNoScaleDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(child_no_scale.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(13, 13),
+ true);
+
+ parent->AddChild(child);
+ parent->AddChild(child_no_scale);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(parent);
+
+ float device_scale_factor = 1.7f;
+ float page_scale_factor = 1.f;
+
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ parent.get(), parent->bounds(), &render_surface_layer_list);
+ inputs.device_scale_factor = device_scale_factor;
+ inputs.page_scale_factor = page_scale_factor;
+ inputs.page_scale_application_layer = parent.get();
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor, parent);
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor, child);
+ EXPECT_CONTENTS_SCALE_EQ(1, child_no_scale);
+
+ EXPECT_EQ(1u, render_surface_layer_list.size());
+
+ // Verify parent transforms
+ gfx::Transform expected_parent_transform;
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_parent_transform,
+ parent->screen_space_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_parent_transform,
+ parent->draw_transform());
+
+ // Verify results of transformed parent rects
+ gfx::RectF parent_content_bounds(parent->content_bounds());
+
+ gfx::RectF parent_draw_rect =
+ MathUtil::MapClippedRect(parent->draw_transform(), parent_content_bounds);
+ gfx::RectF parent_screen_space_rect = MathUtil::MapClippedRect(
+ parent->screen_space_transform(), parent_content_bounds);
+
+ gfx::RectF expected_parent_draw_rect(parent->bounds());
+ expected_parent_draw_rect.Scale(device_scale_factor);
+ expected_parent_draw_rect.set_width(ceil(expected_parent_draw_rect.width()));
+ expected_parent_draw_rect.set_height(
+ ceil(expected_parent_draw_rect.height()));
+ EXPECT_FLOAT_RECT_EQ(expected_parent_draw_rect, parent_draw_rect);
+ EXPECT_FLOAT_RECT_EQ(expected_parent_draw_rect, parent_screen_space_rect);
+
+ // Verify child transforms
+ gfx::Transform expected_child_transform;
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_transform,
+ child->screen_space_transform());
+
+ // Verify results of transformed child rects
+ gfx::RectF child_content_bounds(child->content_bounds());
+
+ gfx::RectF child_draw_rect =
+ MathUtil::MapClippedRect(child->draw_transform(), child_content_bounds);
+ gfx::RectF child_screen_space_rect = MathUtil::MapClippedRect(
+ child->screen_space_transform(), child_content_bounds);
+
+ gfx::RectF expected_child_draw_rect(child->bounds());
+ expected_child_draw_rect.Scale(device_scale_factor);
+ expected_child_draw_rect.set_width(ceil(expected_child_draw_rect.width()));
+ expected_child_draw_rect.set_height(ceil(expected_child_draw_rect.height()));
+ EXPECT_FLOAT_RECT_EQ(expected_child_draw_rect, child_draw_rect);
+ EXPECT_FLOAT_RECT_EQ(expected_child_draw_rect, child_screen_space_rect);
+
+ // Verify child_no_scale transforms
+ gfx::Transform expected_child_no_scale_transform = child->draw_transform();
+ // All transforms operate on content rects. The child's content rect
+ // incorporates device scale, but the child_no_scale does not; add it here.
+ expected_child_no_scale_transform.Scale(device_scale_factor,
+ device_scale_factor);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_no_scale_transform,
+ child_no_scale->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_child_no_scale_transform,
+ child_no_scale->screen_space_transform());
+}
+
+TEST_F(LayerTreeHostCommonTest, ContentsScale) {
+ MockContentLayerClient delegate;
+ gfx::Transform identity_matrix;
+
+ gfx::Transform parent_scale_matrix;
+ double initial_parent_scale = 1.75;
+ parent_scale_matrix.Scale(initial_parent_scale, initial_parent_scale);
+
+ gfx::Transform child_scale_matrix;
+ double initial_child_scale = 1.25;
+ child_scale_matrix.Scale(initial_child_scale, initial_child_scale);
+
+ scoped_refptr<Layer> root = Layer::Create();
+ root->SetBounds(gfx::Size(100, 100));
+
+ scoped_refptr<ContentLayer> parent = CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(parent.get(),
+ parent_scale_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ true);
+
+ scoped_refptr<ContentLayer> child_scale =
+ CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(child_scale.get(),
+ child_scale_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(2.f, 2.f),
+ gfx::Size(10, 10),
+ true);
+
+ scoped_refptr<ContentLayer> child_empty =
+ CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(child_empty.get(),
+ child_scale_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(2.f, 2.f),
+ gfx::Size(),
+ true);
+
+ scoped_refptr<NoScaleContentLayer> child_no_scale =
+ CreateNoScaleDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(child_no_scale.get(),
+ child_scale_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(12.f, 12.f),
+ gfx::Size(10, 10),
+ true);
+
+ root->AddChild(parent);
+
+ parent->AddChild(child_scale);
+ parent->AddChild(child_empty);
+ parent->AddChild(child_no_scale);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ float device_scale_factor = 2.5f;
+ float page_scale_factor = 1.f;
+
+ {
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.device_scale_factor = device_scale_factor;
+ inputs.page_scale_factor = page_scale_factor;
+ inputs.page_scale_application_layer = root.get();
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor *
+ initial_parent_scale, parent);
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor *
+ initial_parent_scale * initial_child_scale,
+ child_scale);
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor *
+ initial_parent_scale * initial_child_scale,
+ child_empty);
+ EXPECT_CONTENTS_SCALE_EQ(1, child_no_scale);
+
+ // The parent is scaled up and shouldn't need to scale during draw. The
+ // child that can scale its contents should also not need to scale during
+ // draw. This shouldn't change if the child has empty bounds. The other
+ // children should.
+ EXPECT_FLOAT_EQ(1.0, parent->draw_transform().matrix().getDouble(0, 0));
+ EXPECT_FLOAT_EQ(1.0, parent->draw_transform().matrix().getDouble(1, 1));
+ EXPECT_FLOAT_EQ(1.0,
+ child_scale->draw_transform().matrix().getDouble(0, 0));
+ EXPECT_FLOAT_EQ(1.0,
+ child_scale->draw_transform().matrix().getDouble(1, 1));
+ EXPECT_FLOAT_EQ(1.0,
+ child_empty->draw_transform().matrix().getDouble(0, 0));
+ EXPECT_FLOAT_EQ(1.0,
+ child_empty->draw_transform().matrix().getDouble(1, 1));
+ EXPECT_FLOAT_EQ(device_scale_factor * page_scale_factor *
+ initial_parent_scale * initial_child_scale,
+ child_no_scale->draw_transform().matrix().getDouble(0, 0));
+ EXPECT_FLOAT_EQ(device_scale_factor * page_scale_factor *
+ initial_parent_scale * initial_child_scale,
+ child_no_scale->draw_transform().matrix().getDouble(1, 1));
+ }
+
+ // If the device_scale_factor or page_scale_factor changes, then it should be
+ // updated using the initial transform as the raster scale.
+ device_scale_factor = 2.25f;
+ page_scale_factor = 1.25f;
+
+ {
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.device_scale_factor = device_scale_factor;
+ inputs.page_scale_factor = page_scale_factor;
+ inputs.page_scale_application_layer = root.get();
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ EXPECT_CONTENTS_SCALE_EQ(
+ device_scale_factor * page_scale_factor * initial_parent_scale, parent);
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor *
+ initial_parent_scale * initial_child_scale,
+ child_scale);
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor *
+ initial_parent_scale * initial_child_scale,
+ child_empty);
+ EXPECT_CONTENTS_SCALE_EQ(1, child_no_scale);
+ }
+
+ // If the transform changes, we expect the raster scale to be reset to 1.0.
+ double second_child_scale = 1.75;
+ child_scale_matrix.Scale(second_child_scale / initial_child_scale,
+ second_child_scale / initial_child_scale);
+ child_scale->SetTransform(child_scale_matrix);
+ child_empty->SetTransform(child_scale_matrix);
+
+ {
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.device_scale_factor = device_scale_factor;
+ inputs.page_scale_factor = page_scale_factor;
+ inputs.page_scale_application_layer = root.get();
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor *
+ initial_parent_scale,
+ parent);
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor,
+ child_scale);
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor,
+ child_empty);
+ EXPECT_CONTENTS_SCALE_EQ(1, child_no_scale);
+ }
+
+ // If the device_scale_factor or page_scale_factor changes, then it should be
+ // updated, but still using 1.0 as the raster scale.
+ device_scale_factor = 2.75f;
+ page_scale_factor = 1.75f;
+
+ {
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.device_scale_factor = device_scale_factor;
+ inputs.page_scale_factor = page_scale_factor;
+ inputs.page_scale_application_layer = root.get();
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor *
+ initial_parent_scale,
+ parent);
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor,
+ child_scale);
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor,
+ child_empty);
+ EXPECT_CONTENTS_SCALE_EQ(1, child_no_scale);
+ }
+}
+
+TEST_F(LayerTreeHostCommonTest,
+ ContentsScale_LayerTransformsDontAffectContentsScale) {
+ MockContentLayerClient delegate;
+ gfx::Transform identity_matrix;
+
+ gfx::Transform parent_scale_matrix;
+ double initial_parent_scale = 1.75;
+ parent_scale_matrix.Scale(initial_parent_scale, initial_parent_scale);
+
+ gfx::Transform child_scale_matrix;
+ double initial_child_scale = 1.25;
+ child_scale_matrix.Scale(initial_child_scale, initial_child_scale);
+
+ scoped_refptr<Layer> root = Layer::Create();
+ root->SetBounds(gfx::Size(100, 100));
+
+ scoped_refptr<ContentLayer> parent = CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(parent.get(),
+ parent_scale_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ true);
+
+ scoped_refptr<ContentLayer> child_scale =
+ CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(child_scale.get(),
+ child_scale_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(2.f, 2.f),
+ gfx::Size(10, 10),
+ true);
+
+ scoped_refptr<ContentLayer> child_empty =
+ CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(child_empty.get(),
+ child_scale_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(2.f, 2.f),
+ gfx::Size(),
+ true);
+
+ scoped_refptr<NoScaleContentLayer> child_no_scale =
+ CreateNoScaleDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(child_no_scale.get(),
+ child_scale_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(12.f, 12.f),
+ gfx::Size(10, 10),
+ true);
+
+ root->AddChild(parent);
+
+ parent->AddChild(child_scale);
+ parent->AddChild(child_empty);
+ parent->AddChild(child_no_scale);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ RenderSurfaceLayerList render_surface_layer_list;
+
+ float device_scale_factor = 2.5f;
+ float page_scale_factor = 1.f;
+
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.device_scale_factor = device_scale_factor;
+ inputs.page_scale_factor = page_scale_factor;
+ inputs.page_scale_application_layer = root.get(),
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor, parent);
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor,
+ child_scale);
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor,
+ child_empty);
+ EXPECT_CONTENTS_SCALE_EQ(1, child_no_scale);
+
+ // Since the transform scale does not affect contents scale, it should affect
+ // the draw transform instead.
+ EXPECT_FLOAT_EQ(initial_parent_scale,
+ parent->draw_transform().matrix().getDouble(0, 0));
+ EXPECT_FLOAT_EQ(initial_parent_scale,
+ parent->draw_transform().matrix().getDouble(1, 1));
+ EXPECT_FLOAT_EQ(initial_parent_scale * initial_child_scale,
+ child_scale->draw_transform().matrix().getDouble(0, 0));
+ EXPECT_FLOAT_EQ(initial_parent_scale * initial_child_scale,
+ child_scale->draw_transform().matrix().getDouble(1, 1));
+ EXPECT_FLOAT_EQ(initial_parent_scale * initial_child_scale,
+ child_empty->draw_transform().matrix().getDouble(0, 0));
+ EXPECT_FLOAT_EQ(initial_parent_scale * initial_child_scale,
+ child_empty->draw_transform().matrix().getDouble(1, 1));
+ EXPECT_FLOAT_EQ(device_scale_factor * page_scale_factor *
+ initial_parent_scale * initial_child_scale,
+ child_no_scale->draw_transform().matrix().getDouble(0, 0));
+ EXPECT_FLOAT_EQ(device_scale_factor * page_scale_factor *
+ initial_parent_scale * initial_child_scale,
+ child_no_scale->draw_transform().matrix().getDouble(1, 1));
+}
+
+TEST_F(LayerTreeHostCommonTest, SmallContentsScale) {
+ MockContentLayerClient delegate;
+ gfx::Transform identity_matrix;
+
+ gfx::Transform parent_scale_matrix;
+ double initial_parent_scale = 1.75;
+ parent_scale_matrix.Scale(initial_parent_scale, initial_parent_scale);
+
+ gfx::Transform child_scale_matrix;
+ double initial_child_scale = 0.25;
+ child_scale_matrix.Scale(initial_child_scale, initial_child_scale);
+
+ scoped_refptr<Layer> root = Layer::Create();
+ root->SetBounds(gfx::Size(100, 100));
+
+ scoped_refptr<ContentLayer> parent = CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(parent.get(),
+ parent_scale_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ true);
+
+ scoped_refptr<ContentLayer> child_scale =
+ CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(child_scale.get(),
+ child_scale_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(2.f, 2.f),
+ gfx::Size(10, 10),
+ true);
+
+ root->AddChild(parent);
+
+ parent->AddChild(child_scale);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ float device_scale_factor = 2.5f;
+ float page_scale_factor = 0.01f;
+
+ {
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.device_scale_factor = device_scale_factor;
+ inputs.page_scale_factor = page_scale_factor;
+ inputs.page_scale_application_layer = root.get();
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor *
+ initial_parent_scale,
+ parent);
+ // The child's scale is < 1, so we should not save and use that scale
+ // factor.
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor * 1,
+ child_scale);
+ }
+
+ // When chilld's total scale becomes >= 1, we should save and use that scale
+ // factor.
+ child_scale_matrix.MakeIdentity();
+ double final_child_scale = 0.75;
+ child_scale_matrix.Scale(final_child_scale, final_child_scale);
+ child_scale->SetTransform(child_scale_matrix);
+
+ {
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.device_scale_factor = device_scale_factor;
+ inputs.page_scale_factor = page_scale_factor;
+ inputs.page_scale_application_layer = root.get();
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor *
+ initial_parent_scale,
+ parent);
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor *
+ initial_parent_scale * final_child_scale,
+ child_scale);
+ }
+}
+
+TEST_F(LayerTreeHostCommonTest, ContentsScaleForSurfaces) {
+ MockContentLayerClient delegate;
+ gfx::Transform identity_matrix;
+
+ gfx::Transform parent_scale_matrix;
+ double initial_parent_scale = 2.0;
+ parent_scale_matrix.Scale(initial_parent_scale, initial_parent_scale);
+
+ gfx::Transform child_scale_matrix;
+ double initial_child_scale = 3.0;
+ child_scale_matrix.Scale(initial_child_scale, initial_child_scale);
+
+ scoped_refptr<Layer> root = Layer::Create();
+ root->SetBounds(gfx::Size(100, 100));
+
+ scoped_refptr<ContentLayer> parent = CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(parent.get(),
+ parent_scale_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ true);
+
+ scoped_refptr<ContentLayer> surface_scale =
+ CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(surface_scale.get(),
+ child_scale_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(2.f, 2.f),
+ gfx::Size(10, 10),
+ true);
+
+ scoped_refptr<ContentLayer> surface_scale_child_scale =
+ CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(surface_scale_child_scale.get(),
+ child_scale_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ true);
+
+ scoped_refptr<NoScaleContentLayer> surface_scale_child_no_scale =
+ CreateNoScaleDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(surface_scale_child_no_scale.get(),
+ child_scale_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ true);
+
+ scoped_refptr<NoScaleContentLayer> surface_no_scale =
+ CreateNoScaleDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(surface_no_scale.get(),
+ child_scale_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(12.f, 12.f),
+ gfx::Size(10, 10),
+ true);
+
+ scoped_refptr<ContentLayer> surface_no_scale_child_scale =
+ CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(surface_no_scale_child_scale.get(),
+ child_scale_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ true);
+
+ scoped_refptr<NoScaleContentLayer> surface_no_scale_child_no_scale =
+ CreateNoScaleDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(surface_no_scale_child_no_scale.get(),
+ child_scale_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ true);
+
+ root->AddChild(parent);
+
+ parent->AddChild(surface_scale);
+ parent->AddChild(surface_no_scale);
+
+ surface_scale->SetForceRenderSurface(true);
+ surface_scale->AddChild(surface_scale_child_scale);
+ surface_scale->AddChild(surface_scale_child_no_scale);
+
+ surface_no_scale->SetForceRenderSurface(true);
+ surface_no_scale->AddChild(surface_no_scale_child_scale);
+ surface_no_scale->AddChild(surface_no_scale_child_no_scale);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ double device_scale_factor = 5;
+ double page_scale_factor = 7;
+
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.device_scale_factor = device_scale_factor;
+ inputs.page_scale_factor = page_scale_factor;
+ inputs.page_scale_application_layer = root.get();
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ EXPECT_CONTENTS_SCALE_EQ(
+ device_scale_factor * page_scale_factor * initial_parent_scale, parent);
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor *
+ initial_parent_scale * initial_child_scale,
+ surface_scale);
+ EXPECT_CONTENTS_SCALE_EQ(1, surface_no_scale);
+ EXPECT_CONTENTS_SCALE_EQ(
+ device_scale_factor * page_scale_factor * initial_parent_scale *
+ initial_child_scale * initial_child_scale,
+ surface_scale_child_scale);
+ EXPECT_CONTENTS_SCALE_EQ(1, surface_scale_child_no_scale);
+ EXPECT_CONTENTS_SCALE_EQ(
+ device_scale_factor * page_scale_factor * initial_parent_scale *
+ initial_child_scale * initial_child_scale,
+ surface_no_scale_child_scale);
+ EXPECT_CONTENTS_SCALE_EQ(1, surface_no_scale_child_no_scale);
+
+ // The parent is scaled up and shouldn't need to scale during draw.
+ EXPECT_FLOAT_EQ(1.0, parent->draw_transform().matrix().getDouble(0, 0));
+ EXPECT_FLOAT_EQ(1.0, parent->draw_transform().matrix().getDouble(1, 1));
+
+ // RenderSurfaces should always be 1:1 with their target.
+ EXPECT_FLOAT_EQ(
+ 1.0,
+ surface_scale->render_surface()->draw_transform().matrix().getDouble(0,
+ 0));
+ EXPECT_FLOAT_EQ(
+ 1.0,
+ surface_scale->render_surface()->draw_transform().matrix().getDouble(1,
+ 1));
+
+ // The surface_scale can apply contents scale so the layer shouldn't need to
+ // scale during draw.
+ EXPECT_FLOAT_EQ(1.0,
+ surface_scale->draw_transform().matrix().getDouble(0, 0));
+ EXPECT_FLOAT_EQ(1.0,
+ surface_scale->draw_transform().matrix().getDouble(1, 1));
+
+ // The surface_scale_child_scale can apply contents scale so it shouldn't need
+ // to scale during draw.
+ EXPECT_FLOAT_EQ(
+ 1.0,
+ surface_scale_child_scale->draw_transform().matrix().getDouble(0, 0));
+ EXPECT_FLOAT_EQ(
+ 1.0,
+ surface_scale_child_scale->draw_transform().matrix().getDouble(1, 1));
+
+ // The surface_scale_child_no_scale can not apply contents scale, so it needs
+ // to be scaled during draw.
+ EXPECT_FLOAT_EQ(
+ device_scale_factor * page_scale_factor * initial_parent_scale *
+ initial_child_scale * initial_child_scale,
+ surface_scale_child_no_scale->draw_transform().matrix().getDouble(0, 0));
+ EXPECT_FLOAT_EQ(
+ device_scale_factor * page_scale_factor * initial_parent_scale *
+ initial_child_scale * initial_child_scale,
+ surface_scale_child_no_scale->draw_transform().matrix().getDouble(1, 1));
+
+ // RenderSurfaces should always be 1:1 with their target.
+ EXPECT_FLOAT_EQ(
+ 1.0,
+ surface_no_scale->render_surface()->draw_transform().matrix().getDouble(
+ 0, 0));
+ EXPECT_FLOAT_EQ(
+ 1.0,
+ surface_no_scale->render_surface()->draw_transform().matrix().getDouble(
+ 1, 1));
+
+ // The surface_no_scale layer can not apply contents scale, so it needs to be
+ // scaled during draw.
+ EXPECT_FLOAT_EQ(device_scale_factor * page_scale_factor *
+ initial_parent_scale * initial_child_scale,
+ surface_no_scale->draw_transform().matrix().getDouble(0, 0));
+ EXPECT_FLOAT_EQ(device_scale_factor * page_scale_factor *
+ initial_parent_scale * initial_child_scale,
+ surface_no_scale->draw_transform().matrix().getDouble(1, 1));
+
+ // The surface_scale_child_scale can apply contents scale so it shouldn't need
+ // to scale during draw.
+ EXPECT_FLOAT_EQ(
+ 1.0,
+ surface_no_scale_child_scale->draw_transform().matrix().getDouble(0, 0));
+ EXPECT_FLOAT_EQ(
+ 1.0,
+ surface_no_scale_child_scale->draw_transform().matrix().getDouble(1, 1));
+
+ // The surface_scale_child_no_scale can not apply contents scale, so it needs
+ // to be scaled during draw.
+ EXPECT_FLOAT_EQ(
+ device_scale_factor * page_scale_factor * initial_parent_scale *
+ initial_child_scale * initial_child_scale,
+ surface_no_scale_child_no_scale->draw_transform().matrix().getDouble(0,
+ 0));
+ EXPECT_FLOAT_EQ(
+ device_scale_factor * page_scale_factor * initial_parent_scale *
+ initial_child_scale * initial_child_scale,
+ surface_no_scale_child_no_scale->draw_transform().matrix().getDouble(1,
+ 1));
+}
+
+TEST_F(LayerTreeHostCommonTest,
+ ContentsScaleForSurfaces_LayerTransformsDontAffectContentsScale) {
+ MockContentLayerClient delegate;
+ gfx::Transform identity_matrix;
+
+ gfx::Transform parent_scale_matrix;
+ double initial_parent_scale = 2.0;
+ parent_scale_matrix.Scale(initial_parent_scale, initial_parent_scale);
+
+ gfx::Transform child_scale_matrix;
+ double initial_child_scale = 3.0;
+ child_scale_matrix.Scale(initial_child_scale, initial_child_scale);
+
+ scoped_refptr<Layer> root = Layer::Create();
+ root->SetBounds(gfx::Size(100, 100));
+
+ scoped_refptr<ContentLayer> parent = CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(parent.get(),
+ parent_scale_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ true);
+
+ scoped_refptr<ContentLayer> surface_scale =
+ CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(surface_scale.get(),
+ child_scale_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(2.f, 2.f),
+ gfx::Size(10, 10),
+ true);
+
+ scoped_refptr<ContentLayer> surface_scale_child_scale =
+ CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(surface_scale_child_scale.get(),
+ child_scale_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ true);
+
+ scoped_refptr<NoScaleContentLayer> surface_scale_child_no_scale =
+ CreateNoScaleDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(surface_scale_child_no_scale.get(),
+ child_scale_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ true);
+
+ scoped_refptr<NoScaleContentLayer> surface_no_scale =
+ CreateNoScaleDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(surface_no_scale.get(),
+ child_scale_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(12.f, 12.f),
+ gfx::Size(10, 10),
+ true);
+
+ scoped_refptr<ContentLayer> surface_no_scale_child_scale =
+ CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(surface_no_scale_child_scale.get(),
+ child_scale_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ true);
+
+ scoped_refptr<NoScaleContentLayer> surface_no_scale_child_no_scale =
+ CreateNoScaleDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(surface_no_scale_child_no_scale.get(),
+ child_scale_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ true);
+
+ root->AddChild(parent);
+
+ parent->AddChild(surface_scale);
+ parent->AddChild(surface_no_scale);
+
+ surface_scale->SetForceRenderSurface(true);
+ surface_scale->AddChild(surface_scale_child_scale);
+ surface_scale->AddChild(surface_scale_child_no_scale);
+
+ surface_no_scale->SetForceRenderSurface(true);
+ surface_no_scale->AddChild(surface_no_scale_child_scale);
+ surface_no_scale->AddChild(surface_no_scale_child_no_scale);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ RenderSurfaceLayerList render_surface_layer_list;
+
+ double device_scale_factor = 5.0;
+ double page_scale_factor = 7.0;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.device_scale_factor = device_scale_factor;
+ inputs.page_scale_factor = page_scale_factor;
+ inputs.page_scale_application_layer = root.get();
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor,
+ parent);
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor,
+ surface_scale);
+ EXPECT_CONTENTS_SCALE_EQ(1.f, surface_no_scale);
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor,
+ surface_scale_child_scale);
+ EXPECT_CONTENTS_SCALE_EQ(1.f, surface_scale_child_no_scale);
+ EXPECT_CONTENTS_SCALE_EQ(device_scale_factor * page_scale_factor,
+ surface_no_scale_child_scale);
+ EXPECT_CONTENTS_SCALE_EQ(1.f, surface_no_scale_child_no_scale);
+
+ // The parent is scaled up during draw, since its contents are not scaled by
+ // the transform hierarchy.
+ EXPECT_FLOAT_EQ(initial_parent_scale,
+ parent->draw_transform().matrix().getDouble(0, 0));
+ EXPECT_FLOAT_EQ(initial_parent_scale,
+ parent->draw_transform().matrix().getDouble(1, 1));
+
+ // The child surface is scaled up during draw since its subtree is not scaled
+ // by the transform hierarchy.
+ EXPECT_FLOAT_EQ(
+ initial_parent_scale * initial_child_scale,
+ surface_scale->render_surface()->draw_transform().matrix().getDouble(0,
+ 0));
+ EXPECT_FLOAT_EQ(
+ initial_parent_scale * initial_child_scale,
+ surface_scale->render_surface()->draw_transform().matrix().getDouble(1,
+ 1));
+
+ // The surface_scale's RenderSurface is scaled during draw, so the layer does
+ // not need to be scaled when drawing into its surface.
+ EXPECT_FLOAT_EQ(1.0,
+ surface_scale->draw_transform().matrix().getDouble(0, 0));
+ EXPECT_FLOAT_EQ(1.0,
+ surface_scale->draw_transform().matrix().getDouble(1, 1));
+
+ // The surface_scale_child_scale is scaled when drawing into its surface,
+ // since its content bounds are not scaled by the transform hierarchy.
+ EXPECT_FLOAT_EQ(
+ initial_child_scale,
+ surface_scale_child_scale->draw_transform().matrix().getDouble(0, 0));
+ EXPECT_FLOAT_EQ(
+ initial_child_scale,
+ surface_scale_child_scale->draw_transform().matrix().getDouble(1, 1));
+
+ // The surface_scale_child_no_scale has a fixed contents scale of 1, so it
+ // needs to be scaled by the device and page scale factors, along with the
+ // transform hierarchy.
+ EXPECT_FLOAT_EQ(
+ device_scale_factor * page_scale_factor * initial_child_scale,
+ surface_scale_child_no_scale->draw_transform().matrix().getDouble(0, 0));
+ EXPECT_FLOAT_EQ(
+ device_scale_factor * page_scale_factor * initial_child_scale,
+ surface_scale_child_no_scale->draw_transform().matrix().getDouble(1, 1));
+
+ // The child surface is scaled up during draw since its subtree is not scaled
+ // by the transform hierarchy.
+ EXPECT_FLOAT_EQ(
+ initial_parent_scale * initial_child_scale,
+ surface_no_scale->render_surface()->draw_transform().matrix().getDouble(
+ 0, 0));
+ EXPECT_FLOAT_EQ(
+ initial_parent_scale * initial_child_scale,
+ surface_no_scale->render_surface()->draw_transform().matrix().getDouble(
+ 1, 1));
+
+ // The surface_no_scale layer has a fixed contents scale of 1, so it needs to
+ // be scaled by the device and page scale factors. Its surface is already
+ // scaled by the transform hierarchy so those don't need to scale the layer's
+ // drawing.
+ EXPECT_FLOAT_EQ(device_scale_factor * page_scale_factor,
+ surface_no_scale->draw_transform().matrix().getDouble(0, 0));
+ EXPECT_FLOAT_EQ(device_scale_factor * page_scale_factor,
+ surface_no_scale->draw_transform().matrix().getDouble(1, 1));
+
+ // The surface_no_scale_child_scale has its contents scaled by the page and
+ // device scale factors, but needs to be scaled by the transform hierarchy
+ // when drawing.
+ EXPECT_FLOAT_EQ(
+ initial_child_scale,
+ surface_no_scale_child_scale->draw_transform().matrix().getDouble(0, 0));
+ EXPECT_FLOAT_EQ(
+ initial_child_scale,
+ surface_no_scale_child_scale->draw_transform().matrix().getDouble(1, 1));
+
+ // The surface_no_scale_child_no_scale has a fixed contents scale of 1, so it
+ // needs to be scaled by the device and page scale factors. It also needs to
+ // be scaled by any transform heirarchy below its target surface.
+ EXPECT_FLOAT_EQ(
+ device_scale_factor * page_scale_factor * initial_child_scale,
+ surface_no_scale_child_no_scale->draw_transform().matrix().getDouble(0,
+ 0));
+ EXPECT_FLOAT_EQ(
+ device_scale_factor * page_scale_factor * initial_child_scale,
+ surface_no_scale_child_no_scale->draw_transform().matrix().getDouble(1,
+ 1));
+}
+
+TEST_F(LayerTreeHostCommonTest, ContentsScaleForAnimatingLayer) {
+ MockContentLayerClient delegate;
+ gfx::Transform identity_matrix;
+
+ gfx::Transform parent_scale_matrix;
+ double initial_parent_scale = 1.75;
+ parent_scale_matrix.Scale(initial_parent_scale, initial_parent_scale);
+
+ gfx::Transform child_scale_matrix;
+ double initial_child_scale = 1.25;
+ child_scale_matrix.Scale(initial_child_scale, initial_child_scale);
+
+ scoped_refptr<Layer> root = Layer::Create();
+ root->SetBounds(gfx::Size(100, 100));
+
+ scoped_refptr<ContentLayer> parent = CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(parent.get(),
+ parent_scale_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ true);
+
+ scoped_refptr<ContentLayer> child_scale =
+ CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(child_scale.get(),
+ child_scale_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(2.f, 2.f),
+ gfx::Size(10, 10),
+ true);
+
+ root->AddChild(parent);
+
+ parent->AddChild(child_scale);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ // Now put an animating transform on child.
+ int animation_id = AddAnimatedTransformToController(
+ child_scale->layer_animation_controller(), 10.0, 30, 0);
+
+ {
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ EXPECT_CONTENTS_SCALE_EQ(initial_parent_scale, parent);
+ // The layers with animating transforms should not compute a contents scale
+ // other than 1 until they finish animating.
+ EXPECT_CONTENTS_SCALE_EQ(1, child_scale);
+ }
+
+ // Remove the animation, now it can save a raster scale.
+ child_scale->layer_animation_controller()->RemoveAnimation(animation_id);
+
+ {
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ EXPECT_CONTENTS_SCALE_EQ(initial_parent_scale, parent);
+ // The layers with animating transforms should not compute a contents scale
+ // other than 1 until they finish animating.
+ EXPECT_CONTENTS_SCALE_EQ(initial_parent_scale * initial_child_scale,
+ child_scale);
+ }
+}
+
+TEST_F(LayerTreeHostCommonTest, RenderSurfaceTransformsInHighDPI) {
+ MockContentLayerClient delegate;
+ gfx::Transform identity_matrix;
+
+ scoped_refptr<ContentLayer> parent = CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(parent.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(30, 30),
+ true);
+
+ scoped_refptr<ContentLayer> child = CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(2.f, 2.f),
+ gfx::Size(10, 10),
+ true);
+
+ gfx::Transform replica_transform;
+ replica_transform.Scale(1.0, -1.0);
+ scoped_refptr<ContentLayer> replica = CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(replica.get(),
+ replica_transform,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(2.f, 2.f),
+ gfx::Size(10, 10),
+ true);
+
+ // This layer should end up in the same surface as child, with the same draw
+ // and screen space transforms.
+ scoped_refptr<ContentLayer> duplicate_child_non_owner =
+ CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(duplicate_child_non_owner.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ true);
+
+ parent->AddChild(child);
+ child->AddChild(duplicate_child_non_owner);
+ child->SetReplicaLayer(replica.get());
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(parent);
+
+ RenderSurfaceLayerList render_surface_layer_list;
+
+ float device_scale_factor = 1.5f;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ parent.get(), parent->bounds(), &render_surface_layer_list);
+ inputs.device_scale_factor = device_scale_factor;
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // We should have two render surfaces. The root's render surface and child's
+ // render surface (it needs one because it has a replica layer).
+ EXPECT_EQ(2u, render_surface_layer_list.size());
+
+ gfx::Transform expected_parent_transform;
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_parent_transform,
+ parent->screen_space_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_parent_transform,
+ parent->draw_transform());
+
+ gfx::Transform expected_draw_transform;
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_draw_transform,
+ child->draw_transform());
+
+ gfx::Transform expected_screen_space_transform;
+ expected_screen_space_transform.Translate(
+ device_scale_factor * child->position().x(),
+ device_scale_factor * child->position().y());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_screen_space_transform,
+ child->screen_space_transform());
+
+ gfx::Transform expected_duplicate_child_draw_transform =
+ child->draw_transform();
+ EXPECT_TRANSFORMATION_MATRIX_EQ(child->draw_transform(),
+ duplicate_child_non_owner->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ child->screen_space_transform(),
+ duplicate_child_non_owner->screen_space_transform());
+ EXPECT_RECT_EQ(child->drawable_content_rect(),
+ duplicate_child_non_owner->drawable_content_rect());
+ EXPECT_EQ(child->content_bounds(),
+ duplicate_child_non_owner->content_bounds());
+
+ gfx::Transform expected_render_surface_draw_transform;
+ expected_render_surface_draw_transform.Translate(
+ device_scale_factor * child->position().x(),
+ device_scale_factor * child->position().y());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_render_surface_draw_transform,
+ child->render_surface()->draw_transform());
+
+ gfx::Transform expected_surface_draw_transform;
+ expected_surface_draw_transform.Translate(device_scale_factor * 2.f,
+ device_scale_factor * 2.f);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(expected_surface_draw_transform,
+ child->render_surface()->draw_transform());
+
+ gfx::Transform expected_surface_screen_space_transform;
+ expected_surface_screen_space_transform.Translate(device_scale_factor * 2.f,
+ device_scale_factor * 2.f);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected_surface_screen_space_transform,
+ child->render_surface()->screen_space_transform());
+
+ gfx::Transform expected_replica_draw_transform;
+ expected_replica_draw_transform.matrix().setDouble(1, 1, -1.0);
+ expected_replica_draw_transform.matrix().setDouble(0, 3, 6.0);
+ expected_replica_draw_transform.matrix().setDouble(1, 3, 6.0);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected_replica_draw_transform,
+ child->render_surface()->replica_draw_transform());
+
+ gfx::Transform expected_replica_screen_space_transform;
+ expected_replica_screen_space_transform.matrix().setDouble(1, 1, -1.0);
+ expected_replica_screen_space_transform.matrix().setDouble(0, 3, 6.0);
+ expected_replica_screen_space_transform.matrix().setDouble(1, 3, 6.0);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected_replica_screen_space_transform,
+ child->render_surface()->replica_screen_space_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected_replica_screen_space_transform,
+ child->render_surface()->replica_screen_space_transform());
+}
+
+TEST_F(LayerTreeHostCommonTest,
+ RenderSurfaceTransformsInHighDPIAccurateScaleZeroPosition) {
+ MockContentLayerClient delegate;
+ gfx::Transform identity_matrix;
+
+ scoped_refptr<ContentLayer> parent = CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(parent.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(33, 31),
+ true);
+
+ scoped_refptr<ContentLayer> child = CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(13, 11),
+ true);
+
+ gfx::Transform replica_transform;
+ replica_transform.Scale(1.0, -1.0);
+ scoped_refptr<ContentLayer> replica = CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(replica.get(),
+ replica_transform,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(13, 11),
+ true);
+
+ // This layer should end up in the same surface as child, with the same draw
+ // and screen space transforms.
+ scoped_refptr<ContentLayer> duplicate_child_non_owner =
+ CreateDrawableContentLayer(&delegate);
+ SetLayerPropertiesForTesting(duplicate_child_non_owner.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(13, 11),
+ true);
+
+ parent->AddChild(child);
+ child->AddChild(duplicate_child_non_owner);
+ child->SetReplicaLayer(replica.get());
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(parent);
+
+ float device_scale_factor = 1.7f;
+
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ parent.get(), parent->bounds(), &render_surface_layer_list);
+ inputs.device_scale_factor = device_scale_factor;
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // We should have two render surfaces. The root's render surface and child's
+ // render surface (it needs one because it has a replica layer).
+ EXPECT_EQ(2u, render_surface_layer_list.size());
+
+ gfx::Transform identity_transform;
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(identity_transform,
+ parent->screen_space_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(identity_transform, parent->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(identity_transform, child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(identity_transform,
+ child->screen_space_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(identity_transform,
+ duplicate_child_non_owner->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ identity_transform, duplicate_child_non_owner->screen_space_transform());
+ EXPECT_RECT_EQ(child->drawable_content_rect(),
+ duplicate_child_non_owner->drawable_content_rect());
+ EXPECT_EQ(child->content_bounds(),
+ duplicate_child_non_owner->content_bounds());
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(identity_transform,
+ child->render_surface()->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(identity_transform,
+ child->render_surface()->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ identity_transform, child->render_surface()->screen_space_transform());
+
+ gfx::Transform expected_replica_draw_transform;
+ expected_replica_draw_transform.matrix().setDouble(1, 1, -1.0);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected_replica_draw_transform,
+ child->render_surface()->replica_draw_transform());
+
+ gfx::Transform expected_replica_screen_space_transform;
+ expected_replica_screen_space_transform.matrix().setDouble(1, 1, -1.0);
+ EXPECT_TRANSFORMATION_MATRIX_EQ(
+ expected_replica_screen_space_transform,
+ child->render_surface()->replica_screen_space_transform());
+}
+
+TEST_F(LayerTreeHostCommonTest, SubtreeSearch) {
+ scoped_refptr<Layer> root = Layer::Create();
+ scoped_refptr<Layer> child = Layer::Create();
+ scoped_refptr<Layer> grand_child = Layer::Create();
+ scoped_refptr<Layer> mask_layer = Layer::Create();
+ scoped_refptr<Layer> replica_layer = Layer::Create();
+
+ grand_child->SetReplicaLayer(replica_layer.get());
+ child->AddChild(grand_child.get());
+ child->SetMaskLayer(mask_layer.get());
+ root->AddChild(child.get());
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ int nonexistent_id = -1;
+ EXPECT_EQ(root,
+ LayerTreeHostCommon::FindLayerInSubtree(root.get(), root->id()));
+ EXPECT_EQ(child,
+ LayerTreeHostCommon::FindLayerInSubtree(root.get(), child->id()));
+ EXPECT_EQ(
+ grand_child,
+ LayerTreeHostCommon::FindLayerInSubtree(root.get(), grand_child->id()));
+ EXPECT_EQ(
+ mask_layer,
+ LayerTreeHostCommon::FindLayerInSubtree(root.get(), mask_layer->id()));
+ EXPECT_EQ(
+ replica_layer,
+ LayerTreeHostCommon::FindLayerInSubtree(root.get(), replica_layer->id()));
+ EXPECT_EQ(
+ 0, LayerTreeHostCommon::FindLayerInSubtree(root.get(), nonexistent_id));
+}
+
+TEST_F(LayerTreeHostCommonTest, TransparentChildRenderSurfaceCreation) {
+ scoped_refptr<Layer> root = Layer::Create();
+ scoped_refptr<Layer> child = Layer::Create();
+ scoped_refptr<LayerWithForcedDrawsContent> grand_child =
+ make_scoped_refptr(new LayerWithForcedDrawsContent());
+
+ const gfx::Transform identity_matrix;
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+ SetLayerPropertiesForTesting(grand_child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(10, 10),
+ false);
+
+ root->AddChild(child);
+ child->AddChild(grand_child);
+ child->SetOpacity(0.5f);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ ExecuteCalculateDrawProperties(root.get());
+
+ EXPECT_FALSE(child->render_surface());
+}
+
+TEST_F(LayerTreeHostCommonTest, OpacityAnimatingOnPendingTree) {
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ host_impl.CreatePendingTree();
+ scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl.pending_tree(), 1);
+
+ const gfx::Transform identity_matrix;
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ root->SetDrawsContent(true);
+
+ scoped_ptr<LayerImpl> child = LayerImpl::Create(host_impl.pending_tree(), 2);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(50, 50),
+ false);
+ child->SetDrawsContent(true);
+ child->SetOpacity(0.0f);
+
+ // Add opacity animation.
+ AddOpacityTransitionToController(
+ child->layer_animation_controller(), 10.0, 0.0f, 1.0f, false);
+
+ root->AddChild(child.Pass());
+
+ LayerImplList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // We should have one render surface and two layers. The child
+ // layer should be included even though it is transparent.
+ ASSERT_EQ(1u, render_surface_layer_list.size());
+ ASSERT_EQ(2u, root->render_surface()->layer_list().size());
+}
+
+typedef std::tr1::tuple<bool, bool> LCDTextTestParam;
+class LCDTextTest
+ : public LayerTreeHostCommonTestBase,
+ public testing::TestWithParam<LCDTextTestParam> {
+ protected:
+ virtual void SetUp() {
+ can_use_lcd_text_ = std::tr1::get<0>(GetParam());
+
+ root_ = Layer::Create();
+ child_ = Layer::Create();
+ grand_child_ = Layer::Create();
+ child_->AddChild(grand_child_.get());
+ root_->AddChild(child_.get());
+
+ gfx::Transform identity_matrix;
+ SetLayerPropertiesForTesting(root_.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(1, 1),
+ false);
+ SetLayerPropertiesForTesting(child_.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(1, 1),
+ false);
+ SetLayerPropertiesForTesting(grand_child_.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(1, 1),
+ false);
+
+ child_->SetForceRenderSurface(std::tr1::get<1>(GetParam()));
+
+ host_ = FakeLayerTreeHost::Create();
+ host_->SetRootLayer(root_);
+ }
+
+ bool can_use_lcd_text_;
+ scoped_ptr<FakeLayerTreeHost> host_;
+ scoped_refptr<Layer> root_;
+ scoped_refptr<Layer> child_;
+ scoped_refptr<Layer> grand_child_;
+};
+
+TEST_P(LCDTextTest, CanUseLCDText) {
+ // Case 1: Identity transform.
+ gfx::Transform identity_matrix;
+ ExecuteCalculateDrawProperties(
+ root_.get(), 1.f, 1.f, NULL, can_use_lcd_text_);
+ EXPECT_EQ(can_use_lcd_text_, root_->can_use_lcd_text());
+ EXPECT_EQ(can_use_lcd_text_, child_->can_use_lcd_text());
+ EXPECT_EQ(can_use_lcd_text_, grand_child_->can_use_lcd_text());
+
+ // Case 2: Integral translation.
+ gfx::Transform integral_translation;
+ integral_translation.Translate(1.0, 2.0);
+ child_->SetTransform(integral_translation);
+ ExecuteCalculateDrawProperties(
+ root_.get(), 1.f, 1.f, NULL, can_use_lcd_text_);
+ EXPECT_EQ(can_use_lcd_text_, root_->can_use_lcd_text());
+ EXPECT_EQ(can_use_lcd_text_, child_->can_use_lcd_text());
+ EXPECT_EQ(can_use_lcd_text_, grand_child_->can_use_lcd_text());
+
+ // Case 3: Non-integral translation.
+ gfx::Transform non_integral_translation;
+ non_integral_translation.Translate(1.5, 2.5);
+ child_->SetTransform(non_integral_translation);
+ ExecuteCalculateDrawProperties(
+ root_.get(), 1.f, 1.f, NULL, can_use_lcd_text_);
+ EXPECT_EQ(can_use_lcd_text_, root_->can_use_lcd_text());
+ EXPECT_FALSE(child_->can_use_lcd_text());
+ EXPECT_FALSE(grand_child_->can_use_lcd_text());
+
+ // Case 4: Rotation.
+ gfx::Transform rotation;
+ rotation.Rotate(10.0);
+ child_->SetTransform(rotation);
+ ExecuteCalculateDrawProperties(
+ root_.get(), 1.f, 1.f, NULL, can_use_lcd_text_);
+ EXPECT_EQ(can_use_lcd_text_, root_->can_use_lcd_text());
+ EXPECT_FALSE(child_->can_use_lcd_text());
+ EXPECT_FALSE(grand_child_->can_use_lcd_text());
+
+ // Case 5: Scale.
+ gfx::Transform scale;
+ scale.Scale(2.0, 2.0);
+ child_->SetTransform(scale);
+ ExecuteCalculateDrawProperties(
+ root_.get(), 1.f, 1.f, NULL, can_use_lcd_text_);
+ EXPECT_EQ(can_use_lcd_text_, root_->can_use_lcd_text());
+ EXPECT_FALSE(child_->can_use_lcd_text());
+ EXPECT_FALSE(grand_child_->can_use_lcd_text());
+
+ // Case 6: Skew.
+ gfx::Transform skew;
+ skew.SkewX(10.0);
+ child_->SetTransform(skew);
+ ExecuteCalculateDrawProperties(
+ root_.get(), 1.f, 1.f, NULL, can_use_lcd_text_);
+ EXPECT_EQ(can_use_lcd_text_, root_->can_use_lcd_text());
+ EXPECT_FALSE(child_->can_use_lcd_text());
+ EXPECT_FALSE(grand_child_->can_use_lcd_text());
+
+ // Case 7: Translucent.
+ child_->SetTransform(identity_matrix);
+ child_->SetOpacity(0.5f);
+ ExecuteCalculateDrawProperties(
+ root_.get(), 1.f, 1.f, NULL, can_use_lcd_text_);
+ EXPECT_EQ(can_use_lcd_text_, root_->can_use_lcd_text());
+ EXPECT_FALSE(child_->can_use_lcd_text());
+ EXPECT_FALSE(grand_child_->can_use_lcd_text());
+
+ // Case 8: Sanity check: restore transform and opacity.
+ child_->SetTransform(identity_matrix);
+ child_->SetOpacity(1.f);
+ ExecuteCalculateDrawProperties(
+ root_.get(), 1.f, 1.f, NULL, can_use_lcd_text_);
+ EXPECT_EQ(can_use_lcd_text_, root_->can_use_lcd_text());
+ EXPECT_EQ(can_use_lcd_text_, child_->can_use_lcd_text());
+ EXPECT_EQ(can_use_lcd_text_, grand_child_->can_use_lcd_text());
+}
+
+TEST_P(LCDTextTest, CanUseLCDTextWithAnimation) {
+ // Sanity check: Make sure can_use_lcd_text_ is set on each node.
+ ExecuteCalculateDrawProperties(
+ root_.get(), 1.f, 1.f, NULL, can_use_lcd_text_);
+ EXPECT_EQ(can_use_lcd_text_, root_->can_use_lcd_text());
+ EXPECT_EQ(can_use_lcd_text_, child_->can_use_lcd_text());
+ EXPECT_EQ(can_use_lcd_text_, grand_child_->can_use_lcd_text());
+
+ // Add opacity animation.
+ child_->SetOpacity(0.9f);
+ AddOpacityTransitionToController(
+ child_->layer_animation_controller(), 10.0, 0.9f, 0.1f, false);
+
+ ExecuteCalculateDrawProperties(
+ root_.get(), 1.f, 1.f, NULL, can_use_lcd_text_);
+ // Text AA should not be adjusted while animation is active.
+ // Make sure LCD text AA setting remains unchanged.
+ EXPECT_EQ(can_use_lcd_text_, root_->can_use_lcd_text());
+ EXPECT_EQ(can_use_lcd_text_, child_->can_use_lcd_text());
+ EXPECT_EQ(can_use_lcd_text_, grand_child_->can_use_lcd_text());
+}
+
+INSTANTIATE_TEST_CASE_P(LayerTreeHostCommonTest,
+ LCDTextTest,
+ testing::Combine(testing::Bool(), testing::Bool()));
+
+TEST_F(LayerTreeHostCommonTest, SubtreeHidden_SingleLayer) {
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ host_impl.CreatePendingTree();
+ const gfx::Transform identity_matrix;
+
+ scoped_refptr<Layer> root = Layer::Create();
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(50, 50),
+ false);
+ root->SetIsDrawable(true);
+
+ scoped_refptr<Layer> child = Layer::Create();
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(40, 40),
+ false);
+ child->SetIsDrawable(true);
+
+ scoped_refptr<Layer> grand_child = Layer::Create();
+ SetLayerPropertiesForTesting(grand_child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(30, 30),
+ false);
+ grand_child->SetIsDrawable(true);
+ grand_child->SetHideLayerAndSubtree(true);
+
+ child->AddChild(grand_child);
+ root->AddChild(child);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // We should have one render surface and two layers. The grand child has
+ // hidden itself.
+ ASSERT_EQ(1u, render_surface_layer_list.size());
+ ASSERT_EQ(2u, root->render_surface()->layer_list().size());
+ EXPECT_EQ(root->id(), root->render_surface()->layer_list().at(0)->id());
+ EXPECT_EQ(child->id(), root->render_surface()->layer_list().at(1)->id());
+}
+
+TEST_F(LayerTreeHostCommonTest, SubtreeHidden_SingleLayerImpl) {
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ host_impl.CreatePendingTree();
+ const gfx::Transform identity_matrix;
+
+ scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl.pending_tree(), 1);
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(50, 50),
+ false);
+ root->SetDrawsContent(true);
+
+ scoped_ptr<LayerImpl> child = LayerImpl::Create(host_impl.pending_tree(), 2);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(40, 40),
+ false);
+ child->SetDrawsContent(true);
+
+ scoped_ptr<LayerImpl> grand_child =
+ LayerImpl::Create(host_impl.pending_tree(), 3);
+ SetLayerPropertiesForTesting(grand_child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(30, 30),
+ false);
+ grand_child->SetDrawsContent(true);
+ grand_child->SetHideLayerAndSubtree(true);
+
+ child->AddChild(grand_child.Pass());
+ root->AddChild(child.Pass());
+
+ LayerImplList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // We should have one render surface and two layers. The grand child has
+ // hidden itself.
+ ASSERT_EQ(1u, render_surface_layer_list.size());
+ ASSERT_EQ(2u, root->render_surface()->layer_list().size());
+ EXPECT_EQ(1, root->render_surface()->layer_list().at(0)->id());
+ EXPECT_EQ(2, root->render_surface()->layer_list().at(1)->id());
+}
+
+TEST_F(LayerTreeHostCommonTest, SubtreeHidden_TwoLayers) {
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ host_impl.CreatePendingTree();
+ const gfx::Transform identity_matrix;
+
+ scoped_refptr<Layer> root = Layer::Create();
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(50, 50),
+ false);
+ root->SetIsDrawable(true);
+
+ scoped_refptr<Layer> child = Layer::Create();
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(40, 40),
+ false);
+ child->SetIsDrawable(true);
+ child->SetHideLayerAndSubtree(true);
+
+ scoped_refptr<Layer> grand_child = Layer::Create();
+ SetLayerPropertiesForTesting(grand_child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(30, 30),
+ false);
+ grand_child->SetIsDrawable(true);
+
+ child->AddChild(grand_child);
+ root->AddChild(child);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // We should have one render surface and one layers. The child has
+ // hidden itself and the grand child.
+ ASSERT_EQ(1u, render_surface_layer_list.size());
+ ASSERT_EQ(1u, root->render_surface()->layer_list().size());
+ EXPECT_EQ(root->id(), root->render_surface()->layer_list().at(0)->id());
+}
+
+TEST_F(LayerTreeHostCommonTest, SubtreeHidden_TwoLayersImpl) {
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ host_impl.CreatePendingTree();
+ const gfx::Transform identity_matrix;
+
+ scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl.pending_tree(), 1);
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(50, 50),
+ false);
+ root->SetDrawsContent(true);
+
+ scoped_ptr<LayerImpl> child = LayerImpl::Create(host_impl.pending_tree(), 2);
+ SetLayerPropertiesForTesting(child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(40, 40),
+ false);
+ child->SetDrawsContent(true);
+ child->SetHideLayerAndSubtree(true);
+
+ scoped_ptr<LayerImpl> grand_child =
+ LayerImpl::Create(host_impl.pending_tree(), 3);
+ SetLayerPropertiesForTesting(grand_child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(30, 30),
+ false);
+ grand_child->SetDrawsContent(true);
+
+ child->AddChild(grand_child.Pass());
+ root->AddChild(child.Pass());
+
+ LayerImplList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // We should have one render surface and one layers. The child has
+ // hidden itself and the grand child.
+ ASSERT_EQ(1u, render_surface_layer_list.size());
+ ASSERT_EQ(1u, root->render_surface()->layer_list().size());
+ EXPECT_EQ(1, root->render_surface()->layer_list().at(0)->id());
+}
+
+void EmptyCopyOutputCallback(scoped_ptr<CopyOutputResult> result) {}
+
+TEST_F(LayerTreeHostCommonTest, SubtreeHiddenWithCopyRequest) {
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ host_impl.CreatePendingTree();
+ const gfx::Transform identity_matrix;
+
+ scoped_refptr<Layer> root = Layer::Create();
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(50, 50),
+ false);
+ root->SetIsDrawable(true);
+
+ scoped_refptr<Layer> copy_grand_parent = Layer::Create();
+ SetLayerPropertiesForTesting(copy_grand_parent.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(40, 40),
+ false);
+ copy_grand_parent->SetIsDrawable(true);
+
+ scoped_refptr<Layer> copy_parent = Layer::Create();
+ SetLayerPropertiesForTesting(copy_parent.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(30, 30),
+ false);
+ copy_parent->SetIsDrawable(true);
+ copy_parent->SetForceRenderSurface(true);
+
+ scoped_refptr<Layer> copy_layer = Layer::Create();
+ SetLayerPropertiesForTesting(copy_layer.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(20, 20),
+ false);
+ copy_layer->SetIsDrawable(true);
+
+ scoped_refptr<Layer> copy_child = Layer::Create();
+ SetLayerPropertiesForTesting(copy_child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(20, 20),
+ false);
+ copy_child->SetIsDrawable(true);
+
+ scoped_refptr<Layer> copy_grand_parent_sibling_before = Layer::Create();
+ SetLayerPropertiesForTesting(copy_grand_parent_sibling_before.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(40, 40),
+ false);
+ copy_grand_parent_sibling_before->SetIsDrawable(true);
+
+ scoped_refptr<Layer> copy_grand_parent_sibling_after = Layer::Create();
+ SetLayerPropertiesForTesting(copy_grand_parent_sibling_after.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(40, 40),
+ false);
+ copy_grand_parent_sibling_after->SetIsDrawable(true);
+
+ copy_layer->AddChild(copy_child);
+ copy_parent->AddChild(copy_layer);
+ copy_grand_parent->AddChild(copy_parent);
+ root->AddChild(copy_grand_parent_sibling_before);
+ root->AddChild(copy_grand_parent);
+ root->AddChild(copy_grand_parent_sibling_after);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ // Hide the copy_grand_parent and its subtree. But make a copy request in that
+ // hidden subtree on copy_layer.
+ copy_grand_parent->SetHideLayerAndSubtree(true);
+ copy_grand_parent_sibling_before->SetHideLayerAndSubtree(true);
+ copy_grand_parent_sibling_after->SetHideLayerAndSubtree(true);
+ copy_layer->RequestCopyOfOutput(CopyOutputRequest::CreateRequest(
+ base::Bind(&EmptyCopyOutputCallback)));
+ EXPECT_TRUE(copy_layer->HasCopyRequest());
+
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ EXPECT_TRUE(root->draw_properties().layer_or_descendant_has_copy_request);
+ EXPECT_TRUE(copy_grand_parent->draw_properties().
+ layer_or_descendant_has_copy_request);
+ EXPECT_TRUE(copy_parent->draw_properties().
+ layer_or_descendant_has_copy_request);
+ EXPECT_TRUE(copy_layer->draw_properties().
+ layer_or_descendant_has_copy_request);
+ EXPECT_FALSE(copy_child->draw_properties().
+ layer_or_descendant_has_copy_request);
+ EXPECT_FALSE(copy_grand_parent_sibling_before->draw_properties().
+ layer_or_descendant_has_copy_request);
+ EXPECT_FALSE(copy_grand_parent_sibling_after->draw_properties().
+ layer_or_descendant_has_copy_request);
+
+ // We should have three render surfaces, one for the root, one for the parent
+ // since it owns a surface, and one for the copy_layer.
+ ASSERT_EQ(3u, render_surface_layer_list.size());
+ EXPECT_EQ(root->id(), render_surface_layer_list.at(0)->id());
+ EXPECT_EQ(copy_parent->id(), render_surface_layer_list.at(1)->id());
+ EXPECT_EQ(copy_layer->id(), render_surface_layer_list.at(2)->id());
+
+ // The root render surface should have 2 contributing layers. The
+ // copy_grand_parent is hidden along with its siblings, but the copy_parent
+ // will appear since something in its subtree needs to be drawn for a copy
+ // request.
+ ASSERT_EQ(2u, root->render_surface()->layer_list().size());
+ EXPECT_EQ(root->id(), root->render_surface()->layer_list().at(0)->id());
+ EXPECT_EQ(copy_parent->id(),
+ root->render_surface()->layer_list().at(1)->id());
+
+ // Nothing actually drawns into the copy parent, so only the copy_layer will
+ // appear in its list, since it needs to be drawn for the copy request.
+ ASSERT_EQ(1u, copy_parent->render_surface()->layer_list().size());
+ EXPECT_EQ(copy_layer->id(),
+ copy_parent->render_surface()->layer_list().at(0)->id());
+
+ // The copy_layer's render surface should have two contributing layers.
+ ASSERT_EQ(2u, copy_layer->render_surface()->layer_list().size());
+ EXPECT_EQ(copy_layer->id(),
+ copy_layer->render_surface()->layer_list().at(0)->id());
+ EXPECT_EQ(copy_child->id(),
+ copy_layer->render_surface()->layer_list().at(1)->id());
+}
+
+TEST_F(LayerTreeHostCommonTest, ClippedOutCopyRequest) {
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ host_impl.CreatePendingTree();
+ const gfx::Transform identity_matrix;
+
+ scoped_refptr<Layer> root = Layer::Create();
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(50, 50),
+ false);
+ root->SetIsDrawable(true);
+
+ scoped_refptr<Layer> copy_parent = Layer::Create();
+ SetLayerPropertiesForTesting(copy_parent.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(),
+ false);
+ copy_parent->SetIsDrawable(true);
+ copy_parent->SetMasksToBounds(true);
+
+ scoped_refptr<Layer> copy_layer = Layer::Create();
+ SetLayerPropertiesForTesting(copy_layer.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(30, 30),
+ false);
+ copy_layer->SetIsDrawable(true);
+
+ scoped_refptr<Layer> copy_child = Layer::Create();
+ SetLayerPropertiesForTesting(copy_child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(20, 20),
+ false);
+ copy_child->SetIsDrawable(true);
+
+ copy_layer->AddChild(copy_child);
+ copy_parent->AddChild(copy_layer);
+ root->AddChild(copy_parent);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ copy_layer->RequestCopyOfOutput(CopyOutputRequest::CreateRequest(
+ base::Bind(&EmptyCopyOutputCallback)));
+ EXPECT_TRUE(copy_layer->HasCopyRequest());
+
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // We should have one render surface, as the others are clipped out.
+ ASSERT_EQ(1u, render_surface_layer_list.size());
+ EXPECT_EQ(root->id(), render_surface_layer_list.at(0)->id());
+
+ // The root render surface should only have 1 contributing layer, since the
+ // other layers are empty/clipped away.
+ ASSERT_EQ(1u, root->render_surface()->layer_list().size());
+ EXPECT_EQ(root->id(), root->render_surface()->layer_list().at(0)->id());
+}
+
+TEST_F(LayerTreeHostCommonTest, VisibleContentRectInsideSurface) {
+ FakeImplProxy proxy;
+ FakeLayerTreeHostImpl host_impl(&proxy);
+ host_impl.CreatePendingTree();
+ const gfx::Transform identity_matrix;
+
+ scoped_refptr<Layer> root = Layer::Create();
+ SetLayerPropertiesForTesting(root.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(50, 50),
+ false);
+ root->SetIsDrawable(true);
+
+ // The surface is moved slightly outside of the viewport.
+ scoped_refptr<Layer> surface = Layer::Create();
+ SetLayerPropertiesForTesting(surface.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(-10, -20),
+ gfx::Size(),
+ false);
+ surface->SetForceRenderSurface(true);
+
+ scoped_refptr<Layer> surface_child = Layer::Create();
+ SetLayerPropertiesForTesting(surface_child.get(),
+ identity_matrix,
+ identity_matrix,
+ gfx::PointF(),
+ gfx::PointF(),
+ gfx::Size(50, 50),
+ false);
+ surface_child->SetIsDrawable(true);
+
+ surface->AddChild(surface_child);
+ root->AddChild(surface);
+
+ scoped_ptr<FakeLayerTreeHost> host = FakeLayerTreeHost::Create();
+ host->SetRootLayer(root);
+
+ RenderSurfaceLayerList render_surface_layer_list;
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root.get(), root->bounds(), &render_surface_layer_list);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ // The visible_content_rect for the |surface_child| should not be clipped by
+ // the viewport.
+ EXPECT_EQ(gfx::Rect(50, 50).ToString(),
+ surface_child->visible_content_rect().ToString());
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/trees/layer_tree_host_impl.cc b/chromium/cc/trees/layer_tree_host_impl.cc
new file mode 100644
index 00000000000..720febd7fc7
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_host_impl.cc
@@ -0,0 +1,2533 @@
+// Copyright 2011 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 "cc/trees/layer_tree_host_impl.h"
+
+#include <algorithm>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/json/json_writer.h"
+#include "base/metrics/histogram.h"
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
+#include "cc/animation/scrollbar_animation_controller.h"
+#include "cc/animation/timing_function.h"
+#include "cc/base/math_util.h"
+#include "cc/base/util.h"
+#include "cc/debug/debug_rect_history.h"
+#include "cc/debug/frame_rate_counter.h"
+#include "cc/debug/overdraw_metrics.h"
+#include "cc/debug/paint_time_counter.h"
+#include "cc/debug/rendering_stats_instrumentation.h"
+#include "cc/debug/traced_value.h"
+#include "cc/input/page_scale_animation.h"
+#include "cc/input/top_controls_manager.h"
+#include "cc/layers/append_quads_data.h"
+#include "cc/layers/heads_up_display_layer_impl.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/layers/layer_iterator.h"
+#include "cc/layers/render_surface_impl.h"
+#include "cc/layers/scrollbar_layer_impl.h"
+#include "cc/output/compositor_frame_metadata.h"
+#include "cc/output/copy_output_request.h"
+#include "cc/output/delegating_renderer.h"
+#include "cc/output/gl_renderer.h"
+#include "cc/output/software_renderer.h"
+#include "cc/quads/render_pass_draw_quad.h"
+#include "cc/quads/shared_quad_state.h"
+#include "cc/quads/solid_color_draw_quad.h"
+#include "cc/resources/memory_history.h"
+#include "cc/resources/picture_layer_tiling.h"
+#include "cc/resources/prioritized_resource_manager.h"
+#include "cc/resources/ui_resource_bitmap.h"
+#include "cc/scheduler/delay_based_time_source.h"
+#include "cc/scheduler/texture_uploader.h"
+#include "cc/trees/damage_tracker.h"
+#include "cc/trees/layer_tree_host.h"
+#include "cc/trees/layer_tree_host_common.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "cc/trees/quad_culler.h"
+#include "cc/trees/single_thread_proxy.h"
+#include "cc/trees/tree_synchronizer.h"
+#include "ui/gfx/size_conversions.h"
+#include "ui/gfx/vector2d_conversions.h"
+
+namespace {
+
+void DidVisibilityChange(cc::LayerTreeHostImpl* id, bool visible) {
+ if (visible) {
+ TRACE_EVENT_ASYNC_BEGIN1("webkit",
+ "LayerTreeHostImpl::SetVisible",
+ id,
+ "LayerTreeHostImpl",
+ id);
+ return;
+ }
+
+ TRACE_EVENT_ASYNC_END0("webkit", "LayerTreeHostImpl::SetVisible", id);
+}
+
+} // namespace
+
+namespace cc {
+
+class LayerTreeHostImplTimeSourceAdapter : public TimeSourceClient {
+ public:
+ static scoped_ptr<LayerTreeHostImplTimeSourceAdapter> Create(
+ LayerTreeHostImpl* layer_tree_host_impl,
+ scoped_refptr<DelayBasedTimeSource> time_source) {
+ return make_scoped_ptr(
+ new LayerTreeHostImplTimeSourceAdapter(layer_tree_host_impl,
+ time_source));
+ }
+ virtual ~LayerTreeHostImplTimeSourceAdapter() {
+ time_source_->SetClient(NULL);
+ time_source_->SetActive(false);
+ }
+
+ virtual void OnTimerTick() OVERRIDE {
+ // In single threaded mode we attempt to simulate changing the current
+ // thread by maintaining a fake thread id. When we switch from one
+ // thread to another, we construct DebugScopedSetXXXThread objects that
+ // update the thread id. This lets DCHECKS that ensure we're on the
+ // right thread to work correctly in single threaded mode. The problem
+ // here is that the timer tasks are run via the message loop, and when
+ // they run, we've had no chance to construct a DebugScopedSetXXXThread
+ // object. The result is that we report that we're running on the main
+ // thread. In multi-threaded mode, this timer is run on the compositor
+ // thread, so to keep this consistent in single-threaded mode, we'll
+ // construct a DebugScopedSetImplThread object. There is no need to do
+ // this in multi-threaded mode since the real thread id's will be
+ // correct. In fact, setting fake thread id's interferes with the real
+ // thread id's and causes breakage.
+ scoped_ptr<DebugScopedSetImplThread> set_impl_thread;
+ if (!layer_tree_host_impl_->proxy()->HasImplThread()) {
+ set_impl_thread.reset(
+ new DebugScopedSetImplThread(layer_tree_host_impl_->proxy()));
+ }
+
+ // TODO(enne): This should probably happen post-animate.
+ if (layer_tree_host_impl_->pending_tree()) {
+ layer_tree_host_impl_->ActivatePendingTreeIfNeeded();
+
+ if (layer_tree_host_impl_->pending_tree()) {
+ layer_tree_host_impl_->pending_tree()->UpdateDrawProperties();
+ layer_tree_host_impl_->ManageTiles();
+ }
+ }
+
+ layer_tree_host_impl_->Animate(
+ layer_tree_host_impl_->CurrentFrameTimeTicks(),
+ layer_tree_host_impl_->CurrentFrameTime());
+ layer_tree_host_impl_->UpdateBackgroundAnimateTicking(true);
+ bool start_ready_animations = true;
+ layer_tree_host_impl_->UpdateAnimationState(start_ready_animations);
+ layer_tree_host_impl_->ResetCurrentFrameTimeForNextFrame();
+ }
+
+ void SetActive(bool active) {
+ if (active != time_source_->Active())
+ time_source_->SetActive(active);
+ }
+
+ private:
+ LayerTreeHostImplTimeSourceAdapter(
+ LayerTreeHostImpl* layer_tree_host_impl,
+ scoped_refptr<DelayBasedTimeSource> time_source)
+ : layer_tree_host_impl_(layer_tree_host_impl),
+ time_source_(time_source) {
+ time_source_->SetClient(this);
+ }
+
+ LayerTreeHostImpl* layer_tree_host_impl_;
+ scoped_refptr<DelayBasedTimeSource> time_source_;
+
+ DISALLOW_COPY_AND_ASSIGN(LayerTreeHostImplTimeSourceAdapter);
+};
+
+LayerTreeHostImpl::FrameData::FrameData()
+ : contains_incomplete_tile(false), has_no_damage(false) {}
+
+LayerTreeHostImpl::FrameData::~FrameData() {}
+
+scoped_ptr<LayerTreeHostImpl> LayerTreeHostImpl::Create(
+ const LayerTreeSettings& settings,
+ LayerTreeHostImplClient* client,
+ Proxy* proxy,
+ RenderingStatsInstrumentation* rendering_stats_instrumentation) {
+ return make_scoped_ptr(
+ new LayerTreeHostImpl(settings,
+ client,
+ proxy,
+ rendering_stats_instrumentation));
+}
+
+LayerTreeHostImpl::LayerTreeHostImpl(
+ const LayerTreeSettings& settings,
+ LayerTreeHostImplClient* client,
+ Proxy* proxy,
+ RenderingStatsInstrumentation* rendering_stats_instrumentation)
+ : client_(client),
+ proxy_(proxy),
+ input_handler_client_(NULL),
+ did_lock_scrolling_layer_(false),
+ should_bubble_scrolls_(false),
+ wheel_scrolling_(false),
+ manage_tiles_needed_(false),
+ root_layer_scroll_offset_delegate_(NULL),
+ settings_(settings),
+ visible_(true),
+ cached_managed_memory_policy_(
+ PrioritizedResourceManager::DefaultMemoryAllocationLimit(),
+ ManagedMemoryPolicy::CUTOFF_ALLOW_EVERYTHING,
+ 0,
+ ManagedMemoryPolicy::CUTOFF_ALLOW_NOTHING,
+ ManagedMemoryPolicy::kDefaultNumResourcesLimit),
+ pinch_gesture_active_(false),
+ fps_counter_(FrameRateCounter::Create(proxy_->HasImplThread())),
+ paint_time_counter_(PaintTimeCounter::Create()),
+ memory_history_(MemoryHistory::Create()),
+ debug_rect_history_(DebugRectHistory::Create()),
+ max_memory_needed_bytes_(0),
+ last_sent_memory_visible_bytes_(0),
+ last_sent_memory_visible_and_nearby_bytes_(0),
+ last_sent_memory_use_bytes_(0),
+ zero_budget_(false),
+ device_scale_factor_(1.f),
+ overdraw_bottom_height_(0.f),
+ external_stencil_test_enabled_(false),
+ animation_registrar_(AnimationRegistrar::Create()),
+ rendering_stats_instrumentation_(rendering_stats_instrumentation),
+ need_to_update_visible_tiles_before_draw_(false) {
+ DCHECK(proxy_->IsImplThread());
+ DidVisibilityChange(this, visible_);
+
+ SetDebugState(settings.initial_debug_state);
+
+ if (settings.calculate_top_controls_position) {
+ top_controls_manager_ =
+ TopControlsManager::Create(this,
+ settings.top_controls_height,
+ settings.top_controls_show_threshold,
+ settings.top_controls_hide_threshold);
+ }
+
+ SetDebugState(settings.initial_debug_state);
+
+ // LTHI always has an active tree.
+ active_tree_ = LayerTreeImpl::create(this);
+ TRACE_EVENT_OBJECT_CREATED_WITH_ID(
+ TRACE_DISABLED_BY_DEFAULT("cc.debug"), "cc::LayerTreeHostImpl", this);
+}
+
+LayerTreeHostImpl::~LayerTreeHostImpl() {
+ DCHECK(proxy_->IsImplThread());
+ TRACE_EVENT0("cc", "LayerTreeHostImpl::~LayerTreeHostImpl()");
+ TRACE_EVENT_OBJECT_DELETED_WITH_ID(
+ TRACE_DISABLED_BY_DEFAULT("cc.debug"), "cc::LayerTreeHostImpl", this);
+
+ if (input_handler_client_) {
+ input_handler_client_->WillShutdown();
+ input_handler_client_ = NULL;
+ }
+
+ // The layer trees must be destroyed before the layer tree host. We've
+ // made a contract with our animation controllers that the registrar
+ // will outlive them, and we must make good.
+ recycle_tree_.reset();
+ pending_tree_.reset();
+ active_tree_.reset();
+}
+
+void LayerTreeHostImpl::BeginCommit() {}
+
+void LayerTreeHostImpl::CommitComplete() {
+ TRACE_EVENT0("cc", "LayerTreeHostImpl::CommitComplete");
+
+ // Impl-side painting needs an update immediately post-commit to have the
+ // opportunity to create tilings. Other paths can call UpdateDrawProperties
+ // more lazily when needed prior to drawing.
+ if (settings_.impl_side_painting) {
+ pending_tree_->set_needs_update_draw_properties();
+ pending_tree_->UpdateDrawProperties();
+ // Start working on newly created tiles immediately if needed.
+ ManageTiles();
+ } else {
+ active_tree_->set_needs_update_draw_properties();
+ }
+
+ client_->SendManagedMemoryStats();
+}
+
+bool LayerTreeHostImpl::CanDraw() const {
+ // Note: If you are changing this function or any other function that might
+ // affect the result of CanDraw, make sure to call
+ // client_->OnCanDrawStateChanged in the proper places and update the
+ // NotifyIfCanDrawChanged test.
+
+ if (!renderer_) {
+ TRACE_EVENT_INSTANT0("cc", "LayerTreeHostImpl::CanDraw no renderer",
+ TRACE_EVENT_SCOPE_THREAD);
+ return false;
+ }
+
+ // Must have an OutputSurface if |renderer_| is not NULL.
+ DCHECK(output_surface_);
+
+ // TODO(boliu): Make draws without root_layer work and move this below
+ // draw_and_swap_full_viewport_every_frame check. Tracked in crbug.com/264967.
+ if (!active_tree_->root_layer()) {
+ TRACE_EVENT_INSTANT0("cc", "LayerTreeHostImpl::CanDraw no root layer",
+ TRACE_EVENT_SCOPE_THREAD);
+ return false;
+ }
+
+ if (output_surface_->capabilities().draw_and_swap_full_viewport_every_frame)
+ return true;
+
+ if (device_viewport_size_.IsEmpty()) {
+ TRACE_EVENT_INSTANT0("cc", "LayerTreeHostImpl::CanDraw empty viewport",
+ TRACE_EVENT_SCOPE_THREAD);
+ return false;
+ }
+ if (active_tree_->ViewportSizeInvalid()) {
+ TRACE_EVENT_INSTANT0(
+ "cc", "LayerTreeHostImpl::CanDraw viewport size recently changed",
+ TRACE_EVENT_SCOPE_THREAD);
+ return false;
+ }
+ if (active_tree_->ContentsTexturesPurged()) {
+ TRACE_EVENT_INSTANT0(
+ "cc", "LayerTreeHostImpl::CanDraw contents textures purged",
+ TRACE_EVENT_SCOPE_THREAD);
+ return false;
+ }
+ return true;
+}
+
+void LayerTreeHostImpl::Animate(base::TimeTicks monotonic_time,
+ base::Time wall_clock_time) {
+ if (input_handler_client_)
+ input_handler_client_->Animate(monotonic_time);
+ AnimatePageScale(monotonic_time);
+ AnimateLayers(monotonic_time, wall_clock_time);
+ AnimateScrollbars(monotonic_time);
+ AnimateTopControls(monotonic_time);
+}
+
+void LayerTreeHostImpl::ManageTiles() {
+ if (!tile_manager_)
+ return;
+ if (!manage_tiles_needed_)
+ return;
+ manage_tiles_needed_ = false;
+ tile_manager_->ManageTiles();
+
+ size_t memory_required_bytes;
+ size_t memory_nice_to_have_bytes;
+ size_t memory_allocated_bytes;
+ size_t memory_used_bytes;
+ tile_manager_->GetMemoryStats(&memory_required_bytes,
+ &memory_nice_to_have_bytes,
+ &memory_allocated_bytes,
+ &memory_used_bytes);
+ SendManagedMemoryStats(memory_required_bytes,
+ memory_nice_to_have_bytes,
+ memory_used_bytes);
+}
+
+void LayerTreeHostImpl::StartPageScaleAnimation(gfx::Vector2d target_offset,
+ bool anchor_point,
+ float page_scale,
+ base::TimeTicks start_time,
+ base::TimeDelta duration) {
+ if (!RootScrollLayer())
+ return;
+
+ gfx::Vector2dF scroll_total =
+ RootScrollLayer()->scroll_offset() + RootScrollLayer()->ScrollDelta();
+ gfx::SizeF scaled_scrollable_size = active_tree_->ScrollableSize();
+ gfx::SizeF viewport_size = VisibleViewportSize();
+
+ double start_time_seconds = (start_time - base::TimeTicks()).InSecondsF();
+
+ // Easing constants experimentally determined.
+ scoped_ptr<TimingFunction> timing_function =
+ CubicBezierTimingFunction::Create(.8, 0, .3, .9).PassAs<TimingFunction>();
+
+ page_scale_animation_ =
+ PageScaleAnimation::Create(scroll_total,
+ active_tree_->total_page_scale_factor(),
+ viewport_size,
+ scaled_scrollable_size,
+ start_time_seconds,
+ timing_function.Pass());
+
+ if (anchor_point) {
+ gfx::Vector2dF anchor(target_offset);
+ page_scale_animation_->ZoomWithAnchor(anchor,
+ page_scale,
+ duration.InSecondsF());
+ } else {
+ gfx::Vector2dF scaled_target_offset = target_offset;
+ page_scale_animation_->ZoomTo(scaled_target_offset,
+ page_scale,
+ duration.InSecondsF());
+ }
+
+ client_->SetNeedsRedrawOnImplThread();
+ client_->SetNeedsCommitOnImplThread();
+ client_->RenewTreePriority();
+}
+
+void LayerTreeHostImpl::ScheduleAnimation() {
+ client_->SetNeedsRedrawOnImplThread();
+}
+
+bool LayerTreeHostImpl::HaveTouchEventHandlersAt(gfx::Point viewport_point) {
+ if (!EnsureRenderSurfaceLayerList())
+ return false;
+
+ gfx::PointF device_viewport_point =
+ gfx::ScalePoint(viewport_point, device_scale_factor_);
+
+ // First find out which layer was hit from the saved list of visible layers
+ // in the most recent frame.
+ LayerImpl* layer_impl = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ device_viewport_point,
+ active_tree_->RenderSurfaceLayerList());
+
+ // Walk up the hierarchy and look for a layer with a touch event handler
+ // region that the given point hits.
+ for (; layer_impl; layer_impl = layer_impl->parent()) {
+ if (LayerTreeHostCommon::LayerHasTouchEventHandlersAt(device_viewport_point,
+ layer_impl))
+ return true;
+ }
+
+ return false;
+}
+
+void LayerTreeHostImpl::SetLatencyInfoForInputEvent(
+ const ui::LatencyInfo& latency_info) {
+ active_tree()->SetLatencyInfo(latency_info);
+}
+
+void LayerTreeHostImpl::TrackDamageForAllSurfaces(
+ LayerImpl* root_draw_layer,
+ const LayerImplList& render_surface_layer_list) {
+ // For now, we use damage tracking to compute a global scissor. To do this, we
+ // must compute all damage tracking before drawing anything, so that we know
+ // the root damage rect. The root damage rect is then used to scissor each
+ // surface.
+
+ for (int surface_index = render_surface_layer_list.size() - 1;
+ surface_index >= 0;
+ --surface_index) {
+ LayerImpl* render_surface_layer = render_surface_layer_list[surface_index];
+ RenderSurfaceImpl* render_surface = render_surface_layer->render_surface();
+ DCHECK(render_surface);
+ render_surface->damage_tracker()->UpdateDamageTrackingState(
+ render_surface->layer_list(),
+ render_surface_layer->id(),
+ render_surface->SurfacePropertyChangedOnlyFromDescendant(),
+ render_surface->content_rect(),
+ render_surface_layer->mask_layer(),
+ render_surface_layer->filters(),
+ render_surface_layer->filter().get());
+ }
+}
+
+scoped_ptr<base::Value> LayerTreeHostImpl::FrameData::AsValue() const {
+ scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue());
+ value->SetBoolean("contains_incomplete_tile", contains_incomplete_tile);
+ value->SetBoolean("has_no_damage", has_no_damage);
+
+ // Quad data can be quite large, so only dump render passes if we select
+ // cc.debug.quads.
+ bool quads_enabled;
+ TRACE_EVENT_CATEGORY_GROUP_ENABLED(
+ TRACE_DISABLED_BY_DEFAULT("cc.debug.quads"), &quads_enabled);
+ if (quads_enabled) {
+ scoped_ptr<base::ListValue> render_pass_list(new base::ListValue());
+ for (size_t i = 0; i < render_passes.size(); ++i)
+ render_pass_list->Append(render_passes[i]->AsValue().release());
+ value->Set("render_passes", render_pass_list.release());
+ }
+ return value.PassAs<base::Value>();
+}
+
+void LayerTreeHostImpl::FrameData::AppendRenderPass(
+ scoped_ptr<RenderPass> render_pass) {
+ render_passes_by_id[render_pass->id] = render_pass.get();
+ render_passes.push_back(render_pass.Pass());
+}
+
+static DrawMode GetDrawMode(OutputSurface* output_surface) {
+ if (output_surface->ForcedDrawToSoftwareDevice()) {
+ return DRAW_MODE_RESOURCELESS_SOFTWARE;
+ } else if (output_surface->context3d()) {
+ return DRAW_MODE_HARDWARE;
+ } else {
+ DCHECK(output_surface->software_device());
+ return DRAW_MODE_SOFTWARE;
+ }
+}
+
+static void AppendQuadsForLayer(RenderPass* target_render_pass,
+ LayerImpl* layer,
+ const OcclusionTrackerImpl& occlusion_tracker,
+ AppendQuadsData* append_quads_data) {
+ bool for_surface = false;
+ QuadCuller quad_culler(&target_render_pass->quad_list,
+ &target_render_pass->shared_quad_state_list,
+ layer,
+ occlusion_tracker,
+ layer->ShowDebugBorders(),
+ for_surface);
+ layer->AppendQuads(&quad_culler, append_quads_data);
+}
+
+static void AppendQuadsForRenderSurfaceLayer(
+ RenderPass* target_render_pass,
+ LayerImpl* layer,
+ const RenderPass* contributing_render_pass,
+ const OcclusionTrackerImpl& occlusion_tracker,
+ AppendQuadsData* append_quads_data) {
+ bool for_surface = true;
+ QuadCuller quad_culler(&target_render_pass->quad_list,
+ &target_render_pass->shared_quad_state_list,
+ layer,
+ occlusion_tracker,
+ layer->ShowDebugBorders(),
+ for_surface);
+
+ bool is_replica = false;
+ layer->render_surface()->AppendQuads(&quad_culler,
+ append_quads_data,
+ is_replica,
+ contributing_render_pass->id);
+
+ // Add replica after the surface so that it appears below the surface.
+ if (layer->has_replica()) {
+ is_replica = true;
+ layer->render_surface()->AppendQuads(&quad_culler,
+ append_quads_data,
+ is_replica,
+ contributing_render_pass->id);
+ }
+}
+
+static void AppendQuadsToFillScreen(
+ RenderPass* target_render_pass,
+ LayerImpl* root_layer,
+ SkColor screen_background_color,
+ const OcclusionTrackerImpl& occlusion_tracker) {
+ if (!root_layer || !SkColorGetA(screen_background_color))
+ return;
+
+ Region fill_region = occlusion_tracker.ComputeVisibleRegionInScreen();
+ if (fill_region.IsEmpty())
+ return;
+
+ bool for_surface = false;
+ QuadCuller quad_culler(&target_render_pass->quad_list,
+ &target_render_pass->shared_quad_state_list,
+ root_layer,
+ occlusion_tracker,
+ root_layer->ShowDebugBorders(),
+ for_surface);
+
+ // Manually create the quad state for the gutter quads, as the root layer
+ // doesn't have any bounds and so can't generate this itself.
+ // TODO(danakj): Make the gutter quads generated by the solid color layer
+ // (make it smarter about generating quads to fill unoccluded areas).
+
+ gfx::Rect root_target_rect = root_layer->render_surface()->content_rect();
+ float opacity = 1.f;
+ SharedQuadState* shared_quad_state =
+ quad_culler.UseSharedQuadState(SharedQuadState::Create());
+ shared_quad_state->SetAll(root_layer->draw_transform(),
+ root_target_rect.size(),
+ root_target_rect,
+ root_target_rect,
+ false,
+ opacity);
+
+ AppendQuadsData append_quads_data;
+
+ gfx::Transform transform_to_layer_space(gfx::Transform::kSkipInitialization);
+ bool did_invert = root_layer->screen_space_transform().GetInverse(
+ &transform_to_layer_space);
+ DCHECK(did_invert);
+ for (Region::Iterator fill_rects(fill_region);
+ fill_rects.has_rect();
+ fill_rects.next()) {
+ // The root layer transform is composed of translations and scales only,
+ // no perspective, so mapping is sufficient (as opposed to projecting).
+ gfx::Rect layer_rect =
+ MathUtil::MapClippedRect(transform_to_layer_space, fill_rects.rect());
+ // Skip the quad culler and just append the quads directly to avoid
+ // occlusion checks.
+ scoped_ptr<SolidColorDrawQuad> quad = SolidColorDrawQuad::Create();
+ quad->SetNew(shared_quad_state, layer_rect, screen_background_color, false);
+ quad_culler.Append(quad.PassAs<DrawQuad>(), &append_quads_data);
+ }
+}
+
+bool LayerTreeHostImpl::CalculateRenderPasses(FrameData* frame) {
+ DCHECK(frame->render_passes.empty());
+
+ if (!CanDraw() || !active_tree_->root_layer())
+ return false;
+
+ TrackDamageForAllSurfaces(active_tree_->root_layer(),
+ *frame->render_surface_layer_list);
+
+ // If the root render surface has no visible damage, then don't generate a
+ // frame at all.
+ RenderSurfaceImpl* root_surface =
+ active_tree_->root_layer()->render_surface();
+ bool root_surface_has_no_visible_damage =
+ !root_surface->damage_tracker()->current_damage_rect().Intersects(
+ root_surface->content_rect());
+ bool root_surface_has_contributing_layers =
+ !root_surface->layer_list().empty();
+ if (root_surface_has_contributing_layers &&
+ root_surface_has_no_visible_damage) {
+ TRACE_EVENT0("cc",
+ "LayerTreeHostImpl::CalculateRenderPasses::EmptyDamageRect");
+ frame->has_no_damage = true;
+ // A copy request should cause damage, so we should not have any copy
+ // requests in this case.
+ DCHECK_EQ(0u, active_tree_->LayersWithCopyOutputRequest().size());
+ DCHECK(!output_surface_->capabilities()
+ .draw_and_swap_full_viewport_every_frame);
+ return true;
+ }
+
+ TRACE_EVENT1("cc",
+ "LayerTreeHostImpl::CalculateRenderPasses",
+ "render_surface_layer_list.size()",
+ static_cast<uint64>(frame->render_surface_layer_list->size()));
+
+ // Create the render passes in dependency order.
+ for (int surface_index = frame->render_surface_layer_list->size() - 1;
+ surface_index >= 0;
+ --surface_index) {
+ LayerImpl* render_surface_layer =
+ (*frame->render_surface_layer_list)[surface_index];
+ RenderSurfaceImpl* render_surface = render_surface_layer->render_surface();
+
+ bool should_draw_into_render_pass =
+ render_surface_layer->parent() == NULL ||
+ render_surface->contributes_to_drawn_surface() ||
+ render_surface_layer->HasCopyRequest();
+ if (should_draw_into_render_pass)
+ render_surface_layer->render_surface()->AppendRenderPasses(frame);
+ }
+
+ bool record_metrics_for_frame =
+ settings_.show_overdraw_in_tracing &&
+ base::debug::TraceLog::GetInstance() &&
+ base::debug::TraceLog::GetInstance()->IsEnabled();
+ OcclusionTrackerImpl occlusion_tracker(
+ active_tree_->root_layer()->render_surface()->content_rect(),
+ record_metrics_for_frame);
+ occlusion_tracker.set_minimum_tracking_size(
+ settings_.minimum_occlusion_tracking_size);
+
+ if (debug_state_.show_occluding_rects) {
+ occlusion_tracker.set_occluding_screen_space_rects_container(
+ &frame->occluding_screen_space_rects);
+ }
+ if (debug_state_.show_non_occluding_rects) {
+ occlusion_tracker.set_non_occluding_screen_space_rects_container(
+ &frame->non_occluding_screen_space_rects);
+ }
+
+ // Add quads to the Render passes in FrontToBack order to allow for testing
+ // occlusion and performing culling during the tree walk.
+ typedef LayerIterator<LayerImpl,
+ LayerImplList,
+ RenderSurfaceImpl,
+ LayerIteratorActions::FrontToBack> LayerIteratorType;
+
+ // Typically when we are missing a texture and use a checkerboard quad, we
+ // still draw the frame. However when the layer being checkerboarded is moving
+ // due to an impl-animation, we drop the frame to avoid flashing due to the
+ // texture suddenly appearing in the future.
+ bool draw_frame = true;
+ // When we have a copy request for a layer, we need to draw no matter
+ // what, as the layer may disappear after this frame.
+ bool have_copy_request = false;
+
+ int layers_drawn = 0;
+
+ const DrawMode draw_mode = GetDrawMode(output_surface_.get());
+
+ LayerIteratorType end =
+ LayerIteratorType::End(frame->render_surface_layer_list);
+ for (LayerIteratorType it =
+ LayerIteratorType::Begin(frame->render_surface_layer_list);
+ it != end;
+ ++it) {
+ RenderPass::Id target_render_pass_id =
+ it.target_render_surface_layer()->render_surface()->RenderPassId();
+ RenderPass* target_render_pass =
+ frame->render_passes_by_id[target_render_pass_id];
+
+ bool prevent_occlusion = it.target_render_surface_layer()->HasCopyRequest();
+ occlusion_tracker.EnterLayer(it, prevent_occlusion);
+
+ AppendQuadsData append_quads_data(target_render_pass_id);
+
+ if (it.represents_target_render_surface()) {
+ if (it->HasCopyRequest()) {
+ have_copy_request = true;
+ it->TakeCopyRequestsAndTransformToTarget(
+ &target_render_pass->copy_requests);
+ }
+ } else if (it.represents_contributing_render_surface() &&
+ it->render_surface()->contributes_to_drawn_surface()) {
+ RenderPass::Id contributing_render_pass_id =
+ it->render_surface()->RenderPassId();
+ RenderPass* contributing_render_pass =
+ frame->render_passes_by_id[contributing_render_pass_id];
+ AppendQuadsForRenderSurfaceLayer(target_render_pass,
+ *it,
+ contributing_render_pass,
+ occlusion_tracker,
+ &append_quads_data);
+ } else if (it.represents_itself() &&
+ !it->visible_content_rect().IsEmpty()) {
+ bool has_occlusion_from_outside_target_surface;
+ bool impl_draw_transform_is_unknown = false;
+ if (occlusion_tracker.Occluded(
+ it->render_target(),
+ it->visible_content_rect(),
+ it->draw_transform(),
+ impl_draw_transform_is_unknown,
+ it->is_clipped(),
+ it->clip_rect(),
+ &has_occlusion_from_outside_target_surface)) {
+ append_quads_data.had_occlusion_from_outside_target_surface |=
+ has_occlusion_from_outside_target_surface;
+ } else if (it->WillDraw(draw_mode, resource_provider_.get())) {
+ DCHECK_EQ(active_tree_, it->layer_tree_impl());
+
+ frame->will_draw_layers.push_back(*it);
+
+ if (it->HasContributingDelegatedRenderPasses()) {
+ RenderPass::Id contributing_render_pass_id =
+ it->FirstContributingRenderPassId();
+ while (frame->render_passes_by_id.find(contributing_render_pass_id) !=
+ frame->render_passes_by_id.end()) {
+ RenderPass* render_pass =
+ frame->render_passes_by_id[contributing_render_pass_id];
+
+ AppendQuadsData append_quads_data(render_pass->id);
+ AppendQuadsForLayer(render_pass,
+ *it,
+ occlusion_tracker,
+ &append_quads_data);
+
+ contributing_render_pass_id =
+ it->NextContributingRenderPassId(contributing_render_pass_id);
+ }
+ }
+
+ AppendQuadsForLayer(target_render_pass,
+ *it,
+ occlusion_tracker,
+ &append_quads_data);
+ }
+
+ ++layers_drawn;
+ }
+
+ if (append_quads_data.had_occlusion_from_outside_target_surface)
+ target_render_pass->has_occlusion_from_outside_target_surface = true;
+
+ if (append_quads_data.num_missing_tiles) {
+ rendering_stats_instrumentation_->AddMissingTiles(
+ append_quads_data.num_missing_tiles);
+ bool layer_has_animating_transform =
+ it->screen_space_transform_is_animating() ||
+ it->draw_transform_is_animating();
+ if (layer_has_animating_transform)
+ draw_frame = false;
+ }
+
+ if (append_quads_data.had_incomplete_tile)
+ frame->contains_incomplete_tile = true;
+
+ occlusion_tracker.LeaveLayer(it);
+ }
+
+ if (have_copy_request ||
+ output_surface_->capabilities().draw_and_swap_full_viewport_every_frame)
+ draw_frame = true;
+
+ rendering_stats_instrumentation_->AddLayersDrawn(layers_drawn);
+
+#ifndef NDEBUG
+ for (size_t i = 0; i < frame->render_passes.size(); ++i) {
+ for (size_t j = 0; j < frame->render_passes[i]->quad_list.size(); ++j)
+ DCHECK(frame->render_passes[i]->quad_list[j]->shared_quad_state);
+ DCHECK(frame->render_passes_by_id.find(frame->render_passes[i]->id)
+ != frame->render_passes_by_id.end());
+ }
+#endif
+ DCHECK(frame->render_passes.back()->output_rect.origin().IsOrigin());
+
+ if (!active_tree_->has_transparent_background()) {
+ frame->render_passes.back()->has_transparent_background = false;
+ AppendQuadsToFillScreen(frame->render_passes.back(),
+ active_tree_->root_layer(),
+ active_tree_->background_color(),
+ occlusion_tracker);
+ }
+
+ if (draw_frame)
+ occlusion_tracker.overdraw_metrics()->RecordMetrics(this);
+ else
+ DCHECK(!have_copy_request);
+
+ RemoveRenderPasses(CullRenderPassesWithNoQuads(), frame);
+ if (!output_surface_->ForcedDrawToSoftwareDevice())
+ renderer_->DecideRenderPassAllocationsForFrame(frame->render_passes);
+ RemoveRenderPasses(CullRenderPassesWithCachedTextures(renderer_.get()),
+ frame);
+
+ // Any copy requests left in the tree are not going to get serviced, and
+ // should be aborted.
+ ScopedPtrVector<CopyOutputRequest> requests_to_abort;
+ while (!active_tree_->LayersWithCopyOutputRequest().empty()) {
+ LayerImpl* layer = active_tree_->LayersWithCopyOutputRequest().back();
+ layer->TakeCopyRequestsAndTransformToTarget(&requests_to_abort);
+ }
+ for (size_t i = 0; i < requests_to_abort.size(); ++i)
+ requests_to_abort[i]->SendEmptyResult();
+
+ // If we're making a frame to draw, it better have at least one render pass.
+ DCHECK(!frame->render_passes.empty());
+ return draw_frame;
+}
+
+void LayerTreeHostImpl::MainThreadHasStoppedFlinging() {
+ if (input_handler_client_)
+ input_handler_client_->MainThreadHasStoppedFlinging();
+}
+
+void LayerTreeHostImpl::UpdateBackgroundAnimateTicking(
+ bool should_background_tick) {
+ DCHECK(proxy_->IsImplThread());
+
+ bool enabled = should_background_tick &&
+ !animation_registrar_->active_animation_controllers().empty();
+
+ // Lazily create the time_source adapter so that we can vary the interval for
+ // testing.
+ if (!time_source_client_adapter_) {
+ time_source_client_adapter_ = LayerTreeHostImplTimeSourceAdapter::Create(
+ this,
+ DelayBasedTimeSource::Create(
+ LowFrequencyAnimationInterval(),
+ proxy_->HasImplThread() ? proxy_->ImplThreadTaskRunner()
+ : proxy_->MainThreadTaskRunner()));
+ }
+
+ time_source_client_adapter_->SetActive(enabled);
+}
+
+void LayerTreeHostImpl::SetViewportDamage(gfx::Rect damage_rect) {
+ viewport_damage_rect_.Union(damage_rect);
+}
+
+static inline RenderPass* FindRenderPassById(
+ RenderPass::Id render_pass_id,
+ const LayerTreeHostImpl::FrameData& frame) {
+ RenderPassIdHashMap::const_iterator it =
+ frame.render_passes_by_id.find(render_pass_id);
+ return it != frame.render_passes_by_id.end() ? it->second : NULL;
+}
+
+static void RemoveRenderPassesRecursive(RenderPass::Id remove_render_pass_id,
+ LayerTreeHostImpl::FrameData* frame) {
+ RenderPass* remove_render_pass =
+ FindRenderPassById(remove_render_pass_id, *frame);
+ // The pass was already removed by another quad - probably the original, and
+ // we are the replica.
+ if (!remove_render_pass)
+ return;
+ RenderPassList& render_passes = frame->render_passes;
+ RenderPassList::iterator to_remove = std::find(render_passes.begin(),
+ render_passes.end(),
+ remove_render_pass);
+
+ DCHECK(to_remove != render_passes.end());
+
+ scoped_ptr<RenderPass> removed_pass = render_passes.take(to_remove);
+ frame->render_passes.erase(to_remove);
+ frame->render_passes_by_id.erase(remove_render_pass_id);
+
+ // Now follow up for all RenderPass quads and remove their RenderPasses
+ // recursively.
+ const QuadList& quad_list = removed_pass->quad_list;
+ QuadList::ConstBackToFrontIterator quad_list_iterator =
+ quad_list.BackToFrontBegin();
+ for (; quad_list_iterator != quad_list.BackToFrontEnd();
+ ++quad_list_iterator) {
+ DrawQuad* current_quad = (*quad_list_iterator);
+ if (current_quad->material != DrawQuad::RENDER_PASS)
+ continue;
+
+ RenderPass::Id next_remove_render_pass_id =
+ RenderPassDrawQuad::MaterialCast(current_quad)->render_pass_id;
+ RemoveRenderPassesRecursive(next_remove_render_pass_id, frame);
+ }
+}
+
+bool LayerTreeHostImpl::CullRenderPassesWithCachedTextures::
+ ShouldRemoveRenderPass(const RenderPassDrawQuad& quad,
+ const FrameData& frame) const {
+ DCHECK(renderer_);
+ bool quad_has_damage = !quad.contents_changed_since_last_frame.IsEmpty();
+ bool quad_has_cached_resource =
+ renderer_->HaveCachedResourcesForRenderPassId(quad.render_pass_id);
+ if (quad_has_damage) {
+ TRACE_EVENT0("cc", "CullRenderPassesWithCachedTextures have damage");
+ return false;
+ } else if (!quad_has_cached_resource) {
+ TRACE_EVENT0("cc", "CullRenderPassesWithCachedTextures have no texture");
+ return false;
+ }
+ TRACE_EVENT0("cc", "CullRenderPassesWithCachedTextures dropped!");
+ return true;
+}
+
+bool LayerTreeHostImpl::CullRenderPassesWithNoQuads::ShouldRemoveRenderPass(
+ const RenderPassDrawQuad& quad, const FrameData& frame) const {
+ const RenderPass* render_pass =
+ FindRenderPassById(quad.render_pass_id, frame);
+ if (!render_pass)
+ return false;
+
+ // If any quad or RenderPass draws into this RenderPass, then keep it.
+ const QuadList& quad_list = render_pass->quad_list;
+ for (QuadList::ConstBackToFrontIterator quad_list_iterator =
+ quad_list.BackToFrontBegin();
+ quad_list_iterator != quad_list.BackToFrontEnd();
+ ++quad_list_iterator) {
+ DrawQuad* current_quad = *quad_list_iterator;
+
+ if (current_quad->material != DrawQuad::RENDER_PASS)
+ return false;
+
+ const RenderPass* contributing_pass = FindRenderPassById(
+ RenderPassDrawQuad::MaterialCast(current_quad)->render_pass_id, frame);
+ if (contributing_pass)
+ return false;
+ }
+ return true;
+}
+
+// Defined for linking tests.
+template CC_EXPORT void LayerTreeHostImpl::RemoveRenderPasses<
+ LayerTreeHostImpl::CullRenderPassesWithCachedTextures>(
+ CullRenderPassesWithCachedTextures culler, FrameData* frame);
+template CC_EXPORT void LayerTreeHostImpl::RemoveRenderPasses<
+ LayerTreeHostImpl::CullRenderPassesWithNoQuads>(
+ CullRenderPassesWithNoQuads culler, FrameData*);
+
+// static
+template <typename RenderPassCuller>
+void LayerTreeHostImpl::RemoveRenderPasses(RenderPassCuller culler,
+ FrameData* frame) {
+ for (size_t it = culler.RenderPassListBegin(frame->render_passes);
+ it != culler.RenderPassListEnd(frame->render_passes);
+ it = culler.RenderPassListNext(it)) {
+ const RenderPass* current_pass = frame->render_passes[it];
+ const QuadList& quad_list = current_pass->quad_list;
+ QuadList::ConstBackToFrontIterator quad_list_iterator =
+ quad_list.BackToFrontBegin();
+
+ for (; quad_list_iterator != quad_list.BackToFrontEnd();
+ ++quad_list_iterator) {
+ DrawQuad* current_quad = *quad_list_iterator;
+
+ if (current_quad->material != DrawQuad::RENDER_PASS)
+ continue;
+
+ const RenderPassDrawQuad* render_pass_quad =
+ RenderPassDrawQuad::MaterialCast(current_quad);
+ if (!culler.ShouldRemoveRenderPass(*render_pass_quad, *frame))
+ continue;
+
+ // We are changing the vector in the middle of iteration. Because we
+ // delete render passes that draw into the current pass, we are
+ // guaranteed that any data from the iterator to the end will not
+ // change. So, capture the iterator position from the end of the
+ // list, and restore it after the change.
+ size_t position_from_end = frame->render_passes.size() - it;
+ RemoveRenderPassesRecursive(render_pass_quad->render_pass_id, frame);
+ it = frame->render_passes.size() - position_from_end;
+ DCHECK_GE(frame->render_passes.size(), position_from_end);
+ }
+ }
+}
+
+bool LayerTreeHostImpl::PrepareToDraw(FrameData* frame,
+ gfx::Rect device_viewport_damage_rect) {
+ TRACE_EVENT1("cc",
+ "LayerTreeHostImpl::PrepareToDraw",
+ "SourceFrameNumber",
+ active_tree_->source_frame_number());
+
+ if (need_to_update_visible_tiles_before_draw_) {
+ DCHECK(tile_manager_);
+ if (tile_manager_->UpdateVisibleTiles())
+ DidInitializeVisibleTile();
+ }
+
+ active_tree_->UpdateDrawProperties();
+
+ frame->render_surface_layer_list = &active_tree_->RenderSurfaceLayerList();
+ frame->render_passes.clear();
+ frame->render_passes_by_id.clear();
+ frame->will_draw_layers.clear();
+ frame->contains_incomplete_tile = false;
+ frame->has_no_damage = false;
+
+ if (active_tree_->root_layer()) {
+ device_viewport_damage_rect.Union(viewport_damage_rect_);
+ viewport_damage_rect_ = gfx::Rect();
+
+ active_tree_->root_layer()->render_surface()->damage_tracker()->
+ AddDamageNextUpdate(device_viewport_damage_rect);
+ }
+
+ if (!CalculateRenderPasses(frame)) {
+ DCHECK(!output_surface_->capabilities()
+ .draw_and_swap_full_viewport_every_frame);
+ return false;
+ }
+
+ // If we return true, then we expect DrawLayers() to be called before this
+ // function is called again.
+ return true;
+}
+
+void LayerTreeHostImpl::EvictTexturesForTesting() {
+ EnforceManagedMemoryPolicy(ManagedMemoryPolicy(0));
+}
+
+void LayerTreeHostImpl::EnforceManagedMemoryPolicy(
+ const ManagedMemoryPolicy& policy) {
+
+ bool evicted_resources = client_->ReduceContentsTextureMemoryOnImplThread(
+ visible_ ? policy.bytes_limit_when_visible
+ : policy.bytes_limit_when_not_visible,
+ ManagedMemoryPolicy::PriorityCutoffToValue(
+ visible_ ? policy.priority_cutoff_when_visible
+ : policy.priority_cutoff_when_not_visible));
+ if (evicted_resources) {
+ active_tree_->SetContentsTexturesPurged();
+ if (pending_tree_)
+ pending_tree_->SetContentsTexturesPurged();
+ client_->SetNeedsCommitOnImplThread();
+ client_->OnCanDrawStateChanged(CanDraw());
+ client_->RenewTreePriority();
+ }
+ client_->SendManagedMemoryStats();
+
+ UpdateTileManagerMemoryPolicy(policy);
+}
+
+void LayerTreeHostImpl::UpdateTileManagerMemoryPolicy(
+ const ManagedMemoryPolicy& policy) {
+ if (!tile_manager_)
+ return;
+
+ GlobalStateThatImpactsTilePriority new_state(tile_manager_->GlobalState());
+ new_state.memory_limit_in_bytes = visible_ ?
+ policy.bytes_limit_when_visible :
+ policy.bytes_limit_when_not_visible;
+ // TODO(reveman): We should avoid keeping around unused resources if
+ // possible. crbug.com/224475
+ new_state.unused_memory_limit_in_bytes = static_cast<size_t>(
+ (static_cast<int64>(new_state.memory_limit_in_bytes) *
+ settings_.max_unused_resource_memory_percentage) / 100);
+ new_state.memory_limit_policy =
+ ManagedMemoryPolicy::PriorityCutoffToTileMemoryLimitPolicy(
+ visible_ ?
+ policy.priority_cutoff_when_visible :
+ policy.priority_cutoff_when_not_visible);
+ new_state.num_resources_limit = policy.num_resources_limit;
+ tile_manager_->SetGlobalState(new_state);
+ manage_tiles_needed_ = true;
+}
+
+bool LayerTreeHostImpl::HasImplThread() const {
+ return proxy_->HasImplThread();
+}
+
+void LayerTreeHostImpl::DidInitializeVisibleTile() {
+ // TODO(reveman): Determine tiles that changed and only damage
+ // what's necessary.
+ SetFullRootLayerDamage();
+ if (client_)
+ client_->DidInitializeVisibleTileOnImplThread();
+}
+
+void LayerTreeHostImpl::NotifyReadyToActivate() {
+ if (pending_tree_) {
+ need_to_update_visible_tiles_before_draw_ = true;
+ ActivatePendingTree();
+ }
+}
+
+bool LayerTreeHostImpl::ShouldClearRootRenderPass() const {
+ return settings_.should_clear_root_render_pass;
+}
+
+void LayerTreeHostImpl::SetMemoryPolicy(const ManagedMemoryPolicy& policy) {
+ SetManagedMemoryPolicy(policy, zero_budget_);
+}
+
+void LayerTreeHostImpl::SetDiscardBackBufferWhenNotVisible(bool discard) {
+ DCHECK(renderer_);
+ renderer_->SetDiscardBackBufferWhenNotVisible(discard);
+}
+
+void LayerTreeHostImpl::SetTreeActivationCallback(
+ const base::Closure& callback) {
+ DCHECK(proxy_->IsImplThread());
+ DCHECK(settings_.impl_side_painting || callback.is_null());
+ tree_activation_callback_ = callback;
+}
+
+void LayerTreeHostImpl::SetManagedMemoryPolicy(
+ const ManagedMemoryPolicy& policy, bool zero_budget) {
+ if (cached_managed_memory_policy_ == policy && zero_budget_ == zero_budget)
+ return;
+
+ ManagedMemoryPolicy old_policy = ActualManagedMemoryPolicy();
+
+ cached_managed_memory_policy_ = policy;
+ zero_budget_ = zero_budget;
+ ManagedMemoryPolicy actual_policy = ActualManagedMemoryPolicy();
+
+ if (old_policy == actual_policy)
+ return;
+
+ if (!proxy_->HasImplThread()) {
+ // In single-thread mode, this can be called on the main thread by
+ // GLRenderer::OnMemoryAllocationChanged.
+ DebugScopedSetImplThread impl_thread(proxy_);
+ EnforceManagedMemoryPolicy(actual_policy);
+ } else {
+ DCHECK(proxy_->IsImplThread());
+ EnforceManagedMemoryPolicy(actual_policy);
+ }
+
+ // If there is already enough memory to draw everything imaginable and the
+ // new memory limit does not change this, then do not re-commit. Don't bother
+ // skipping commits if this is not visible (commits don't happen when not
+ // visible, there will almost always be a commit when this becomes visible).
+ bool needs_commit = true;
+ if (visible() &&
+ actual_policy.bytes_limit_when_visible >= max_memory_needed_bytes_ &&
+ old_policy.bytes_limit_when_visible >= max_memory_needed_bytes_ &&
+ actual_policy.priority_cutoff_when_visible ==
+ old_policy.priority_cutoff_when_visible) {
+ needs_commit = false;
+ }
+
+ if (needs_commit)
+ client_->SetNeedsCommitOnImplThread();
+}
+
+void LayerTreeHostImpl::SetExternalDrawConstraints(
+ const gfx::Transform& transform,
+ gfx::Rect viewport) {
+ external_transform_ = transform;
+ external_viewport_ = viewport;
+}
+
+void LayerTreeHostImpl::SetExternalStencilTest(bool enabled) {
+ external_stencil_test_enabled_ = enabled;
+}
+
+void LayerTreeHostImpl::SetNeedsRedrawRect(gfx::Rect damage_rect) {
+ client_->SetNeedsRedrawRectOnImplThread(damage_rect);
+}
+
+void LayerTreeHostImpl::BeginFrame(const BeginFrameArgs& args) {
+ client_->BeginFrameOnImplThread(args);
+}
+
+void LayerTreeHostImpl::OnSwapBuffersComplete(
+ const CompositorFrameAck* ack) {
+ // TODO(piman): We may need to do some validation on this ack before
+ // processing it.
+ if (ack && renderer_)
+ renderer_->ReceiveSwapBuffersAck(*ack);
+
+ client_->OnSwapBuffersCompleteOnImplThread();
+}
+
+void LayerTreeHostImpl::OnCanDrawStateChangedForTree() {
+ client_->OnCanDrawStateChanged(CanDraw());
+}
+
+CompositorFrameMetadata LayerTreeHostImpl::MakeCompositorFrameMetadata() const {
+ CompositorFrameMetadata metadata;
+ metadata.device_scale_factor = device_scale_factor_;
+ metadata.page_scale_factor = active_tree_->total_page_scale_factor();
+ metadata.viewport_size = active_tree_->ScrollableViewportSize();
+ metadata.root_layer_size = active_tree_->ScrollableSize();
+ metadata.min_page_scale_factor = active_tree_->min_page_scale_factor();
+ metadata.max_page_scale_factor = active_tree_->max_page_scale_factor();
+ metadata.latency_info = active_tree_->GetLatencyInfo();
+ if (top_controls_manager_) {
+ metadata.location_bar_offset =
+ gfx::Vector2dF(0.f, top_controls_manager_->controls_top_offset());
+ metadata.location_bar_content_translation =
+ gfx::Vector2dF(0.f, top_controls_manager_->content_top_offset());
+ metadata.overdraw_bottom_height = overdraw_bottom_height_;
+ }
+
+ if (!RootScrollLayer())
+ return metadata;
+
+ metadata.root_scroll_offset = RootScrollLayer()->TotalScrollOffset();
+
+ return metadata;
+}
+
+bool LayerTreeHostImpl::AllowPartialSwap() const {
+ // We don't track damage on the HUD layer (it interacts with damage tracking
+ // visualizations), so disable partial swaps to make the HUD layer display
+ // properly.
+ return !debug_state_.ShowHudRects();
+}
+
+bool LayerTreeHostImpl::ExternalStencilTestEnabled() const {
+ return external_stencil_test_enabled_;
+}
+
+static void LayerTreeHostImplDidBeginTracingCallback(LayerImpl* layer) {
+ layer->DidBeginTracing();
+}
+
+void LayerTreeHostImpl::DrawLayers(FrameData* frame,
+ base::TimeTicks frame_begin_time) {
+ TRACE_EVENT0("cc", "LayerTreeHostImpl::DrawLayers");
+ DCHECK(CanDraw());
+
+ if (frame->has_no_damage) {
+ TRACE_EVENT0("cc", "EarlyOut_NoDamage");
+ DCHECK(!output_surface_->capabilities()
+ .draw_and_swap_full_viewport_every_frame);
+ return;
+ }
+
+ DCHECK(!frame->render_passes.empty());
+
+ fps_counter_->SaveTimeStamp(frame_begin_time);
+
+ rendering_stats_instrumentation_->SetScreenFrameCount(
+ fps_counter_->current_frame_number());
+ rendering_stats_instrumentation_->SetDroppedFrameCount(
+ fps_counter_->dropped_frame_count());
+
+ if (tile_manager_) {
+ memory_history_->SaveEntry(
+ tile_manager_->memory_stats_from_last_assign());
+ }
+
+ if (debug_state_.ShowHudRects()) {
+ debug_rect_history_->SaveDebugRectsForCurrentFrame(
+ active_tree_->root_layer(),
+ *frame->render_surface_layer_list,
+ frame->occluding_screen_space_rects,
+ frame->non_occluding_screen_space_rects,
+ debug_state_);
+ }
+
+ if (!settings_.impl_side_painting && debug_state_.continuous_painting) {
+ const RenderingStats& stats =
+ rendering_stats_instrumentation_->GetRenderingStats();
+ paint_time_counter_->SavePaintTime(stats.total_paint_time);
+ }
+
+ bool is_new_trace;
+ TRACE_EVENT_IS_NEW_TRACE(&is_new_trace);
+ if (is_new_trace) {
+ if (pending_tree_) {
+ LayerTreeHostCommon::CallFunctionForSubtree(
+ pending_tree_->root_layer(),
+ base::Bind(&LayerTreeHostImplDidBeginTracingCallback));
+ }
+ LayerTreeHostCommon::CallFunctionForSubtree(
+ active_tree_->root_layer(),
+ base::Bind(&LayerTreeHostImplDidBeginTracingCallback));
+ }
+
+ TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(
+ TRACE_DISABLED_BY_DEFAULT("cc.debug") ","
+ TRACE_DISABLED_BY_DEFAULT("cc.debug.quads"), "cc::LayerTreeHostImpl",
+ this, TracedValue::FromValue(AsValueWithFrame(frame).release()));
+
+ // Because the contents of the HUD depend on everything else in the frame, the
+ // contents of its texture are updated as the last thing before the frame is
+ // drawn.
+ if (active_tree_->hud_layer())
+ active_tree_->hud_layer()->UpdateHudTexture(resource_provider_.get());
+
+ if (output_surface_->ForcedDrawToSoftwareDevice()) {
+ scoped_ptr<SoftwareRenderer> temp_software_renderer =
+ SoftwareRenderer::Create(this, output_surface_.get(), NULL);
+ temp_software_renderer->DrawFrame(&frame->render_passes);
+ } else {
+ renderer_->DrawFrame(&frame->render_passes);
+ }
+ // The render passes should be consumed by the renderer.
+ DCHECK(frame->render_passes.empty());
+ frame->render_passes_by_id.clear();
+
+ // The next frame should start by assuming nothing has changed, and changes
+ // are noted as they occur.
+ for (size_t i = 0; i < frame->render_surface_layer_list->size(); i++) {
+ (*frame->render_surface_layer_list)[i]->render_surface()->damage_tracker()->
+ DidDrawDamagedArea();
+ }
+ active_tree_->root_layer()->ResetAllChangeTrackingForSubtree();
+}
+
+void LayerTreeHostImpl::DidDrawAllLayers(const FrameData& frame) {
+ for (size_t i = 0; i < frame.will_draw_layers.size(); ++i)
+ frame.will_draw_layers[i]->DidDraw(resource_provider_.get());
+
+ // Once all layers have been drawn, pending texture uploads should no
+ // longer block future uploads.
+ resource_provider_->MarkPendingUploadsAsNonBlocking();
+}
+
+void LayerTreeHostImpl::FinishAllRendering() {
+ if (renderer_)
+ renderer_->Finish();
+}
+
+bool LayerTreeHostImpl::IsContextLost() {
+ DCHECK(proxy_->IsImplThread());
+ return renderer_ && renderer_->IsContextLost();
+}
+
+const RendererCapabilities& LayerTreeHostImpl::GetRendererCapabilities() const {
+ return renderer_->Capabilities();
+}
+
+bool LayerTreeHostImpl::SwapBuffers(const LayerTreeHostImpl::FrameData& frame) {
+ if (frame.has_no_damage)
+ return false;
+ renderer_->SwapBuffers();
+ active_tree_->ClearLatencyInfo();
+ return true;
+}
+
+void LayerTreeHostImpl::SetNeedsBeginFrame(bool enable) {
+ if (output_surface_)
+ output_surface_->SetNeedsBeginFrame(enable);
+}
+
+float LayerTreeHostImpl::DeviceScaleFactor() const {
+ return device_scale_factor_;
+}
+
+gfx::SizeF LayerTreeHostImpl::VisibleViewportSize() const {
+ // The container layer bounds should be used if non-overlay scrollbars may
+ // exist since it adjusts for them.
+ LayerImpl* container_layer = active_tree_->RootContainerLayer();
+ if (!Settings().solid_color_scrollbars && container_layer) {
+ DCHECK(!top_controls_manager_);
+ DCHECK_EQ(0, overdraw_bottom_height_);
+ return container_layer->bounds();
+ }
+
+ gfx::SizeF dip_size =
+ gfx::ScaleSize(device_viewport_size(), 1.f / device_scale_factor());
+
+ float top_offset =
+ top_controls_manager_ ? top_controls_manager_->content_top_offset() : 0.f;
+ return gfx::SizeF(dip_size.width(),
+ dip_size.height() - top_offset - overdraw_bottom_height_);
+}
+
+const LayerTreeSettings& LayerTreeHostImpl::Settings() const {
+ return settings();
+}
+
+void LayerTreeHostImpl::DidLoseOutputSurface() {
+ // TODO(jamesr): The renderer_ check is needed to make some of the
+ // LayerTreeHostContextTest tests pass, but shouldn't be necessary (or
+ // important) in production. We should adjust the test to not need this.
+ if (renderer_)
+ client_->DidLoseOutputSurfaceOnImplThread();
+}
+
+void LayerTreeHostImpl::Readback(void* pixels,
+ gfx::Rect rect_in_device_viewport) {
+ DCHECK(renderer_);
+ renderer_->GetFramebufferPixels(pixels, rect_in_device_viewport);
+}
+
+bool LayerTreeHostImpl::HaveRootScrollLayer() const {
+ return !!RootScrollLayer();
+}
+
+LayerImpl* LayerTreeHostImpl::RootLayer() const {
+ return active_tree_->root_layer();
+}
+
+LayerImpl* LayerTreeHostImpl::RootScrollLayer() const {
+ return active_tree_->RootScrollLayer();
+}
+
+LayerImpl* LayerTreeHostImpl::CurrentlyScrollingLayer() const {
+ return active_tree_->CurrentlyScrollingLayer();
+}
+
+// Content layers can be either directly scrollable or contained in an outer
+// scrolling layer which applies the scroll transform. Given a content layer,
+// this function returns the associated scroll layer if any.
+static LayerImpl* FindScrollLayerForContentLayer(LayerImpl* layer_impl) {
+ if (!layer_impl)
+ return 0;
+
+ if (layer_impl->scrollable())
+ return layer_impl;
+
+ if (layer_impl->DrawsContent() &&
+ layer_impl->parent() &&
+ layer_impl->parent()->scrollable())
+ return layer_impl->parent();
+
+ return 0;
+}
+
+void LayerTreeHostImpl::CreatePendingTree() {
+ CHECK(!pending_tree_);
+ if (recycle_tree_)
+ recycle_tree_.swap(pending_tree_);
+ else
+ pending_tree_ = LayerTreeImpl::create(this);
+ client_->OnCanDrawStateChanged(CanDraw());
+ client_->OnHasPendingTreeStateChanged(pending_tree_);
+ TRACE_EVENT_ASYNC_BEGIN0("cc", "PendingTree", pending_tree_.get());
+ TRACE_EVENT_ASYNC_STEP0("cc",
+ "PendingTree", pending_tree_.get(), "waiting");
+}
+
+void LayerTreeHostImpl::UpdateVisibleTiles() {
+ DCHECK(!client_->IsInsideDraw()) <<
+ "Updating visible tiles within a draw may trigger "
+ "spurious redraws.";
+ if (tile_manager_ && tile_manager_->UpdateVisibleTiles())
+ DidInitializeVisibleTile();
+
+ need_to_update_visible_tiles_before_draw_ = false;
+}
+
+void LayerTreeHostImpl::ActivatePendingTreeIfNeeded() {
+ DCHECK(pending_tree_);
+ CHECK(settings_.impl_side_painting);
+
+ if (!pending_tree_)
+ return;
+
+ // The tile manager is usually responsible for notifying activation.
+ // If there is no tile manager, then we need to manually activate.
+ if (!tile_manager_ || tile_manager_->AreTilesRequiredForActivationReady()) {
+ ActivatePendingTree();
+ return;
+ }
+
+ // Manage tiles in case state affecting tile priority has changed.
+ ManageTiles();
+
+ TRACE_EVENT_ASYNC_STEP1(
+ "cc",
+ "PendingTree", pending_tree_.get(), "activate",
+ "state", TracedValue::FromValue(ActivationStateAsValue().release()));
+}
+
+void LayerTreeHostImpl::ActivatePendingTree() {
+ CHECK(pending_tree_);
+ TRACE_EVENT_ASYNC_END0("cc", "PendingTree", pending_tree_.get());
+
+ active_tree_->SetRootLayerScrollOffsetDelegate(NULL);
+ active_tree_->PushPersistedState(pending_tree_.get());
+ if (pending_tree_->needs_full_tree_sync()) {
+ active_tree_->SetRootLayer(
+ TreeSynchronizer::SynchronizeTrees(pending_tree_->root_layer(),
+ active_tree_->DetachLayerTree(),
+ active_tree_.get()));
+ }
+ TreeSynchronizer::PushProperties(pending_tree_->root_layer(),
+ active_tree_->root_layer());
+ DCHECK(!recycle_tree_);
+
+ // Process any requests in the UI resource queue. The request queue is given
+ // in LayerTreeHost::FinishCommitOnImplThread. This must take place before
+ // the swap.
+ pending_tree_->ProcessUIResourceRequestQueue();
+
+ pending_tree_->PushPropertiesTo(active_tree_.get());
+
+ // Now that we've synced everything from the pending tree to the active
+ // tree, rename the pending tree the recycle tree so we can reuse it on the
+ // next sync.
+ pending_tree_.swap(recycle_tree_);
+
+ active_tree_->SetRootLayerScrollOffsetDelegate(
+ root_layer_scroll_offset_delegate_);
+ active_tree_->DidBecomeActive();
+
+ // Reduce wasted memory now that unlinked resources are guaranteed not
+ // to be used.
+ client_->ReduceWastedContentsTextureMemoryOnImplThread();
+
+ client_->OnCanDrawStateChanged(CanDraw());
+ client_->OnHasPendingTreeStateChanged(pending_tree_);
+ client_->SetNeedsRedrawOnImplThread();
+ client_->RenewTreePriority();
+
+ if (debug_state_.continuous_painting) {
+ const RenderingStats& stats =
+ rendering_stats_instrumentation_->GetRenderingStats();
+ paint_time_counter_->SavePaintTime(
+ stats.total_paint_time + stats.total_record_time +
+ stats.total_rasterize_time_for_now_bins_on_pending_tree);
+ }
+
+ client_->DidActivatePendingTree();
+ if (!tree_activation_callback_.is_null())
+ tree_activation_callback_.Run();
+}
+
+void LayerTreeHostImpl::SetVisible(bool visible) {
+ DCHECK(proxy_->IsImplThread());
+
+ if (visible_ == visible)
+ return;
+ visible_ = visible;
+ DidVisibilityChange(this, visible_);
+ EnforceManagedMemoryPolicy(ActualManagedMemoryPolicy());
+
+ if (!renderer_)
+ return;
+
+ renderer_->SetVisible(visible);
+}
+
+ManagedMemoryPolicy LayerTreeHostImpl::ActualManagedMemoryPolicy() const {
+ ManagedMemoryPolicy actual = cached_managed_memory_policy_;
+ if (debug_state_.rasterize_only_visible_content) {
+ actual.priority_cutoff_when_not_visible =
+ ManagedMemoryPolicy::CUTOFF_ALLOW_NOTHING;
+ actual.priority_cutoff_when_visible =
+ ManagedMemoryPolicy::CUTOFF_ALLOW_REQUIRED_ONLY;
+ }
+
+ if (zero_budget_) {
+ actual.bytes_limit_when_visible = 0;
+ actual.bytes_limit_when_not_visible = 0;
+ }
+
+ return actual;
+}
+
+size_t LayerTreeHostImpl::memory_allocation_limit_bytes() const {
+ return ActualManagedMemoryPolicy().bytes_limit_when_visible;
+}
+
+void LayerTreeHostImpl::ReleaseTreeResources() {
+ if (active_tree_->root_layer())
+ SendReleaseResourcesRecursive(active_tree_->root_layer());
+ if (pending_tree_ && pending_tree_->root_layer())
+ SendReleaseResourcesRecursive(pending_tree_->root_layer());
+ if (recycle_tree_ && recycle_tree_->root_layer())
+ SendReleaseResourcesRecursive(recycle_tree_->root_layer());
+
+ // Remove all existing maps from UIResourceId to ResourceId.
+ ui_resource_map_.clear();
+}
+
+void LayerTreeHostImpl::CreateAndSetRenderer(
+ OutputSurface* output_surface,
+ ResourceProvider* resource_provider,
+ bool skip_gl_renderer) {
+ DCHECK(!renderer_);
+ if (output_surface->capabilities().delegated_rendering) {
+ renderer_ =
+ DelegatingRenderer::Create(this, output_surface, resource_provider);
+ } else if (output_surface->context3d() && !skip_gl_renderer) {
+ renderer_ = GLRenderer::Create(this,
+ output_surface,
+ resource_provider,
+ settings_.highp_threshold_min,
+ settings_.force_direct_layer_drawing);
+ } else if (output_surface->software_device()) {
+ renderer_ =
+ SoftwareRenderer::Create(this, output_surface, resource_provider);
+ }
+
+ if (renderer_) {
+ renderer_->SetVisible(visible_);
+ SetFullRootLayerDamage();
+ }
+}
+
+void LayerTreeHostImpl::CreateAndSetTileManager(
+ ResourceProvider* resource_provider,
+ bool using_map_image) {
+ DCHECK(settings_.impl_side_painting);
+ DCHECK(resource_provider);
+ tile_manager_ = TileManager::Create(this,
+ resource_provider,
+ settings_.num_raster_threads,
+ rendering_stats_instrumentation_,
+ using_map_image);
+ UpdateTileManagerMemoryPolicy(ActualManagedMemoryPolicy());
+ need_to_update_visible_tiles_before_draw_ = false;
+}
+
+void LayerTreeHostImpl::EnforceZeroBudget(bool zero_budget) {
+ SetManagedMemoryPolicy(cached_managed_memory_policy_, zero_budget);
+}
+
+bool LayerTreeHostImpl::InitializeRenderer(
+ scoped_ptr<OutputSurface> output_surface) {
+ // Since we will create a new resource provider, we cannot continue to use
+ // the old resources (i.e. render_surfaces and texture IDs). Clear them
+ // before we destroy the old resource provider.
+ ReleaseTreeResources();
+ if (resource_provider_)
+ resource_provider_->DidLoseOutputSurface();
+
+ // Note: order is important here.
+ renderer_.reset();
+ tile_manager_.reset();
+ resource_provider_.reset();
+ output_surface_.reset();
+
+ if (!output_surface->BindToClient(this))
+ return false;
+
+ scoped_ptr<ResourceProvider> resource_provider = ResourceProvider::Create(
+ output_surface.get(), settings_.highp_threshold_min);
+ if (!resource_provider)
+ return false;
+
+ if (output_surface->capabilities().deferred_gl_initialization)
+ EnforceZeroBudget(true);
+
+ bool skip_gl_renderer = false;
+ CreateAndSetRenderer(
+ output_surface.get(), resource_provider.get(), skip_gl_renderer);
+
+ if (!renderer_)
+ return false;
+
+ if (settings_.impl_side_painting) {
+ CreateAndSetTileManager(resource_provider.get(),
+ GetRendererCapabilities().using_map_image);
+ }
+
+ // Setup BeginFrameEmulation if it's not supported natively
+ if (!settings_.begin_frame_scheduling_enabled) {
+ const base::TimeDelta display_refresh_interval =
+ base::TimeDelta::FromMicroseconds(
+ base::Time::kMicrosecondsPerSecond /
+ settings_.refresh_rate);
+
+ output_surface->InitializeBeginFrameEmulation(
+ proxy_->ImplThreadTaskRunner(),
+ settings_.throttle_frame_production,
+ display_refresh_interval);
+ }
+
+ int max_frames_pending =
+ output_surface->capabilities().max_frames_pending;
+ if (max_frames_pending <= 0)
+ max_frames_pending = OutputSurface::DEFAULT_MAX_FRAMES_PENDING;
+ output_surface->SetMaxFramesPending(max_frames_pending);
+
+ resource_provider_ = resource_provider.Pass();
+ output_surface_ = output_surface.Pass();
+
+ client_->OnCanDrawStateChanged(CanDraw());
+
+ // See note in LayerTreeImpl::UpdateDrawProperties. Renderer needs
+ // to be initialized to get max texture size.
+ active_tree_->set_needs_update_draw_properties();
+ if (pending_tree_)
+ pending_tree_->set_needs_update_draw_properties();
+
+ return true;
+}
+
+bool LayerTreeHostImpl::DeferredInitialize(
+ scoped_refptr<ContextProvider> offscreen_context_provider) {
+ DCHECK(output_surface_->capabilities().deferred_gl_initialization);
+ DCHECK(settings_.impl_side_painting);
+ DCHECK(settings_.solid_color_scrollbars);
+ DCHECK(output_surface_->context3d());
+
+ ReleaseTreeResources();
+ renderer_.reset();
+ resource_provider_->InitializeGL();
+ bool skip_gl_renderer = false;
+ CreateAndSetRenderer(
+ output_surface_.get(), resource_provider_.get(), skip_gl_renderer);
+
+ bool success = !!renderer_.get();
+ client_->DidTryInitializeRendererOnImplThread(success,
+ offscreen_context_provider);
+ if (success) {
+ EnforceZeroBudget(false);
+ client_->SetNeedsCommitOnImplThread();
+ }
+ return success;
+}
+
+void LayerTreeHostImpl::ReleaseGL() {
+ DCHECK(output_surface_->capabilities().deferred_gl_initialization);
+ DCHECK(settings_.impl_side_painting);
+ DCHECK(settings_.solid_color_scrollbars);
+ DCHECK(output_surface_->context3d());
+
+ ReleaseTreeResources();
+ renderer_.reset();
+ tile_manager_.reset();
+ resource_provider_->InitializeSoftware();
+
+ bool skip_gl_renderer = true;
+ CreateAndSetRenderer(
+ output_surface_.get(), resource_provider_.get(), skip_gl_renderer);
+ DCHECK(renderer_);
+
+ EnforceZeroBudget(true);
+ CreateAndSetTileManager(resource_provider_.get(),
+ GetRendererCapabilities().using_map_image);
+ DCHECK(tile_manager_);
+
+ bool success = true;
+ client_->DidTryInitializeRendererOnImplThread(
+ success, scoped_refptr<ContextProvider>());
+ client_->SetNeedsCommitOnImplThread();
+}
+
+void LayerTreeHostImpl::SetViewportSize(gfx::Size device_viewport_size) {
+ if (device_viewport_size == device_viewport_size_)
+ return;
+
+ if (pending_tree_ && device_viewport_size_ != device_viewport_size)
+ active_tree_->SetViewportSizeInvalid();
+
+ device_viewport_size_ = device_viewport_size;
+
+ UpdateMaxScrollOffset();
+
+ if (renderer_)
+ renderer_->ViewportChanged();
+
+ client_->OnCanDrawStateChanged(CanDraw());
+ SetFullRootLayerDamage();
+}
+
+void LayerTreeHostImpl::SetOverdrawBottomHeight(float overdraw_bottom_height) {
+ if (overdraw_bottom_height == overdraw_bottom_height_)
+ return;
+ overdraw_bottom_height_ = overdraw_bottom_height;
+
+ UpdateMaxScrollOffset();
+ SetFullRootLayerDamage();
+}
+
+void LayerTreeHostImpl::SetDeviceScaleFactor(float device_scale_factor) {
+ if (device_scale_factor == device_scale_factor_)
+ return;
+ device_scale_factor_ = device_scale_factor;
+
+ if (renderer_)
+ renderer_->ViewportChanged();
+
+ UpdateMaxScrollOffset();
+ SetFullRootLayerDamage();
+}
+
+gfx::Rect LayerTreeHostImpl::DeviceViewport() const {
+ if (external_viewport_.IsEmpty())
+ return gfx::Rect(device_viewport_size_);
+
+ return external_viewport_;
+}
+
+const gfx::Transform& LayerTreeHostImpl::DeviceTransform() const {
+ return external_transform_;
+}
+
+void LayerTreeHostImpl::UpdateMaxScrollOffset() {
+ active_tree_->UpdateMaxScrollOffset();
+}
+
+void LayerTreeHostImpl::DidChangeTopControlsPosition() {
+ client_->SetNeedsRedrawOnImplThread();
+ active_tree_->set_needs_update_draw_properties();
+ SetFullRootLayerDamage();
+}
+
+bool LayerTreeHostImpl::EnsureRenderSurfaceLayerList() {
+ active_tree_->UpdateDrawProperties();
+ return !active_tree_->RenderSurfaceLayerList().empty();
+}
+
+void LayerTreeHostImpl::BindToClient(InputHandlerClient* client) {
+ DCHECK(input_handler_client_ == NULL);
+ input_handler_client_ = client;
+}
+
+InputHandler::ScrollStatus LayerTreeHostImpl::ScrollBegin(
+ gfx::Point viewport_point, InputHandler::ScrollInputType type) {
+ TRACE_EVENT0("cc", "LayerTreeHostImpl::ScrollBegin");
+
+ if (top_controls_manager_)
+ top_controls_manager_->ScrollBegin();
+
+ DCHECK(!CurrentlyScrollingLayer());
+ ClearCurrentlyScrollingLayer();
+
+ if (!EnsureRenderSurfaceLayerList())
+ return ScrollIgnored;
+
+ gfx::PointF device_viewport_point = gfx::ScalePoint(viewport_point,
+ device_scale_factor_);
+
+ // First find out which layer was hit from the saved list of visible layers
+ // in the most recent frame.
+ LayerImpl* layer_impl = LayerTreeHostCommon::FindLayerThatIsHitByPoint(
+ device_viewport_point, active_tree_->RenderSurfaceLayerList());
+
+ // Walk up the hierarchy and look for a scrollable layer.
+ LayerImpl* potentially_scrolling_layer_impl = 0;
+ for (; layer_impl; layer_impl = layer_impl->parent()) {
+ // The content layer can also block attempts to scroll outside the main
+ // thread.
+ ScrollStatus status = layer_impl->TryScroll(device_viewport_point, type);
+ if (status == ScrollOnMainThread) {
+ rendering_stats_instrumentation_->IncrementMainThreadScrolls();
+ UMA_HISTOGRAM_BOOLEAN("TryScroll.SlowScroll", true);
+ return ScrollOnMainThread;
+ }
+
+ LayerImpl* scroll_layer_impl = FindScrollLayerForContentLayer(layer_impl);
+ if (!scroll_layer_impl)
+ continue;
+
+ status = scroll_layer_impl->TryScroll(device_viewport_point, type);
+
+ // If any layer wants to divert the scroll event to the main thread, abort.
+ if (status == ScrollOnMainThread) {
+ rendering_stats_instrumentation_->IncrementMainThreadScrolls();
+ UMA_HISTOGRAM_BOOLEAN("TryScroll.SlowScroll", true);
+ return ScrollOnMainThread;
+ }
+
+ if (status == ScrollStarted && !potentially_scrolling_layer_impl)
+ potentially_scrolling_layer_impl = scroll_layer_impl;
+ }
+
+ // When hiding top controls is enabled and the controls are hidden or
+ // overlaying the content, force scrolls to be enabled on the root layer to
+ // allow bringing the top controls back into view.
+ if (!potentially_scrolling_layer_impl && top_controls_manager_ &&
+ top_controls_manager_->content_top_offset() !=
+ settings_.top_controls_height) {
+ potentially_scrolling_layer_impl = RootScrollLayer();
+ }
+
+ if (potentially_scrolling_layer_impl) {
+ active_tree_->SetCurrentlyScrollingLayer(
+ potentially_scrolling_layer_impl);
+ should_bubble_scrolls_ = (type != NonBubblingGesture);
+ wheel_scrolling_ = (type == Wheel);
+ rendering_stats_instrumentation_->IncrementImplThreadScrolls();
+ client_->RenewTreePriority();
+ UMA_HISTOGRAM_BOOLEAN("TryScroll.SlowScroll", false);
+ return ScrollStarted;
+ }
+ return ScrollIgnored;
+}
+
+gfx::Vector2dF LayerTreeHostImpl::ScrollLayerWithViewportSpaceDelta(
+ LayerImpl* layer_impl,
+ float scale_from_viewport_to_screen_space,
+ gfx::PointF viewport_point,
+ gfx::Vector2dF viewport_delta) {
+ // Layers with non-invertible screen space transforms should not have passed
+ // the scroll hit test in the first place.
+ DCHECK(layer_impl->screen_space_transform().IsInvertible());
+ gfx::Transform inverse_screen_space_transform(
+ gfx::Transform::kSkipInitialization);
+ bool did_invert = layer_impl->screen_space_transform().GetInverse(
+ &inverse_screen_space_transform);
+ // TODO(shawnsingh): With the advent of impl-side crolling for non-root
+ // layers, we may need to explicitly handle uninvertible transforms here.
+ DCHECK(did_invert);
+
+ gfx::PointF screen_space_point =
+ gfx::ScalePoint(viewport_point, scale_from_viewport_to_screen_space);
+
+ gfx::Vector2dF screen_space_delta = viewport_delta;
+ screen_space_delta.Scale(scale_from_viewport_to_screen_space);
+
+ // First project the scroll start and end points to local layer space to find
+ // the scroll delta in layer coordinates.
+ bool start_clipped, end_clipped;
+ gfx::PointF screen_space_end_point = screen_space_point + screen_space_delta;
+ gfx::PointF local_start_point =
+ MathUtil::ProjectPoint(inverse_screen_space_transform,
+ screen_space_point,
+ &start_clipped);
+ gfx::PointF local_end_point =
+ MathUtil::ProjectPoint(inverse_screen_space_transform,
+ screen_space_end_point,
+ &end_clipped);
+
+ // In general scroll point coordinates should not get clipped.
+ DCHECK(!start_clipped);
+ DCHECK(!end_clipped);
+ if (start_clipped || end_clipped)
+ return gfx::Vector2dF();
+
+ // local_start_point and local_end_point are in content space but we want to
+ // move them to layer space for scrolling.
+ float width_scale = 1.f / layer_impl->contents_scale_x();
+ float height_scale = 1.f / layer_impl->contents_scale_y();
+ local_start_point.Scale(width_scale, height_scale);
+ local_end_point.Scale(width_scale, height_scale);
+
+ // Apply the scroll delta.
+ gfx::Vector2dF previous_delta = layer_impl->ScrollDelta();
+ layer_impl->ScrollBy(local_end_point - local_start_point);
+
+ // Get the end point in the layer's content space so we can apply its
+ // ScreenSpaceTransform.
+ gfx::PointF actual_local_end_point = local_start_point +
+ layer_impl->ScrollDelta() -
+ previous_delta;
+ gfx::PointF actual_local_content_end_point =
+ gfx::ScalePoint(actual_local_end_point,
+ 1.f / width_scale,
+ 1.f / height_scale);
+
+ // Calculate the applied scroll delta in viewport space coordinates.
+ gfx::PointF actual_screen_space_end_point =
+ MathUtil::MapPoint(layer_impl->screen_space_transform(),
+ actual_local_content_end_point,
+ &end_clipped);
+ DCHECK(!end_clipped);
+ if (end_clipped)
+ return gfx::Vector2dF();
+ gfx::PointF actual_viewport_end_point =
+ gfx::ScalePoint(actual_screen_space_end_point,
+ 1.f / scale_from_viewport_to_screen_space);
+ return actual_viewport_end_point - viewport_point;
+}
+
+static gfx::Vector2dF ScrollLayerWithLocalDelta(LayerImpl* layer_impl,
+ gfx::Vector2dF local_delta) {
+ gfx::Vector2dF previous_delta(layer_impl->ScrollDelta());
+ layer_impl->ScrollBy(local_delta);
+ return layer_impl->ScrollDelta() - previous_delta;
+}
+
+bool LayerTreeHostImpl::ScrollBy(gfx::Point viewport_point,
+ gfx::Vector2dF scroll_delta) {
+ TRACE_EVENT0("cc", "LayerTreeHostImpl::ScrollBy");
+ if (!CurrentlyScrollingLayer())
+ return false;
+
+ gfx::Vector2dF pending_delta = scroll_delta;
+ gfx::Vector2dF unused_root_delta;
+ bool did_scroll_x = false;
+ bool did_scroll_y = false;
+ bool consume_by_top_controls = top_controls_manager_ &&
+ (CurrentlyScrollingLayer() == RootScrollLayer() || scroll_delta.y() < 0);
+
+ for (LayerImpl* layer_impl = CurrentlyScrollingLayer();
+ layer_impl;
+ layer_impl = layer_impl->parent()) {
+ if (!layer_impl->scrollable())
+ continue;
+
+ if (layer_impl == RootScrollLayer()) {
+ // Only allow bubble scrolling when the scroll is in the direction to make
+ // the top controls visible.
+ if (consume_by_top_controls && layer_impl == RootScrollLayer()) {
+ pending_delta = top_controls_manager_->ScrollBy(pending_delta);
+ UpdateMaxScrollOffset();
+ }
+ // Track root layer deltas for reporting overscroll.
+ unused_root_delta = pending_delta;
+ }
+
+ gfx::Vector2dF applied_delta;
+ // Gesture events need to be transformed from viewport coordinates to local
+ // layer coordinates so that the scrolling contents exactly follow the
+ // user's finger. In contrast, wheel events represent a fixed amount of
+ // scrolling so we can just apply them directly.
+ if (!wheel_scrolling_) {
+ float scale_from_viewport_to_screen_space = device_scale_factor_;
+ applied_delta =
+ ScrollLayerWithViewportSpaceDelta(layer_impl,
+ scale_from_viewport_to_screen_space,
+ viewport_point, pending_delta);
+ } else {
+ applied_delta = ScrollLayerWithLocalDelta(layer_impl, pending_delta);
+ }
+
+ // If the layer wasn't able to move, try the next one in the hierarchy.
+ float move_threshold = 0.1f;
+ bool did_move_layer_x = std::abs(applied_delta.x()) > move_threshold;
+ bool did_move_layer_y = std::abs(applied_delta.y()) > move_threshold;
+ did_scroll_x |= did_move_layer_x;
+ did_scroll_y |= did_move_layer_y;
+ if (!did_move_layer_x && !did_move_layer_y) {
+ if (should_bubble_scrolls_ || !did_lock_scrolling_layer_)
+ continue;
+ else
+ break;
+ }
+
+ if (layer_impl == RootScrollLayer())
+ unused_root_delta.Subtract(applied_delta);
+
+ did_lock_scrolling_layer_ = true;
+ if (!should_bubble_scrolls_) {
+ active_tree_->SetCurrentlyScrollingLayer(layer_impl);
+ break;
+ }
+
+ // If the applied delta is within 45 degrees of the input delta, bail out to
+ // make it easier to scroll just one layer in one direction without
+ // affecting any of its parents.
+ float angle_threshold = 45;
+ if (MathUtil::SmallestAngleBetweenVectors(
+ applied_delta, pending_delta) < angle_threshold) {
+ pending_delta = gfx::Vector2d();
+ break;
+ }
+
+ // Allow further movement only on an axis perpendicular to the direction in
+ // which the layer moved.
+ gfx::Vector2dF perpendicular_axis(-applied_delta.y(), applied_delta.x());
+ pending_delta = MathUtil::ProjectVector(pending_delta, perpendicular_axis);
+
+ if (gfx::ToRoundedVector2d(pending_delta).IsZero())
+ break;
+ }
+
+ bool did_scroll = did_scroll_x || did_scroll_y;
+ if (did_scroll) {
+ client_->SetNeedsCommitOnImplThread();
+ client_->SetNeedsRedrawOnImplThread();
+ client_->RenewTreePriority();
+ }
+
+ // Scrolling along an axis resets accumulated root overscroll for that axis.
+ if (did_scroll_x)
+ accumulated_root_overscroll_.set_x(0);
+ if (did_scroll_y)
+ accumulated_root_overscroll_.set_y(0);
+
+ accumulated_root_overscroll_ += unused_root_delta;
+ bool did_overscroll = !gfx::ToRoundedVector2d(unused_root_delta).IsZero();
+ if (did_overscroll && input_handler_client_) {
+ DidOverscrollParams params;
+ params.accumulated_overscroll = accumulated_root_overscroll_;
+ params.latest_overscroll_delta = unused_root_delta;
+ params.current_fling_velocity = current_fling_velocity_;
+ input_handler_client_->DidOverscroll(params);
+ }
+
+ return did_scroll;
+}
+
+// This implements scrolling by page as described here:
+// http://msdn.microsoft.com/en-us/library/windows/desktop/ms645601(v=vs.85).aspx#_win32_The_Mouse_Wheel
+// for events with WHEEL_PAGESCROLL set.
+bool LayerTreeHostImpl::ScrollVerticallyByPage(gfx::Point viewport_point,
+ ScrollDirection direction) {
+ DCHECK(wheel_scrolling_);
+
+ for (LayerImpl* layer_impl = CurrentlyScrollingLayer();
+ layer_impl;
+ layer_impl = layer_impl->parent()) {
+ if (!layer_impl->scrollable())
+ continue;
+
+ if (!layer_impl->vertical_scrollbar_layer())
+ continue;
+
+ float height = layer_impl->vertical_scrollbar_layer()->bounds().height();
+
+ // These magical values match WebKit and are designed to scroll nearly the
+ // entire visible content height but leave a bit of overlap.
+ float page = std::max(height * 0.875f, 1.f);
+ if (direction == SCROLL_BACKWARD)
+ page = -page;
+
+ gfx::Vector2dF delta = gfx::Vector2dF(0.f, page);
+
+ gfx::Vector2dF applied_delta = ScrollLayerWithLocalDelta(layer_impl, delta);
+
+ if (!applied_delta.IsZero()) {
+ client_->SetNeedsCommitOnImplThread();
+ client_->SetNeedsRedrawOnImplThread();
+ client_->RenewTreePriority();
+ return true;
+ }
+
+ active_tree_->SetCurrentlyScrollingLayer(layer_impl);
+ }
+
+ return false;
+}
+
+void LayerTreeHostImpl::SetRootLayerScrollOffsetDelegate(
+ LayerScrollOffsetDelegate* root_layer_scroll_offset_delegate) {
+ root_layer_scroll_offset_delegate_ = root_layer_scroll_offset_delegate;
+ active_tree_->SetRootLayerScrollOffsetDelegate(
+ root_layer_scroll_offset_delegate_);
+}
+
+void LayerTreeHostImpl::OnRootLayerDelegatedScrollOffsetChanged() {
+ DCHECK(root_layer_scroll_offset_delegate_ != NULL);
+ client_->SetNeedsCommitOnImplThread();
+}
+
+void LayerTreeHostImpl::ClearCurrentlyScrollingLayer() {
+ active_tree_->ClearCurrentlyScrollingLayer();
+ did_lock_scrolling_layer_ = false;
+ accumulated_root_overscroll_ = gfx::Vector2dF();
+ current_fling_velocity_ = gfx::Vector2dF();
+}
+
+void LayerTreeHostImpl::ScrollEnd() {
+ if (top_controls_manager_)
+ top_controls_manager_->ScrollEnd();
+ ClearCurrentlyScrollingLayer();
+ StartScrollbarAnimation();
+}
+
+InputHandler::ScrollStatus LayerTreeHostImpl::FlingScrollBegin() {
+ if (!active_tree_->CurrentlyScrollingLayer())
+ return ScrollIgnored;
+
+ if (settings_.ignore_root_layer_flings &&
+ active_tree_->CurrentlyScrollingLayer() ==
+ active_tree_->RootScrollLayer()) {
+ ClearCurrentlyScrollingLayer();
+ return ScrollIgnored;
+ }
+
+ return ScrollStarted;
+}
+
+void LayerTreeHostImpl::NotifyCurrentFlingVelocity(gfx::Vector2dF velocity) {
+ current_fling_velocity_ = velocity;
+}
+
+void LayerTreeHostImpl::PinchGestureBegin() {
+ pinch_gesture_active_ = true;
+ previous_pinch_anchor_ = gfx::Point();
+ client_->RenewTreePriority();
+ active_tree_->SetCurrentlyScrollingLayer(RootScrollLayer());
+}
+
+void LayerTreeHostImpl::PinchGestureUpdate(float magnify_delta,
+ gfx::Point anchor) {
+ TRACE_EVENT0("cc", "LayerTreeHostImpl::PinchGestureUpdate");
+
+ if (!RootScrollLayer())
+ return;
+
+ // Keep the center-of-pinch anchor specified by (x, y) in a stable
+ // position over the course of the magnify.
+ float page_scale_delta = active_tree_->page_scale_delta();
+ gfx::PointF previous_scale_anchor =
+ gfx::ScalePoint(anchor, 1.f / page_scale_delta);
+ active_tree_->SetPageScaleDelta(page_scale_delta * magnify_delta);
+ page_scale_delta = active_tree_->page_scale_delta();
+ gfx::PointF new_scale_anchor =
+ gfx::ScalePoint(anchor, 1.f / page_scale_delta);
+ gfx::Vector2dF move = previous_scale_anchor - new_scale_anchor;
+
+ previous_pinch_anchor_ = anchor;
+
+ move.Scale(1 / active_tree_->page_scale_factor());
+
+ RootScrollLayer()->ScrollBy(move);
+
+ client_->SetNeedsCommitOnImplThread();
+ client_->SetNeedsRedrawOnImplThread();
+ client_->RenewTreePriority();
+}
+
+void LayerTreeHostImpl::PinchGestureEnd() {
+ pinch_gesture_active_ = false;
+ client_->SetNeedsCommitOnImplThread();
+}
+
+static void CollectScrollDeltas(ScrollAndScaleSet* scroll_info,
+ LayerImpl* layer_impl) {
+ if (!layer_impl)
+ return;
+
+ gfx::Vector2d scroll_delta =
+ gfx::ToFlooredVector2d(layer_impl->ScrollDelta());
+ if (!scroll_delta.IsZero()) {
+ LayerTreeHostCommon::ScrollUpdateInfo scroll;
+ scroll.layer_id = layer_impl->id();
+ scroll.scroll_delta = scroll_delta;
+ scroll_info->scrolls.push_back(scroll);
+ layer_impl->SetSentScrollDelta(scroll_delta);
+ }
+
+ for (size_t i = 0; i < layer_impl->children().size(); ++i)
+ CollectScrollDeltas(scroll_info, layer_impl->children()[i]);
+}
+
+scoped_ptr<ScrollAndScaleSet> LayerTreeHostImpl::ProcessScrollDeltas() {
+ scoped_ptr<ScrollAndScaleSet> scroll_info(new ScrollAndScaleSet());
+
+ CollectScrollDeltas(scroll_info.get(), active_tree_->root_layer());
+ scroll_info->page_scale_delta = active_tree_->page_scale_delta();
+ active_tree_->set_sent_page_scale_delta(scroll_info->page_scale_delta);
+
+ return scroll_info.Pass();
+}
+
+void LayerTreeHostImpl::SetFullRootLayerDamage() {
+ SetViewportDamage(gfx::Rect(device_viewport_size_));
+}
+
+void LayerTreeHostImpl::AnimatePageScale(base::TimeTicks time) {
+ if (!page_scale_animation_ || !RootScrollLayer())
+ return;
+
+ double monotonic_time = (time - base::TimeTicks()).InSecondsF();
+ gfx::Vector2dF scroll_total = RootScrollLayer()->scroll_offset() +
+ RootScrollLayer()->ScrollDelta();
+
+ active_tree_->SetPageScaleDelta(
+ page_scale_animation_->PageScaleFactorAtTime(monotonic_time) /
+ active_tree_->page_scale_factor());
+ gfx::Vector2dF next_scroll =
+ page_scale_animation_->ScrollOffsetAtTime(monotonic_time);
+
+ RootScrollLayer()->ScrollBy(next_scroll - scroll_total);
+ client_->SetNeedsRedrawOnImplThread();
+
+ if (page_scale_animation_->IsAnimationCompleteAtTime(monotonic_time)) {
+ page_scale_animation_.reset();
+ client_->SetNeedsCommitOnImplThread();
+ client_->RenewTreePriority();
+ }
+}
+
+void LayerTreeHostImpl::AnimateTopControls(base::TimeTicks time) {
+ if (!top_controls_manager_ || !RootScrollLayer())
+ return;
+ gfx::Vector2dF scroll = top_controls_manager_->Animate(time);
+ UpdateMaxScrollOffset();
+ if (RootScrollLayer()->TotalScrollOffset().y() == 0.f)
+ return;
+ RootScrollLayer()->ScrollBy(gfx::ScaleVector2d(
+ scroll, 1.f / active_tree_->total_page_scale_factor()));
+}
+
+void LayerTreeHostImpl::AnimateLayers(base::TimeTicks monotonic_time,
+ base::Time wall_clock_time) {
+ if (!settings_.accelerated_animation_enabled ||
+ animation_registrar_->active_animation_controllers().empty() ||
+ !active_tree_->root_layer())
+ return;
+
+ TRACE_EVENT0("cc", "LayerTreeHostImpl::AnimateLayers");
+
+ last_animation_time_ = wall_clock_time;
+ double monotonic_seconds = (monotonic_time - base::TimeTicks()).InSecondsF();
+
+ AnimationRegistrar::AnimationControllerMap copy =
+ animation_registrar_->active_animation_controllers();
+ for (AnimationRegistrar::AnimationControllerMap::iterator iter = copy.begin();
+ iter != copy.end();
+ ++iter)
+ (*iter).second->Animate(monotonic_seconds);
+
+ client_->SetNeedsRedrawOnImplThread();
+}
+
+void LayerTreeHostImpl::UpdateAnimationState(bool start_ready_animations) {
+ if (!settings_.accelerated_animation_enabled ||
+ animation_registrar_->active_animation_controllers().empty() ||
+ !active_tree_->root_layer())
+ return;
+
+ TRACE_EVENT0("cc", "LayerTreeHostImpl::UpdateAnimationState");
+ scoped_ptr<AnimationEventsVector> events =
+ make_scoped_ptr(new AnimationEventsVector);
+ AnimationRegistrar::AnimationControllerMap copy =
+ animation_registrar_->active_animation_controllers();
+ for (AnimationRegistrar::AnimationControllerMap::iterator iter = copy.begin();
+ iter != copy.end();
+ ++iter)
+ (*iter).second->UpdateState(start_ready_animations, events.get());
+
+ if (!events->empty()) {
+ client_->PostAnimationEventsToMainThreadOnImplThread(events.Pass(),
+ last_animation_time_);
+ }
+}
+
+base::TimeDelta LayerTreeHostImpl::LowFrequencyAnimationInterval() const {
+ return base::TimeDelta::FromSeconds(1);
+}
+
+void LayerTreeHostImpl::SendReleaseResourcesRecursive(LayerImpl* current) {
+ DCHECK(current);
+ // TODO(boliu): Rename DidLoseOutputSurface to ReleaseResources.
+ current->DidLoseOutputSurface();
+ if (current->mask_layer())
+ SendReleaseResourcesRecursive(current->mask_layer());
+ if (current->replica_layer())
+ SendReleaseResourcesRecursive(current->replica_layer());
+ for (size_t i = 0; i < current->children().size(); ++i)
+ SendReleaseResourcesRecursive(current->children()[i]);
+}
+
+std::string LayerTreeHostImpl::LayerTreeAsJson() const {
+ std::string str;
+ if (active_tree_->root_layer()) {
+ scoped_ptr<base::Value> json(active_tree_->root_layer()->LayerTreeAsJson());
+ base::JSONWriter::WriteWithOptions(
+ json.get(), base::JSONWriter::OPTIONS_PRETTY_PRINT, &str);
+ }
+ return str;
+}
+
+int LayerTreeHostImpl::SourceAnimationFrameNumber() const {
+ return fps_counter_->current_frame_number();
+}
+
+void LayerTreeHostImpl::SendManagedMemoryStats(
+ size_t memory_visible_bytes,
+ size_t memory_visible_and_nearby_bytes,
+ size_t memory_use_bytes) {
+ if (!renderer_)
+ return;
+
+ // Round the numbers being sent up to the next 8MB, to throttle the rate
+ // at which we spam the GPU process.
+ static const size_t rounding_step = 8 * 1024 * 1024;
+ memory_visible_bytes = RoundUp(memory_visible_bytes, rounding_step);
+ memory_visible_and_nearby_bytes = RoundUp(memory_visible_and_nearby_bytes,
+ rounding_step);
+ memory_use_bytes = RoundUp(memory_use_bytes, rounding_step);
+ if (last_sent_memory_visible_bytes_ == memory_visible_bytes &&
+ last_sent_memory_visible_and_nearby_bytes_ ==
+ memory_visible_and_nearby_bytes &&
+ last_sent_memory_use_bytes_ == memory_use_bytes) {
+ return;
+ }
+ last_sent_memory_visible_bytes_ = memory_visible_bytes;
+ last_sent_memory_visible_and_nearby_bytes_ = memory_visible_and_nearby_bytes;
+ last_sent_memory_use_bytes_ = memory_use_bytes;
+
+ renderer_->SendManagedMemoryStats(last_sent_memory_visible_bytes_,
+ last_sent_memory_visible_and_nearby_bytes_,
+ last_sent_memory_use_bytes_);
+}
+
+void LayerTreeHostImpl::AnimateScrollbars(base::TimeTicks time) {
+ AnimateScrollbarsRecursive(active_tree_->root_layer(), time);
+}
+
+void LayerTreeHostImpl::AnimateScrollbarsRecursive(LayerImpl* layer,
+ base::TimeTicks time) {
+ if (!layer)
+ return;
+
+ ScrollbarAnimationController* scrollbar_controller =
+ layer->scrollbar_animation_controller();
+ if (scrollbar_controller && scrollbar_controller->Animate(time)) {
+ TRACE_EVENT_INSTANT0(
+ "cc", "LayerTreeHostImpl::SetNeedsRedraw due to AnimateScrollbars",
+ TRACE_EVENT_SCOPE_THREAD);
+ client_->SetNeedsRedrawOnImplThread();
+ }
+
+ for (size_t i = 0; i < layer->children().size(); ++i)
+ AnimateScrollbarsRecursive(layer->children()[i], time);
+}
+
+void LayerTreeHostImpl::StartScrollbarAnimation() {
+ TRACE_EVENT0("cc", "LayerTreeHostImpl::StartScrollbarAnimation");
+ StartScrollbarAnimationRecursive(RootLayer(), CurrentPhysicalTimeTicks());
+}
+
+void LayerTreeHostImpl::StartScrollbarAnimationRecursive(LayerImpl* layer,
+ base::TimeTicks time) {
+ if (!layer)
+ return;
+
+ ScrollbarAnimationController* scrollbar_controller =
+ layer->scrollbar_animation_controller();
+ if (scrollbar_controller && scrollbar_controller->IsAnimating()) {
+ base::TimeDelta delay = scrollbar_controller->DelayBeforeStart(time);
+ if (delay > base::TimeDelta())
+ client_->RequestScrollbarAnimationOnImplThread(delay);
+ else if (scrollbar_controller->Animate(time))
+ client_->SetNeedsRedrawOnImplThread();
+ }
+
+ for (size_t i = 0; i < layer->children().size(); ++i)
+ StartScrollbarAnimationRecursive(layer->children()[i], time);
+}
+
+void LayerTreeHostImpl::SetTreePriority(TreePriority priority) {
+ if (!tile_manager_)
+ return;
+
+ GlobalStateThatImpactsTilePriority new_state(tile_manager_->GlobalState());
+ if (new_state.tree_priority == priority)
+ return;
+
+ new_state.tree_priority = priority;
+ tile_manager_->SetGlobalState(new_state);
+ manage_tiles_needed_ = true;
+}
+
+void LayerTreeHostImpl::ResetCurrentFrameTimeForNextFrame() {
+ current_frame_timeticks_ = base::TimeTicks();
+ current_frame_time_ = base::Time();
+}
+
+void LayerTreeHostImpl::UpdateCurrentFrameTime(base::TimeTicks* ticks,
+ base::Time* now) const {
+ if (ticks->is_null()) {
+ DCHECK(now->is_null());
+ *ticks = CurrentPhysicalTimeTicks();
+ *now = base::Time::Now();
+ }
+}
+
+base::TimeTicks LayerTreeHostImpl::CurrentFrameTimeTicks() {
+ UpdateCurrentFrameTime(&current_frame_timeticks_, &current_frame_time_);
+ return current_frame_timeticks_;
+}
+
+base::Time LayerTreeHostImpl::CurrentFrameTime() {
+ UpdateCurrentFrameTime(&current_frame_timeticks_, &current_frame_time_);
+ return current_frame_time_;
+}
+
+base::TimeTicks LayerTreeHostImpl::CurrentPhysicalTimeTicks() const {
+ return base::TimeTicks::Now();
+}
+
+scoped_ptr<base::Value> LayerTreeHostImpl::AsValueWithFrame(
+ FrameData* frame) const {
+ scoped_ptr<base::DictionaryValue> state(new base::DictionaryValue());
+ if (this->pending_tree_)
+ state->Set("activation_state", ActivationStateAsValue().release());
+ state->Set("device_viewport_size",
+ MathUtil::AsValue(device_viewport_size_).release());
+ if (tile_manager_)
+ state->Set("tiles", tile_manager_->AllTilesAsValue().release());
+ state->Set("active_tree", active_tree_->AsValue().release());
+ if (pending_tree_)
+ state->Set("pending_tree", pending_tree_->AsValue().release());
+ if (frame)
+ state->Set("frame", frame->AsValue().release());
+ return state.PassAs<base::Value>();
+}
+
+scoped_ptr<base::Value> LayerTreeHostImpl::ActivationStateAsValue() const {
+ scoped_ptr<base::DictionaryValue> state(new base::DictionaryValue());
+ state->Set("lthi", TracedValue::CreateIDRef(this).release());
+ if (tile_manager_)
+ state->Set("tile_manager", tile_manager_->BasicStateAsValue().release());
+ return state.PassAs<base::Value>();
+}
+
+void LayerTreeHostImpl::SetDebugState(
+ const LayerTreeDebugState& new_debug_state) {
+ if (LayerTreeDebugState::Equal(debug_state_, new_debug_state))
+ return;
+ if (debug_state_.continuous_painting != new_debug_state.continuous_painting)
+ paint_time_counter_->ClearHistory();
+
+ debug_state_ = new_debug_state;
+ SetFullRootLayerDamage();
+}
+
+void LayerTreeHostImpl::CreateUIResource(
+ UIResourceId uid,
+ scoped_refptr<UIResourceBitmap> bitmap) {
+ DCHECK_GT(uid, 0);
+ DCHECK_EQ(bitmap->GetFormat(), UIResourceBitmap::RGBA8);
+
+ // Allow for multiple creation requests with the same UIResourceId. The
+ // previous resource is simply deleted.
+ ResourceProvider::ResourceId id = ResourceIdForUIResource(uid);
+ if (id)
+ DeleteUIResource(uid);
+ id = resource_provider_->CreateResource(
+ bitmap->GetSize(),
+ resource_provider_->best_texture_format(),
+ ResourceProvider::TextureUsageAny);
+
+ ui_resource_map_[uid] = id;
+ resource_provider_->SetPixels(id,
+ reinterpret_cast<uint8_t*>(bitmap->GetPixels()),
+ gfx::Rect(bitmap->GetSize()),
+ gfx::Rect(bitmap->GetSize()),
+ gfx::Vector2d(0, 0));
+}
+
+void LayerTreeHostImpl::DeleteUIResource(UIResourceId uid) {
+ ResourceProvider::ResourceId id = ResourceIdForUIResource(uid);
+ if (id) {
+ resource_provider_->DeleteResource(id);
+ ui_resource_map_.erase(uid);
+ }
+}
+
+ResourceProvider::ResourceId LayerTreeHostImpl::ResourceIdForUIResource(
+ UIResourceId uid) const {
+ UIResourceMap::const_iterator iter = ui_resource_map_.find(uid);
+ if (iter != ui_resource_map_.end())
+ return iter->second;
+ return 0;
+}
+
+} // namespace cc
diff --git a/chromium/cc/trees/layer_tree_host_impl.h b/chromium/cc/trees/layer_tree_host_impl.h
new file mode 100644
index 00000000000..31870ac48bd
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_host_impl.h
@@ -0,0 +1,566 @@
+// Copyright 2011 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.
+
+#ifndef CC_TREES_LAYER_TREE_HOST_IMPL_H_
+#define CC_TREES_LAYER_TREE_HOST_IMPL_H_
+
+#include <list>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "cc/animation/animation_events.h"
+#include "cc/animation/animation_registrar.h"
+#include "cc/base/cc_export.h"
+#include "cc/input/input_handler.h"
+#include "cc/input/layer_scroll_offset_delegate.h"
+#include "cc/input/top_controls_manager_client.h"
+#include "cc/layers/layer_lists.h"
+#include "cc/layers/render_pass_sink.h"
+#include "cc/output/begin_frame_args.h"
+#include "cc/output/managed_memory_policy.h"
+#include "cc/output/output_surface_client.h"
+#include "cc/output/renderer.h"
+#include "cc/quads/render_pass.h"
+#include "cc/resources/resource_provider.h"
+#include "cc/resources/tile_manager.h"
+#include "skia/ext/refptr.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/gfx/rect.h"
+
+namespace cc {
+
+class CompletionEvent;
+class CompositorFrameMetadata;
+class DebugRectHistory;
+class FrameRateCounter;
+class LayerImpl;
+class LayerTreeHostImplTimeSourceAdapter;
+class LayerTreeImpl;
+class PageScaleAnimation;
+class PaintTimeCounter;
+class MemoryHistory;
+class RenderingStatsInstrumentation;
+class RenderPassDrawQuad;
+class TopControlsManager;
+class UIResourceBitmap;
+struct RendererCapabilities;
+struct UIResourceRequest;
+
+// LayerTreeHost->Proxy callback interface.
+class LayerTreeHostImplClient {
+ public:
+ virtual void DidTryInitializeRendererOnImplThread(
+ bool success,
+ scoped_refptr<ContextProvider> offscreen_context_provider) = 0;
+ virtual void DidLoseOutputSurfaceOnImplThread() = 0;
+ virtual void OnSwapBuffersCompleteOnImplThread() = 0;
+ virtual void BeginFrameOnImplThread(const BeginFrameArgs& args) = 0;
+ virtual void OnCanDrawStateChanged(bool can_draw) = 0;
+ virtual void OnHasPendingTreeStateChanged(bool has_pending_tree) = 0;
+ virtual void SetNeedsRedrawOnImplThread() = 0;
+ virtual void SetNeedsRedrawRectOnImplThread(gfx::Rect damage_rect) = 0;
+ virtual void DidInitializeVisibleTileOnImplThread() = 0;
+ virtual void SetNeedsCommitOnImplThread() = 0;
+ virtual void PostAnimationEventsToMainThreadOnImplThread(
+ scoped_ptr<AnimationEventsVector> events,
+ base::Time wall_clock_time) = 0;
+ // Returns true if resources were deleted by this call.
+ virtual bool ReduceContentsTextureMemoryOnImplThread(
+ size_t limit_bytes,
+ int priority_cutoff) = 0;
+ virtual void ReduceWastedContentsTextureMemoryOnImplThread() = 0;
+ virtual void SendManagedMemoryStats() = 0;
+ virtual bool IsInsideDraw() = 0;
+ virtual void RenewTreePriority() = 0;
+ virtual void RequestScrollbarAnimationOnImplThread(base::TimeDelta delay) = 0;
+ virtual void DidActivatePendingTree() = 0;
+
+ protected:
+ virtual ~LayerTreeHostImplClient() {}
+};
+
+// LayerTreeHostImpl owns the LayerImpl trees as well as associated rendering
+// state.
+class CC_EXPORT LayerTreeHostImpl
+ : public InputHandler,
+ public RendererClient,
+ public TileManagerClient,
+ public OutputSurfaceClient,
+ public TopControlsManagerClient,
+ public base::SupportsWeakPtr<LayerTreeHostImpl> {
+ public:
+ static scoped_ptr<LayerTreeHostImpl> Create(
+ const LayerTreeSettings& settings,
+ LayerTreeHostImplClient* client,
+ Proxy* proxy,
+ RenderingStatsInstrumentation* rendering_stats_instrumentation);
+ virtual ~LayerTreeHostImpl();
+
+ // InputHandler implementation
+ virtual void BindToClient(InputHandlerClient* client) OVERRIDE;
+ virtual InputHandler::ScrollStatus ScrollBegin(
+ gfx::Point viewport_point,
+ InputHandler::ScrollInputType type) OVERRIDE;
+ virtual bool ScrollBy(gfx::Point viewport_point,
+ gfx::Vector2dF scroll_delta) OVERRIDE;
+ virtual bool ScrollVerticallyByPage(gfx::Point viewport_point,
+ ScrollDirection direction) OVERRIDE;
+ virtual void SetRootLayerScrollOffsetDelegate(
+ LayerScrollOffsetDelegate* root_layer_scroll_offset_delegate) OVERRIDE;
+ virtual void OnRootLayerDelegatedScrollOffsetChanged() OVERRIDE;
+ virtual void ScrollEnd() OVERRIDE;
+ virtual InputHandler::ScrollStatus FlingScrollBegin() OVERRIDE;
+ virtual void NotifyCurrentFlingVelocity(gfx::Vector2dF velocity) OVERRIDE;
+ virtual void PinchGestureBegin() OVERRIDE;
+ virtual void PinchGestureUpdate(float magnify_delta,
+ gfx::Point anchor) OVERRIDE;
+ virtual void PinchGestureEnd() OVERRIDE;
+ virtual void StartPageScaleAnimation(gfx::Vector2d target_offset,
+ bool anchor_point,
+ float page_scale,
+ base::TimeTicks start_time,
+ base::TimeDelta duration) OVERRIDE;
+ virtual void ScheduleAnimation() OVERRIDE;
+ virtual bool HaveTouchEventHandlersAt(gfx::Point viewport_port) OVERRIDE;
+ virtual void SetLatencyInfoForInputEvent(const ui::LatencyInfo& latency_info)
+ OVERRIDE;
+
+ // TopControlsManagerClient implementation.
+ virtual void DidChangeTopControlsPosition() OVERRIDE;
+ virtual bool HaveRootScrollLayer() const OVERRIDE;
+
+ void StartScrollbarAnimation();
+
+ struct CC_EXPORT FrameData : public RenderPassSink {
+ FrameData();
+ virtual ~FrameData();
+ scoped_ptr<base::Value> AsValue() const;
+
+ std::vector<gfx::Rect> occluding_screen_space_rects;
+ std::vector<gfx::Rect> non_occluding_screen_space_rects;
+ RenderPassList render_passes;
+ RenderPassIdHashMap render_passes_by_id;
+ const LayerImplList* render_surface_layer_list;
+ LayerImplList will_draw_layers;
+ bool contains_incomplete_tile;
+ bool has_no_damage;
+
+ // RenderPassSink implementation.
+ virtual void AppendRenderPass(scoped_ptr<RenderPass> render_pass) OVERRIDE;
+ };
+
+ virtual void BeginCommit();
+ virtual void CommitComplete();
+ virtual void Animate(base::TimeTicks monotonic_time,
+ base::Time wall_clock_time);
+ virtual void UpdateAnimationState(bool start_ready_animations);
+ void MainThreadHasStoppedFlinging();
+ void UpdateBackgroundAnimateTicking(bool should_background_tick);
+ void SetViewportDamage(gfx::Rect damage_rect);
+
+ void ManageTiles();
+
+ // Returns false if problems occured preparing the frame, and we should try
+ // to avoid displaying the frame. If PrepareToDraw is called, DidDrawAllLayers
+ // must also be called, regardless of whether DrawLayers is called between the
+ // two.
+ virtual bool PrepareToDraw(FrameData* frame,
+ gfx::Rect device_viewport_damage_rect);
+ virtual void DrawLayers(FrameData* frame, base::TimeTicks frame_begin_time);
+ // Must be called if and only if PrepareToDraw was called.
+ void DidDrawAllLayers(const FrameData& frame);
+
+ const LayerTreeSettings& settings() const { return settings_; }
+
+ // Returns the currently visible viewport size in DIP. This value excludes
+ // the URL bar and non-overlay scrollbars.
+ gfx::SizeF VisibleViewportSize() const;
+
+ // Evict all textures by enforcing a memory policy with an allocation of 0.
+ void EvictTexturesForTesting();
+
+ // RendererClient implementation
+ virtual gfx::Rect DeviceViewport() const OVERRIDE;
+ private:
+ virtual float DeviceScaleFactor() const OVERRIDE;
+ virtual const LayerTreeSettings& Settings() const OVERRIDE;
+ public:
+ virtual void SetFullRootLayerDamage() OVERRIDE;
+ virtual bool HasImplThread() const OVERRIDE;
+ virtual bool ShouldClearRootRenderPass() const OVERRIDE;
+ virtual CompositorFrameMetadata MakeCompositorFrameMetadata() const OVERRIDE;
+ virtual bool AllowPartialSwap() const OVERRIDE;
+ virtual bool ExternalStencilTestEnabled() const OVERRIDE;
+
+ // TileManagerClient implementation.
+ virtual void NotifyReadyToActivate() OVERRIDE;
+
+ // OutputSurfaceClient implementation.
+ virtual bool DeferredInitialize(
+ scoped_refptr<ContextProvider> offscreen_context_provider) OVERRIDE;
+ virtual void ReleaseGL() OVERRIDE;
+ virtual void SetNeedsRedrawRect(gfx::Rect rect) OVERRIDE;
+ virtual void BeginFrame(const BeginFrameArgs& args) OVERRIDE;
+ virtual void SetExternalDrawConstraints(const gfx::Transform& transform,
+ gfx::Rect viewport) OVERRIDE;
+ virtual void SetExternalStencilTest(bool enabled) OVERRIDE;
+ virtual void DidLoseOutputSurface() OVERRIDE;
+ virtual void OnSwapBuffersComplete(const CompositorFrameAck* ack) OVERRIDE;
+ virtual void SetMemoryPolicy(const ManagedMemoryPolicy& policy) OVERRIDE;
+ virtual void SetDiscardBackBufferWhenNotVisible(bool discard) OVERRIDE;
+ virtual void SetTreeActivationCallback(const base::Closure& callback)
+ OVERRIDE;
+
+ // Called from LayerTreeImpl.
+ void OnCanDrawStateChangedForTree();
+
+ // Implementation
+ bool CanDraw() const;
+ OutputSurface* output_surface() const { return output_surface_.get(); }
+
+ std::string LayerTreeAsJson() const;
+
+ void FinishAllRendering();
+ int SourceAnimationFrameNumber() const;
+
+ virtual bool InitializeRenderer(scoped_ptr<OutputSurface> output_surface);
+ bool IsContextLost();
+ TileManager* tile_manager() { return tile_manager_.get(); }
+ Renderer* renderer() { return renderer_.get(); }
+ const RendererCapabilities& GetRendererCapabilities() const;
+
+ virtual bool SwapBuffers(const FrameData& frame);
+ void SetNeedsBeginFrame(bool enable);
+ void SetNeedsManageTiles() { manage_tiles_needed_ = true; }
+
+ void Readback(void* pixels, gfx::Rect rect_in_device_viewport);
+
+ LayerTreeImpl* active_tree() { return active_tree_.get(); }
+ const LayerTreeImpl* active_tree() const { return active_tree_.get(); }
+ LayerTreeImpl* pending_tree() { return pending_tree_.get(); }
+ const LayerTreeImpl* pending_tree() const { return pending_tree_.get(); }
+ const LayerTreeImpl* recycle_tree() const { return recycle_tree_.get(); }
+ virtual void CreatePendingTree();
+ void UpdateVisibleTiles();
+ virtual void ActivatePendingTreeIfNeeded();
+
+ // Shortcuts to layers on the active tree.
+ LayerImpl* RootLayer() const;
+ LayerImpl* RootScrollLayer() const;
+ LayerImpl* CurrentlyScrollingLayer() const;
+
+ virtual void SetVisible(bool visible);
+ bool visible() const { return visible_; }
+
+ void SetNeedsCommit() { client_->SetNeedsCommitOnImplThread(); }
+ void SetNeedsRedraw() { client_->SetNeedsRedrawOnImplThread(); }
+
+ ManagedMemoryPolicy ActualManagedMemoryPolicy() const;
+
+ size_t memory_allocation_limit_bytes() const;
+
+ void SetViewportSize(gfx::Size device_viewport_size);
+ gfx::Size device_viewport_size() const { return device_viewport_size_; }
+
+ void SetOverdrawBottomHeight(float overdraw_bottom_height);
+ float overdraw_bottom_height() const { return overdraw_bottom_height_; }
+
+ void SetDeviceScaleFactor(float device_scale_factor);
+ float device_scale_factor() const { return device_scale_factor_; }
+
+ const gfx::Transform& DeviceTransform() const;
+
+ scoped_ptr<ScrollAndScaleSet> ProcessScrollDeltas();
+
+ bool needs_animate_layers() const {
+ return !animation_registrar_->active_animation_controllers().empty();
+ }
+
+ void SendManagedMemoryStats(
+ size_t memory_visible_bytes,
+ size_t memory_visible_and_nearby_bytes,
+ size_t memory_use_bytes);
+
+ void set_max_memory_needed_bytes(size_t bytes) {
+ max_memory_needed_bytes_ = bytes;
+ }
+
+ FrameRateCounter* fps_counter() {
+ return fps_counter_.get();
+ }
+ PaintTimeCounter* paint_time_counter() {
+ return paint_time_counter_.get();
+ }
+ MemoryHistory* memory_history() {
+ return memory_history_.get();
+ }
+ DebugRectHistory* debug_rect_history() {
+ return debug_rect_history_.get();
+ }
+ ResourceProvider* resource_provider() {
+ return resource_provider_.get();
+ }
+ TopControlsManager* top_controls_manager() {
+ return top_controls_manager_.get();
+ }
+
+ Proxy* proxy() const { return proxy_; }
+
+ AnimationRegistrar* animation_registrar() const {
+ return animation_registrar_.get();
+ }
+
+ void SetDebugState(const LayerTreeDebugState& new_debug_state);
+ const LayerTreeDebugState& debug_state() const { return debug_state_; }
+
+ class CC_EXPORT CullRenderPassesWithCachedTextures {
+ public:
+ bool ShouldRemoveRenderPass(const RenderPassDrawQuad& quad,
+ const FrameData& frame) const;
+
+ // Iterates from the root first, in order to remove the surfaces closest
+ // to the root with cached textures, and all surfaces that draw into
+ // them.
+ size_t RenderPassListBegin(const RenderPassList& list) const {
+ return list.size() - 1;
+ }
+ size_t RenderPassListEnd(const RenderPassList& list) const { return 0 - 1; }
+ size_t RenderPassListNext(size_t it) const { return it - 1; }
+
+ explicit CullRenderPassesWithCachedTextures(Renderer* renderer)
+ : renderer_(renderer) {}
+ private:
+ Renderer* renderer_;
+ };
+
+ class CC_EXPORT CullRenderPassesWithNoQuads {
+ public:
+ bool ShouldRemoveRenderPass(const RenderPassDrawQuad& quad,
+ const FrameData& frame) const;
+
+ // Iterates in draw order, so that when a surface is removed, and its
+ // target becomes empty, then its target can be removed also.
+ size_t RenderPassListBegin(const RenderPassList& list) const { return 0; }
+ size_t RenderPassListEnd(const RenderPassList& list) const {
+ return list.size();
+ }
+ size_t RenderPassListNext(size_t it) const { return it + 1; }
+ };
+
+ template <typename RenderPassCuller>
+ static void RemoveRenderPasses(RenderPassCuller culler, FrameData* frame);
+
+ gfx::Vector2dF accumulated_root_overscroll() const {
+ return accumulated_root_overscroll_;
+ }
+ gfx::Vector2dF current_fling_velocity() const {
+ return current_fling_velocity_;
+ }
+
+ bool pinch_gesture_active() const { return pinch_gesture_active_; }
+
+ void SetTreePriority(TreePriority priority);
+
+ void ResetCurrentFrameTimeForNextFrame();
+ base::TimeTicks CurrentFrameTimeTicks();
+ base::Time CurrentFrameTime();
+
+ virtual base::TimeTicks CurrentPhysicalTimeTicks() const;
+
+ scoped_ptr<base::Value> AsValue() const { return AsValueWithFrame(NULL); }
+ scoped_ptr<base::Value> AsValueWithFrame(FrameData* frame) const;
+ scoped_ptr<base::Value> ActivationStateAsValue() const;
+
+ bool page_scale_animation_active() const { return !!page_scale_animation_; }
+
+ void CreateUIResource(UIResourceId uid,
+ scoped_refptr<UIResourceBitmap> bitmap);
+ // Deletes a UI resource. May safely be called more than once.
+ void DeleteUIResource(UIResourceId uid);
+
+ ResourceProvider::ResourceId ResourceIdForUIResource(UIResourceId uid) const;
+
+ protected:
+ LayerTreeHostImpl(
+ const LayerTreeSettings& settings,
+ LayerTreeHostImplClient* client,
+ Proxy* proxy,
+ RenderingStatsInstrumentation* rendering_stats_instrumentation);
+ virtual void ActivatePendingTree();
+
+ // Virtual for testing.
+ virtual void AnimateLayers(base::TimeTicks monotonic_time,
+ base::Time wall_clock_time);
+
+ // Virtual for testing.
+ virtual base::TimeDelta LowFrequencyAnimationInterval() const;
+
+ const AnimationRegistrar::AnimationControllerMap&
+ active_animation_controllers() const {
+ return animation_registrar_->active_animation_controllers();
+ }
+
+ LayerTreeHostImplClient* client_;
+ Proxy* proxy_;
+
+ private:
+ void CreateAndSetRenderer(OutputSurface* output_surface,
+ ResourceProvider* resource_provider,
+ bool skip_gl_renderer);
+ void CreateAndSetTileManager(ResourceProvider* resource_provider,
+ bool using_map_image);
+ void ReleaseTreeResources();
+ void EnforceZeroBudget(bool zero_budget);
+
+ void AnimatePageScale(base::TimeTicks monotonic_time);
+ void AnimateScrollbars(base::TimeTicks monotonic_time);
+ void AnimateTopControls(base::TimeTicks monotonic_time);
+
+ gfx::Vector2dF ScrollLayerWithViewportSpaceDelta(
+ LayerImpl* layer_impl,
+ float scale_from_viewport_to_screen_space,
+ gfx::PointF viewport_point,
+ gfx::Vector2dF viewport_delta);
+
+ void UpdateMaxScrollOffset();
+ void TrackDamageForAllSurfaces(
+ LayerImpl* root_draw_layer,
+ const LayerImplList& render_surface_layer_list);
+
+ void UpdateTileManagerMemoryPolicy(const ManagedMemoryPolicy& policy);
+
+ // Returns false if the frame should not be displayed. This function should
+ // only be called from PrepareToDraw, as DidDrawAllLayers must be called
+ // if this helper function is called.
+ bool CalculateRenderPasses(FrameData* frame);
+
+ void SendReleaseResourcesRecursive(LayerImpl* current);
+ bool EnsureRenderSurfaceLayerList();
+ void ClearCurrentlyScrollingLayer();
+
+ void AnimateScrollbarsRecursive(LayerImpl* layer,
+ base::TimeTicks time);
+
+ void UpdateCurrentFrameTime(base::TimeTicks* ticks, base::Time* now) const;
+
+ void StartScrollbarAnimationRecursive(LayerImpl* layer, base::TimeTicks time);
+ void SetManagedMemoryPolicy(const ManagedMemoryPolicy& policy,
+ bool zero_budget);
+ void EnforceManagedMemoryPolicy(const ManagedMemoryPolicy& policy);
+
+ void DidInitializeVisibleTile();
+
+ typedef base::hash_map<UIResourceId, ResourceProvider::ResourceId>
+ UIResourceMap;
+ UIResourceMap ui_resource_map_;
+
+ scoped_ptr<OutputSurface> output_surface_;
+
+ // |resource_provider_| and |tile_manager_| can be NULL, e.g. when using tile-
+ // free rendering - see OutputSurface::ForcedDrawToSoftwareDevice().
+ scoped_ptr<ResourceProvider> resource_provider_;
+ scoped_ptr<TileManager> tile_manager_;
+ scoped_ptr<Renderer> renderer_;
+
+ // Tree currently being drawn.
+ scoped_ptr<LayerTreeImpl> active_tree_;
+
+ // In impl-side painting mode, tree with possibly incomplete rasterized
+ // content. May be promoted to active by ActivatePendingTreeIfNeeded().
+ scoped_ptr<LayerTreeImpl> pending_tree_;
+
+ // In impl-side painting mode, inert tree with layers that can be recycled
+ // by the next sync from the main thread.
+ scoped_ptr<LayerTreeImpl> recycle_tree_;
+
+ InputHandlerClient* input_handler_client_;
+ bool did_lock_scrolling_layer_;
+ bool should_bubble_scrolls_;
+ bool wheel_scrolling_;
+
+ bool manage_tiles_needed_;
+
+ // The optional delegate for the root layer scroll offset.
+ LayerScrollOffsetDelegate* root_layer_scroll_offset_delegate_;
+ LayerTreeSettings settings_;
+ LayerTreeDebugState debug_state_;
+ bool visible_;
+ ManagedMemoryPolicy cached_managed_memory_policy_;
+
+ gfx::Vector2dF accumulated_root_overscroll_;
+ gfx::Vector2dF current_fling_velocity_;
+
+ bool pinch_gesture_active_;
+ gfx::Point previous_pinch_anchor_;
+
+ // This is set by AnimateLayers() and used by UpdateAnimationState()
+ // when sending animation events to the main thread.
+ base::Time last_animation_time_;
+
+ scoped_ptr<TopControlsManager> top_controls_manager_;
+
+ scoped_ptr<PageScaleAnimation> page_scale_animation_;
+
+ // This is used for ticking animations slowly when hidden.
+ scoped_ptr<LayerTreeHostImplTimeSourceAdapter> time_source_client_adapter_;
+
+ scoped_ptr<FrameRateCounter> fps_counter_;
+ scoped_ptr<PaintTimeCounter> paint_time_counter_;
+ scoped_ptr<MemoryHistory> memory_history_;
+ scoped_ptr<DebugRectHistory> debug_rect_history_;
+
+ // The maximum memory that would be used by the prioritized resource
+ // manager, if there were no limit on memory usage.
+ size_t max_memory_needed_bytes_;
+
+ size_t last_sent_memory_visible_bytes_;
+ size_t last_sent_memory_visible_and_nearby_bytes_;
+ size_t last_sent_memory_use_bytes_;
+ bool zero_budget_;
+
+ // Viewport size passed in from the main thread, in physical pixels.
+ gfx::Size device_viewport_size_;
+
+ // Conversion factor from CSS pixels to physical pixels when
+ // pageScaleFactor=1.
+ float device_scale_factor_;
+
+ // Vertical amount of the viewport size that's known to covered by a
+ // browser-side UI element, such as an on-screen-keyboard. This affects
+ // scrollable size since we want to still be able to scroll to the bottom of
+ // the page when the keyboard is up.
+ float overdraw_bottom_height_;
+
+ // Optional top-level constraints that can be set by the OutputSurface. The
+ // external_viewport_'s size takes precedence over device_viewport_size_ for
+ // DrawQuad generation and Renderer; however, device_viewport_size_ is still
+ // used for scrollable size.
+ gfx::Transform external_transform_;
+ gfx::Rect external_viewport_;
+ bool external_stencil_test_enabled_;
+
+ gfx::Rect viewport_damage_rect_;
+
+ base::TimeTicks current_frame_timeticks_;
+ base::Time current_frame_time_;
+
+ scoped_ptr<AnimationRegistrar> animation_registrar_;
+
+ RenderingStatsInstrumentation* rendering_stats_instrumentation_;
+
+ bool need_to_update_visible_tiles_before_draw_;
+
+ // Optional callback to notify of new tree activations.
+ base::Closure tree_activation_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(LayerTreeHostImpl);
+};
+
+} // namespace cc
+
+#endif // CC_TREES_LAYER_TREE_HOST_IMPL_H_
diff --git a/chromium/cc/trees/layer_tree_host_impl_unittest.cc b/chromium/cc/trees/layer_tree_host_impl_unittest.cc
new file mode 100644
index 00000000000..764a9cf93de
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_host_impl_unittest.cc
@@ -0,0 +1,6326 @@
+// Copyright 2011 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 "cc/trees/layer_tree_host_impl.h"
+
+#include <cmath>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/containers/hash_tables.h"
+#include "cc/base/math_util.h"
+#include "cc/debug/test_web_graphics_context_3d.h"
+#include "cc/input/top_controls_manager.h"
+#include "cc/layers/delegated_renderer_layer_impl.h"
+#include "cc/layers/heads_up_display_layer_impl.h"
+#include "cc/layers/io_surface_layer_impl.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/layers/quad_sink.h"
+#include "cc/layers/render_surface_impl.h"
+#include "cc/layers/scrollbar_layer_impl.h"
+#include "cc/layers/solid_color_layer_impl.h"
+#include "cc/layers/texture_layer_impl.h"
+#include "cc/layers/tiled_layer_impl.h"
+#include "cc/layers/video_layer_impl.h"
+#include "cc/output/begin_frame_args.h"
+#include "cc/output/compositor_frame_ack.h"
+#include "cc/output/compositor_frame_metadata.h"
+#include "cc/output/gl_renderer.h"
+#include "cc/quads/render_pass_draw_quad.h"
+#include "cc/quads/solid_color_draw_quad.h"
+#include "cc/quads/texture_draw_quad.h"
+#include "cc/quads/tile_draw_quad.h"
+#include "cc/resources/layer_tiling_data.h"
+#include "cc/test/animation_test_common.h"
+#include "cc/test/fake_output_surface.h"
+#include "cc/test/fake_proxy.h"
+#include "cc/test/fake_rendering_stats_instrumentation.h"
+#include "cc/test/fake_video_frame_provider.h"
+#include "cc/test/geometry_test_utils.h"
+#include "cc/test/layer_test_common.h"
+#include "cc/test/render_pass_test_common.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "cc/trees/single_thread_proxy.h"
+#include "media/base/media.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/size_conversions.h"
+#include "ui/gfx/vector2d_conversions.h"
+
+using ::testing::Mock;
+using ::testing::Return;
+using ::testing::AnyNumber;
+using ::testing::AtLeast;
+using ::testing::_;
+using media::VideoFrame;
+
+namespace cc {
+namespace {
+
+class LayerTreeHostImplTest : public testing::Test,
+ public LayerTreeHostImplClient {
+ public:
+ LayerTreeHostImplTest()
+ : proxy_(),
+ always_impl_thread_(&proxy_),
+ always_main_thread_blocked_(&proxy_),
+ did_try_initialize_renderer_(false),
+ on_can_draw_state_changed_called_(false),
+ has_pending_tree_(false),
+ did_request_commit_(false),
+ did_request_redraw_(false),
+ did_upload_visible_tile_(false),
+ reduce_memory_result_(true),
+ current_limit_bytes_(0),
+ current_priority_cutoff_value_(0) {
+ media::InitializeMediaLibraryForTesting();
+ }
+
+ virtual void SetUp() OVERRIDE {
+ LayerTreeSettings settings;
+ settings.minimum_occlusion_tracking_size = gfx::Size();
+ settings.impl_side_painting = true;
+ settings.solid_color_scrollbars = true;
+
+ host_impl_ = LayerTreeHostImpl::Create(settings,
+ this,
+ &proxy_,
+ &stats_instrumentation_);
+ host_impl_->InitializeRenderer(CreateOutputSurface());
+ host_impl_->SetViewportSize(gfx::Size(10, 10));
+ }
+
+ virtual void TearDown() OVERRIDE {}
+
+ virtual void DidTryInitializeRendererOnImplThread(
+ bool success,
+ scoped_refptr<ContextProvider> offscreen_context_provider) OVERRIDE {
+ did_try_initialize_renderer_ = true;
+ }
+ virtual void DidLoseOutputSurfaceOnImplThread() OVERRIDE {}
+ virtual void OnSwapBuffersCompleteOnImplThread() OVERRIDE {}
+ virtual void BeginFrameOnImplThread(const BeginFrameArgs& args)
+ OVERRIDE {}
+ virtual void OnCanDrawStateChanged(bool can_draw) OVERRIDE {
+ on_can_draw_state_changed_called_ = true;
+ }
+ virtual void OnHasPendingTreeStateChanged(bool has_pending_tree) OVERRIDE {
+ has_pending_tree_ = has_pending_tree;
+ }
+ virtual void SetNeedsRedrawOnImplThread() OVERRIDE {
+ did_request_redraw_ = true;
+ }
+ virtual void SetNeedsRedrawRectOnImplThread(gfx::Rect damage_rect) OVERRIDE {
+ did_request_redraw_ = true;
+ }
+ virtual void DidInitializeVisibleTileOnImplThread() OVERRIDE {
+ did_upload_visible_tile_ = true;
+ }
+ virtual void SetNeedsCommitOnImplThread() OVERRIDE {
+ did_request_commit_ = true;
+ }
+ virtual void PostAnimationEventsToMainThreadOnImplThread(
+ scoped_ptr<AnimationEventsVector> events,
+ base::Time wall_clock_time) OVERRIDE {}
+ virtual bool ReduceContentsTextureMemoryOnImplThread(
+ size_t limit_bytes, int priority_cutoff) OVERRIDE {
+ current_limit_bytes_ = limit_bytes;
+ current_priority_cutoff_value_ = priority_cutoff;
+ return reduce_memory_result_;
+ }
+ virtual void ReduceWastedContentsTextureMemoryOnImplThread() OVERRIDE {}
+ virtual void SendManagedMemoryStats() OVERRIDE {}
+ virtual bool IsInsideDraw() OVERRIDE { return false; }
+ virtual void RenewTreePriority() OVERRIDE {}
+ virtual void RequestScrollbarAnimationOnImplThread(base::TimeDelta delay)
+ OVERRIDE { requested_scrollbar_animation_delay_ = delay; }
+ virtual void DidActivatePendingTree() OVERRIDE {}
+
+ void set_reduce_memory_result(bool reduce_memory_result) {
+ reduce_memory_result_ = reduce_memory_result;
+ }
+
+ void CreateLayerTreeHost(bool partial_swap,
+ scoped_ptr<OutputSurface> output_surface) {
+ LayerTreeSettings settings;
+ settings.minimum_occlusion_tracking_size = gfx::Size();
+ settings.partial_swap_enabled = partial_swap;
+
+ host_impl_ = LayerTreeHostImpl::Create(settings,
+ this,
+ &proxy_,
+ &stats_instrumentation_);
+
+ host_impl_->InitializeRenderer(output_surface.Pass());
+ host_impl_->SetViewportSize(gfx::Size(10, 10));
+ }
+
+ void SetupRootLayerImpl(scoped_ptr<LayerImpl> root) {
+ root->SetAnchorPoint(gfx::PointF());
+ root->SetPosition(gfx::PointF());
+ root->SetBounds(gfx::Size(10, 10));
+ root->SetContentBounds(gfx::Size(10, 10));
+ root->SetDrawsContent(true);
+ root->draw_properties().visible_content_rect = gfx::Rect(0, 0, 10, 10);
+ host_impl_->active_tree()->SetRootLayer(root.Pass());
+ }
+
+ static void ExpectClearedScrollDeltasRecursive(LayerImpl* layer) {
+ ASSERT_EQ(layer->ScrollDelta(), gfx::Vector2d());
+ for (size_t i = 0; i < layer->children().size(); ++i)
+ ExpectClearedScrollDeltasRecursive(layer->children()[i]);
+ }
+
+ static void ExpectContains(const ScrollAndScaleSet& scroll_info,
+ int id,
+ gfx::Vector2d scroll_delta) {
+ int times_encountered = 0;
+
+ for (size_t i = 0; i < scroll_info.scrolls.size(); ++i) {
+ if (scroll_info.scrolls[i].layer_id != id)
+ continue;
+ EXPECT_VECTOR_EQ(scroll_delta, scroll_info.scrolls[i].scroll_delta);
+ times_encountered++;
+ }
+
+ ASSERT_EQ(times_encountered, 1);
+ }
+
+ static void ExpectNone(const ScrollAndScaleSet& scroll_info, int id) {
+ int times_encountered = 0;
+
+ for (size_t i = 0; i < scroll_info.scrolls.size(); ++i) {
+ if (scroll_info.scrolls[i].layer_id != id)
+ continue;
+ times_encountered++;
+ }
+
+ ASSERT_EQ(0, times_encountered);
+ }
+
+ LayerImpl* SetupScrollAndContentsLayers(gfx::Size content_size) {
+ scoped_ptr<LayerImpl> root =
+ LayerImpl::Create(host_impl_->active_tree(), 1);
+ root->SetBounds(content_size);
+ root->SetContentBounds(content_size);
+ root->SetPosition(gfx::PointF());
+ root->SetAnchorPoint(gfx::PointF());
+
+ scoped_ptr<LayerImpl> scroll =
+ LayerImpl::Create(host_impl_->active_tree(), 2);
+ LayerImpl* scroll_layer = scroll.get();
+ scroll->SetScrollable(true);
+ scroll->SetScrollOffset(gfx::Vector2d());
+ scroll->SetMaxScrollOffset(gfx::Vector2d(content_size.width(),
+ content_size.height()));
+ scroll->SetBounds(content_size);
+ scroll->SetContentBounds(content_size);
+ scroll->SetPosition(gfx::PointF());
+ scroll->SetAnchorPoint(gfx::PointF());
+
+ scoped_ptr<LayerImpl> contents =
+ LayerImpl::Create(host_impl_->active_tree(), 3);
+ contents->SetDrawsContent(true);
+ contents->SetBounds(content_size);
+ contents->SetContentBounds(content_size);
+ contents->SetPosition(gfx::PointF());
+ contents->SetAnchorPoint(gfx::PointF());
+
+ scroll->AddChild(contents.Pass());
+ root->AddChild(scroll.Pass());
+
+ host_impl_->active_tree()->SetRootLayer(root.Pass());
+ host_impl_->active_tree()->DidBecomeActive();
+ return scroll_layer;
+ }
+
+ scoped_ptr<LayerImpl> CreateScrollableLayer(int id, gfx::Size size) {
+ scoped_ptr<LayerImpl> layer =
+ LayerImpl::Create(host_impl_->active_tree(), id);
+ layer->SetScrollable(true);
+ layer->SetDrawsContent(true);
+ layer->SetBounds(size);
+ layer->SetContentBounds(size);
+ layer->SetMaxScrollOffset(gfx::Vector2d(size.width() * 2,
+ size.height() * 2));
+ return layer.Pass();
+ }
+
+ void InitializeRendererAndDrawFrame() {
+ host_impl_->InitializeRenderer(CreateOutputSurface());
+ DrawFrame();
+ }
+
+ void DrawFrame() {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+ }
+
+ void pinch_zoom_pan_viewport_forces_commit_redraw(float device_scale_factor);
+ void pinch_zoom_pan_viewport_test(float device_scale_factor);
+ void pinch_zoom_pan_viewport_and_scroll_test(float device_scale_factor);
+ void pinch_zoom_pan_viewport_and_scroll_boundary_test(
+ float device_scale_factor);
+
+ void CheckNotifyCalledIfCanDrawChanged(bool always_draw) {
+ // Note: It is not possible to disable the renderer once it has been set,
+ // so we do not need to test that disabling the renderer notifies us
+ // that can_draw changed.
+ EXPECT_FALSE(host_impl_->CanDraw());
+ on_can_draw_state_changed_called_ = false;
+
+ // Set up the root layer, which allows us to draw.
+ SetupScrollAndContentsLayers(gfx::Size(100, 100));
+ EXPECT_TRUE(host_impl_->CanDraw());
+ EXPECT_TRUE(on_can_draw_state_changed_called_);
+ on_can_draw_state_changed_called_ = false;
+
+ // Toggle the root layer to make sure it toggles can_draw
+ host_impl_->active_tree()->SetRootLayer(scoped_ptr<LayerImpl>());
+ EXPECT_FALSE(host_impl_->CanDraw());
+ EXPECT_TRUE(on_can_draw_state_changed_called_);
+ on_can_draw_state_changed_called_ = false;
+
+ SetupScrollAndContentsLayers(gfx::Size(100, 100));
+ EXPECT_TRUE(host_impl_->CanDraw());
+ EXPECT_TRUE(on_can_draw_state_changed_called_);
+ on_can_draw_state_changed_called_ = false;
+
+ // Toggle the device viewport size to make sure it toggles can_draw.
+ host_impl_->SetViewportSize(gfx::Size());
+ if (always_draw) {
+ EXPECT_TRUE(host_impl_->CanDraw());
+ } else {
+ EXPECT_FALSE(host_impl_->CanDraw());
+ }
+ EXPECT_TRUE(on_can_draw_state_changed_called_);
+ on_can_draw_state_changed_called_ = false;
+
+ host_impl_->SetViewportSize(gfx::Size(100, 100));
+ EXPECT_TRUE(host_impl_->CanDraw());
+ EXPECT_TRUE(on_can_draw_state_changed_called_);
+ on_can_draw_state_changed_called_ = false;
+
+ // Toggle contents textures purged without causing any evictions,
+ // and make sure that it does not change can_draw.
+ set_reduce_memory_result(false);
+ host_impl_->SetMemoryPolicy(ManagedMemoryPolicy(
+ host_impl_->memory_allocation_limit_bytes() - 1));
+ host_impl_->SetDiscardBackBufferWhenNotVisible(true);
+ EXPECT_TRUE(host_impl_->CanDraw());
+ EXPECT_FALSE(on_can_draw_state_changed_called_);
+ on_can_draw_state_changed_called_ = false;
+
+ // Toggle contents textures purged to make sure it toggles can_draw.
+ set_reduce_memory_result(true);
+ host_impl_->SetMemoryPolicy(ManagedMemoryPolicy(
+ host_impl_->memory_allocation_limit_bytes() - 1));
+ host_impl_->SetDiscardBackBufferWhenNotVisible(true);
+ if (always_draw) {
+ EXPECT_TRUE(host_impl_->CanDraw());
+ } else {
+ EXPECT_FALSE(host_impl_->CanDraw());
+ }
+ EXPECT_TRUE(on_can_draw_state_changed_called_);
+ on_can_draw_state_changed_called_ = false;
+
+ host_impl_->active_tree()->ResetContentsTexturesPurged();
+ EXPECT_TRUE(host_impl_->CanDraw());
+ EXPECT_TRUE(on_can_draw_state_changed_called_);
+ on_can_draw_state_changed_called_ = false;
+ }
+
+ protected:
+ virtual scoped_ptr<OutputSurface> CreateOutputSurface() {
+ return CreateFakeOutputSurface();
+ }
+
+ void DrawOneFrame() {
+ LayerTreeHostImpl::FrameData frame_data;
+ host_impl_->PrepareToDraw(&frame_data, gfx::Rect());
+ host_impl_->DidDrawAllLayers(frame_data);
+ }
+
+ FakeProxy proxy_;
+ DebugScopedSetImplThread always_impl_thread_;
+ DebugScopedSetMainThreadBlocked always_main_thread_blocked_;
+
+ scoped_ptr<LayerTreeHostImpl> host_impl_;
+ FakeRenderingStatsInstrumentation stats_instrumentation_;
+ bool did_try_initialize_renderer_;
+ bool on_can_draw_state_changed_called_;
+ bool has_pending_tree_;
+ bool did_request_commit_;
+ bool did_request_redraw_;
+ bool did_upload_visible_tile_;
+ bool reduce_memory_result_;
+ base::TimeDelta requested_scrollbar_animation_delay_;
+ size_t current_limit_bytes_;
+ int current_priority_cutoff_value_;
+};
+
+TEST_F(LayerTreeHostImplTest, NotifyIfCanDrawChanged) {
+ bool always_draw = false;
+ CheckNotifyCalledIfCanDrawChanged(always_draw);
+}
+
+TEST_F(LayerTreeHostImplTest, CanDrawIncompleteFrames) {
+ LayerTreeSettings settings;
+ settings.impl_side_painting = true;
+ host_impl_ = LayerTreeHostImpl::Create(
+ settings, this, &proxy_, &stats_instrumentation_);
+ host_impl_->InitializeRenderer(
+ FakeOutputSurface::CreateAlwaysDrawAndSwap3d().PassAs<OutputSurface>());
+ host_impl_->SetViewportSize(gfx::Size(10, 10));
+
+ bool always_draw = true;
+ CheckNotifyCalledIfCanDrawChanged(always_draw);
+}
+
+class TestWebGraphicsContext3DMakeCurrentFails
+ : public TestWebGraphicsContext3D {
+ public:
+ virtual bool makeContextCurrent() OVERRIDE { return false; }
+};
+
+TEST_F(LayerTreeHostImplTest, ScrollDeltaNoLayers) {
+ ASSERT_FALSE(host_impl_->active_tree()->root_layer());
+
+ scoped_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
+ ASSERT_EQ(scroll_info->scrolls.size(), 0u);
+}
+
+TEST_F(LayerTreeHostImplTest, ScrollDeltaTreeButNoChanges) {
+ {
+ scoped_ptr<LayerImpl> root =
+ LayerImpl::Create(host_impl_->active_tree(), 1);
+ root->AddChild(LayerImpl::Create(host_impl_->active_tree(), 2));
+ root->AddChild(LayerImpl::Create(host_impl_->active_tree(), 3));
+ root->children()[1]->AddChild(
+ LayerImpl::Create(host_impl_->active_tree(), 4));
+ root->children()[1]->AddChild(
+ LayerImpl::Create(host_impl_->active_tree(), 5));
+ root->children()[1]->children()[0]->AddChild(
+ LayerImpl::Create(host_impl_->active_tree(), 6));
+ host_impl_->active_tree()->SetRootLayer(root.Pass());
+ }
+ LayerImpl* root = host_impl_->active_tree()->root_layer();
+
+ ExpectClearedScrollDeltasRecursive(root);
+
+ scoped_ptr<ScrollAndScaleSet> scroll_info;
+
+ scroll_info = host_impl_->ProcessScrollDeltas();
+ ASSERT_EQ(scroll_info->scrolls.size(), 0u);
+ ExpectClearedScrollDeltasRecursive(root);
+
+ scroll_info = host_impl_->ProcessScrollDeltas();
+ ASSERT_EQ(scroll_info->scrolls.size(), 0u);
+ ExpectClearedScrollDeltasRecursive(root);
+}
+
+TEST_F(LayerTreeHostImplTest, ScrollDeltaRepeatedScrolls) {
+ gfx::Vector2d scroll_offset(20, 30);
+ gfx::Vector2d scroll_delta(11, -15);
+ {
+ scoped_ptr<LayerImpl> root =
+ LayerImpl::Create(host_impl_->active_tree(), 1);
+ root->SetMaxScrollOffset(gfx::Vector2d(100, 100));
+ root->SetScrollOffset(scroll_offset);
+ root->SetScrollable(true);
+ root->ScrollBy(scroll_delta);
+ host_impl_->active_tree()->SetRootLayer(root.Pass());
+ }
+ LayerImpl* root = host_impl_->active_tree()->root_layer();
+
+ scoped_ptr<ScrollAndScaleSet> scroll_info;
+
+ scroll_info = host_impl_->ProcessScrollDeltas();
+ ASSERT_EQ(scroll_info->scrolls.size(), 1u);
+ EXPECT_VECTOR_EQ(root->sent_scroll_delta(), scroll_delta);
+ ExpectContains(*scroll_info, root->id(), scroll_delta);
+
+ gfx::Vector2d scroll_delta2(-5, 27);
+ root->ScrollBy(scroll_delta2);
+ scroll_info = host_impl_->ProcessScrollDeltas();
+ ASSERT_EQ(scroll_info->scrolls.size(), 1u);
+ EXPECT_VECTOR_EQ(root->sent_scroll_delta(), scroll_delta + scroll_delta2);
+ ExpectContains(*scroll_info, root->id(), scroll_delta + scroll_delta2);
+
+ root->ScrollBy(gfx::Vector2d());
+ scroll_info = host_impl_->ProcessScrollDeltas();
+ EXPECT_EQ(root->sent_scroll_delta(), scroll_delta + scroll_delta2);
+}
+
+TEST_F(LayerTreeHostImplTest, ScrollRootCallsCommitAndRedraw) {
+ SetupScrollAndContentsLayers(gfx::Size(100, 100));
+ host_impl_->SetViewportSize(gfx::Size(50, 50));
+ InitializeRendererAndDrawFrame();
+
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel));
+ host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 10));
+ host_impl_->ScrollEnd();
+ EXPECT_TRUE(did_request_redraw_);
+ EXPECT_TRUE(did_request_commit_);
+}
+
+TEST_F(LayerTreeHostImplTest, ScrollWithoutRootLayer) {
+ // We should not crash when trying to scroll an empty layer tree.
+ EXPECT_EQ(InputHandler::ScrollIgnored,
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel));
+}
+
+TEST_F(LayerTreeHostImplTest, ScrollWithoutRenderer) {
+ LayerTreeSettings settings;
+ host_impl_ = LayerTreeHostImpl::Create(settings,
+ this,
+ &proxy_,
+ &stats_instrumentation_);
+
+ // Initialization will fail here.
+ host_impl_->InitializeRenderer(FakeOutputSurface::Create3d(
+ scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new TestWebGraphicsContext3DMakeCurrentFails))
+ .PassAs<OutputSurface>());
+ host_impl_->SetViewportSize(gfx::Size(10, 10));
+
+ SetupScrollAndContentsLayers(gfx::Size(100, 100));
+
+ // We should not crash when trying to scroll after the renderer initialization
+ // fails.
+ EXPECT_EQ(InputHandler::ScrollIgnored,
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel));
+}
+
+TEST_F(LayerTreeHostImplTest, ReplaceTreeWhileScrolling) {
+ LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
+ host_impl_->SetViewportSize(gfx::Size(50, 50));
+ InitializeRendererAndDrawFrame();
+
+ // We should not crash if the tree is replaced while we are scrolling.
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel));
+ host_impl_->active_tree()->DetachLayerTree();
+
+ scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
+
+ // We should still be scrolling, because the scrolled layer also exists in the
+ // new tree.
+ gfx::Vector2d scroll_delta(0, 10);
+ host_impl_->ScrollBy(gfx::Point(), scroll_delta);
+ host_impl_->ScrollEnd();
+ scoped_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
+ ExpectContains(*scroll_info, scroll_layer->id(), scroll_delta);
+}
+
+TEST_F(LayerTreeHostImplTest, ClearRootRenderSurfaceAndScroll) {
+ SetupScrollAndContentsLayers(gfx::Size(100, 100));
+ host_impl_->SetViewportSize(gfx::Size(50, 50));
+ InitializeRendererAndDrawFrame();
+
+ // We should be able to scroll even if the root layer loses its render surface
+ // after the most recent render.
+ host_impl_->active_tree()->root_layer()->ClearRenderSurface();
+ host_impl_->active_tree()->set_needs_update_draw_properties();
+
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel));
+}
+
+TEST_F(LayerTreeHostImplTest, WheelEventHandlers) {
+ SetupScrollAndContentsLayers(gfx::Size(100, 100));
+ host_impl_->SetViewportSize(gfx::Size(50, 50));
+ InitializeRendererAndDrawFrame();
+ LayerImpl* root = host_impl_->active_tree()->root_layer();
+
+ root->SetHaveWheelEventHandlers(true);
+
+ // With registered event handlers, wheel scrolls have to go to the main
+ // thread.
+ EXPECT_EQ(InputHandler::ScrollOnMainThread,
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel));
+
+ // But gesture scrolls can still be handled.
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture));
+}
+
+TEST_F(LayerTreeHostImplTest, FlingOnlyWhenScrollingTouchscreen) {
+ SetupScrollAndContentsLayers(gfx::Size(100, 100));
+ host_impl_->SetViewportSize(gfx::Size(50, 50));
+ InitializeRendererAndDrawFrame();
+
+ // Ignore the fling since no layer is being scrolled
+ EXPECT_EQ(InputHandler::ScrollIgnored,
+ host_impl_->FlingScrollBegin());
+
+ // Start scrolling a layer
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture));
+
+ // Now the fling should go ahead since we've started scrolling a layer
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->FlingScrollBegin());
+}
+
+TEST_F(LayerTreeHostImplTest, FlingOnlyWhenScrollingTouchpad) {
+ SetupScrollAndContentsLayers(gfx::Size(100, 100));
+ host_impl_->SetViewportSize(gfx::Size(50, 50));
+ InitializeRendererAndDrawFrame();
+
+ // Ignore the fling since no layer is being scrolled
+ EXPECT_EQ(InputHandler::ScrollIgnored,
+ host_impl_->FlingScrollBegin());
+
+ // Start scrolling a layer
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel));
+
+ // Now the fling should go ahead since we've started scrolling a layer
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->FlingScrollBegin());
+}
+
+TEST_F(LayerTreeHostImplTest, NoFlingWhenScrollingOnMain) {
+ SetupScrollAndContentsLayers(gfx::Size(100, 100));
+ host_impl_->SetViewportSize(gfx::Size(50, 50));
+ InitializeRendererAndDrawFrame();
+ LayerImpl* root = host_impl_->active_tree()->root_layer();
+
+ root->SetShouldScrollOnMainThread(true);
+
+ // Start scrolling a layer
+ EXPECT_EQ(InputHandler::ScrollOnMainThread,
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture));
+
+ // The fling should be ignored since there's no layer being scrolled impl-side
+ EXPECT_EQ(InputHandler::ScrollIgnored,
+ host_impl_->FlingScrollBegin());
+}
+
+TEST_F(LayerTreeHostImplTest, ShouldScrollOnMainThread) {
+ SetupScrollAndContentsLayers(gfx::Size(100, 100));
+ host_impl_->SetViewportSize(gfx::Size(50, 50));
+ InitializeRendererAndDrawFrame();
+ LayerImpl* root = host_impl_->active_tree()->root_layer();
+
+ root->SetShouldScrollOnMainThread(true);
+
+ EXPECT_EQ(InputHandler::ScrollOnMainThread,
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel));
+ EXPECT_EQ(InputHandler::ScrollOnMainThread,
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture));
+}
+
+TEST_F(LayerTreeHostImplTest, NonFastScrollableRegionBasic) {
+ SetupScrollAndContentsLayers(gfx::Size(200, 200));
+ host_impl_->SetViewportSize(gfx::Size(100, 100));
+
+ LayerImpl* root = host_impl_->active_tree()->root_layer();
+ root->SetContentsScale(2.f, 2.f);
+ root->SetNonFastScrollableRegion(gfx::Rect(0, 0, 50, 50));
+
+ InitializeRendererAndDrawFrame();
+
+ // All scroll types inside the non-fast scrollable region should fail.
+ EXPECT_EQ(InputHandler::ScrollOnMainThread,
+ host_impl_->ScrollBegin(gfx::Point(25, 25),
+ InputHandler::Wheel));
+ EXPECT_EQ(InputHandler::ScrollOnMainThread,
+ host_impl_->ScrollBegin(gfx::Point(25, 25),
+ InputHandler::Gesture));
+
+ // All scroll types outside this region should succeed.
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(75, 75),
+ InputHandler::Wheel));
+ host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 10));
+ host_impl_->ScrollEnd();
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(75, 75),
+ InputHandler::Gesture));
+ host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 10));
+ host_impl_->ScrollEnd();
+}
+
+TEST_F(LayerTreeHostImplTest, NonFastScrollableRegionWithOffset) {
+ SetupScrollAndContentsLayers(gfx::Size(200, 200));
+ host_impl_->SetViewportSize(gfx::Size(100, 100));
+
+ LayerImpl* root = host_impl_->active_tree()->root_layer();
+ root->SetContentsScale(2.f, 2.f);
+ root->SetNonFastScrollableRegion(gfx::Rect(0, 0, 50, 50));
+ root->SetPosition(gfx::PointF(-25.f, 0.f));
+
+ InitializeRendererAndDrawFrame();
+
+ // This point would fall into the non-fast scrollable region except that we've
+ // moved the layer down by 25 pixels.
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(40, 10),
+ InputHandler::Wheel));
+ host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 1));
+ host_impl_->ScrollEnd();
+
+ // This point is still inside the non-fast region.
+ EXPECT_EQ(InputHandler::ScrollOnMainThread,
+ host_impl_->ScrollBegin(gfx::Point(10, 10),
+ InputHandler::Wheel));
+}
+
+TEST_F(LayerTreeHostImplTest, ScrollByReturnsCorrectValue) {
+ SetupScrollAndContentsLayers(gfx::Size(200, 200));
+ host_impl_->SetViewportSize(gfx::Size(100, 100));
+
+ InitializeRendererAndDrawFrame();
+
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture));
+
+ // Trying to scroll to the left/top will not succeed.
+ EXPECT_FALSE(host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(-10, 0)));
+ EXPECT_FALSE(host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, -10)));
+ EXPECT_FALSE(host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(-10, -10)));
+
+ // Scrolling to the right/bottom will succeed.
+ EXPECT_TRUE(host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(10, 0)));
+ EXPECT_TRUE(host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 10)));
+ EXPECT_TRUE(host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(10, 10)));
+
+ // Scrolling to left/top will now succeed.
+ EXPECT_TRUE(host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(-10, 0)));
+ EXPECT_TRUE(host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, -10)));
+ EXPECT_TRUE(host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(-10, -10)));
+
+ // Scrolling diagonally against an edge will succeed.
+ EXPECT_TRUE(host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(10, -10)));
+ EXPECT_TRUE(host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(-10, 0)));
+ EXPECT_TRUE(host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(-10, 10)));
+
+ // Trying to scroll more than the available space will also succeed.
+ EXPECT_TRUE(host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(5000, 5000)));
+}
+
+TEST_F(LayerTreeHostImplTest, ScrollVerticallyByPageReturnsCorrectValue) {
+ SetupScrollAndContentsLayers(gfx::Size(200, 2000));
+ host_impl_->SetViewportSize(gfx::Size(100, 1000));
+
+ InitializeRendererAndDrawFrame();
+
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(),
+ InputHandler::Wheel));
+
+ // Trying to scroll without a vertical scrollbar will fail.
+ EXPECT_FALSE(host_impl_->ScrollVerticallyByPage(
+ gfx::Point(), SCROLL_FORWARD));
+ EXPECT_FALSE(host_impl_->ScrollVerticallyByPage(
+ gfx::Point(), SCROLL_BACKWARD));
+
+ scoped_ptr<cc::ScrollbarLayerImpl> vertical_scrollbar(
+ cc::ScrollbarLayerImpl::Create(
+ host_impl_->active_tree(),
+ 20,
+ VERTICAL));
+ vertical_scrollbar->SetBounds(gfx::Size(15, 1000));
+ host_impl_->RootScrollLayer()->SetVerticalScrollbarLayer(
+ vertical_scrollbar.get());
+
+ // Trying to scroll with a vertical scrollbar will succeed.
+ EXPECT_TRUE(host_impl_->ScrollVerticallyByPage(
+ gfx::Point(), SCROLL_FORWARD));
+ EXPECT_FLOAT_EQ(875.f, host_impl_->RootScrollLayer()->ScrollDelta().y());
+ EXPECT_TRUE(host_impl_->ScrollVerticallyByPage(
+ gfx::Point(), SCROLL_BACKWARD));
+}
+
+TEST_F(LayerTreeHostImplTest,
+ ClearRootRenderSurfaceAndHitTestTouchHandlerRegion) {
+ SetupScrollAndContentsLayers(gfx::Size(100, 100));
+ host_impl_->SetViewportSize(gfx::Size(50, 50));
+ InitializeRendererAndDrawFrame();
+
+ // We should be able to hit test for touch event handlers even if the root
+ // layer loses its render surface after the most recent render.
+ host_impl_->active_tree()->root_layer()->ClearRenderSurface();
+ host_impl_->active_tree()->set_needs_update_draw_properties();
+
+ EXPECT_EQ(host_impl_->HaveTouchEventHandlersAt(gfx::Point()), false);
+}
+
+TEST_F(LayerTreeHostImplTest, ImplPinchZoom) {
+ LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
+ host_impl_->SetViewportSize(gfx::Size(50, 50));
+ InitializeRendererAndDrawFrame();
+
+ EXPECT_EQ(scroll_layer, host_impl_->RootScrollLayer());
+
+ float min_page_scale = 1.f, max_page_scale = 4.f;
+
+ // The impl-based pinch zoom should adjust the max scroll position.
+ {
+ host_impl_->active_tree()->SetPageScaleFactorAndLimits(1.f,
+ min_page_scale,
+ max_page_scale);
+ host_impl_->active_tree()->SetPageScaleDelta(1.f);
+ scroll_layer->SetScrollDelta(gfx::Vector2d());
+
+ float page_scale_delta = 2.f;
+ host_impl_->ScrollBegin(gfx::Point(50, 50), InputHandler::Gesture);
+ host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point(50, 50));
+ host_impl_->PinchGestureEnd();
+ host_impl_->ScrollEnd();
+ EXPECT_TRUE(did_request_redraw_);
+ EXPECT_TRUE(did_request_commit_);
+
+ scoped_ptr<ScrollAndScaleSet> scroll_info =
+ host_impl_->ProcessScrollDeltas();
+ EXPECT_EQ(scroll_info->page_scale_delta, page_scale_delta);
+
+ EXPECT_EQ(gfx::Vector2d(75, 75).ToString(),
+ scroll_layer->max_scroll_offset().ToString());
+ }
+
+ // Scrolling after a pinch gesture should always be in local space. The
+ // scroll deltas do not have the page scale factor applied.
+ {
+ host_impl_->active_tree()->SetPageScaleFactorAndLimits(1.f,
+ min_page_scale,
+ max_page_scale);
+ host_impl_->active_tree()->SetPageScaleDelta(1.f);
+ scroll_layer->SetScrollDelta(gfx::Vector2d());
+
+ float page_scale_delta = 2.f;
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture);
+ host_impl_->PinchGestureBegin();
+ host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point());
+ host_impl_->PinchGestureEnd();
+ host_impl_->ScrollEnd();
+
+ gfx::Vector2d scroll_delta(0, 10);
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(5, 5),
+ InputHandler::Wheel));
+ host_impl_->ScrollBy(gfx::Point(), scroll_delta);
+ host_impl_->ScrollEnd();
+
+ scoped_ptr<ScrollAndScaleSet> scroll_info =
+ host_impl_->ProcessScrollDeltas();
+ ExpectContains(*scroll_info.get(),
+ scroll_layer->id(),
+ scroll_delta);
+ }
+}
+
+TEST_F(LayerTreeHostImplTest, PinchGesture) {
+ SetupScrollAndContentsLayers(gfx::Size(100, 100));
+ host_impl_->SetViewportSize(gfx::Size(50, 50));
+ InitializeRendererAndDrawFrame();
+
+ LayerImpl* scroll_layer = host_impl_->RootScrollLayer();
+ DCHECK(scroll_layer);
+
+ float min_page_scale = 1.f;
+ float max_page_scale = 4.f;
+
+ // Basic pinch zoom in gesture
+ {
+ host_impl_->active_tree()->SetPageScaleFactorAndLimits(1.f,
+ min_page_scale,
+ max_page_scale);
+ scroll_layer->SetScrollDelta(gfx::Vector2d());
+
+ float page_scale_delta = 2.f;
+ host_impl_->ScrollBegin(gfx::Point(50, 50), InputHandler::Gesture);
+ host_impl_->PinchGestureBegin();
+ host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point(50, 50));
+ host_impl_->PinchGestureEnd();
+ host_impl_->ScrollEnd();
+ EXPECT_TRUE(did_request_redraw_);
+ EXPECT_TRUE(did_request_commit_);
+
+ scoped_ptr<ScrollAndScaleSet> scroll_info =
+ host_impl_->ProcessScrollDeltas();
+ EXPECT_EQ(scroll_info->page_scale_delta, page_scale_delta);
+ }
+
+ // Zoom-in clamping
+ {
+ host_impl_->active_tree()->SetPageScaleFactorAndLimits(1.f,
+ min_page_scale,
+ max_page_scale);
+ scroll_layer->SetScrollDelta(gfx::Vector2d());
+ float page_scale_delta = 10.f;
+
+ host_impl_->ScrollBegin(gfx::Point(50, 50), InputHandler::Gesture);
+ host_impl_->PinchGestureBegin();
+ host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point(50, 50));
+ host_impl_->PinchGestureEnd();
+ host_impl_->ScrollEnd();
+
+ scoped_ptr<ScrollAndScaleSet> scroll_info =
+ host_impl_->ProcessScrollDeltas();
+ EXPECT_EQ(scroll_info->page_scale_delta, max_page_scale);
+ }
+
+ // Zoom-out clamping
+ {
+ host_impl_->active_tree()->SetPageScaleFactorAndLimits(1.f,
+ min_page_scale,
+ max_page_scale);
+ scroll_layer->SetScrollDelta(gfx::Vector2d());
+ scroll_layer->SetScrollOffset(gfx::Vector2d(50, 50));
+
+ float page_scale_delta = 0.1f;
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture);
+ host_impl_->PinchGestureBegin();
+ host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point());
+ host_impl_->PinchGestureEnd();
+ host_impl_->ScrollEnd();
+
+ scoped_ptr<ScrollAndScaleSet> scroll_info =
+ host_impl_->ProcessScrollDeltas();
+ EXPECT_EQ(scroll_info->page_scale_delta, min_page_scale);
+
+ EXPECT_TRUE(scroll_info->scrolls.empty());
+ }
+
+ // Two-finger panning should not happen based on pinch events only
+ {
+ host_impl_->active_tree()->SetPageScaleFactorAndLimits(1.f,
+ min_page_scale,
+ max_page_scale);
+ scroll_layer->SetScrollDelta(gfx::Vector2d());
+ scroll_layer->SetScrollOffset(gfx::Vector2d(20, 20));
+
+ float page_scale_delta = 1.f;
+ host_impl_->ScrollBegin(gfx::Point(10, 10), InputHandler::Gesture);
+ host_impl_->PinchGestureBegin();
+ host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point(10, 10));
+ host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point(20, 20));
+ host_impl_->PinchGestureEnd();
+ host_impl_->ScrollEnd();
+
+ scoped_ptr<ScrollAndScaleSet> scroll_info =
+ host_impl_->ProcessScrollDeltas();
+ EXPECT_EQ(scroll_info->page_scale_delta, page_scale_delta);
+ EXPECT_TRUE(scroll_info->scrolls.empty());
+ }
+
+ // Two-finger panning should work with interleaved scroll events
+ {
+ host_impl_->active_tree()->SetPageScaleFactorAndLimits(1.f,
+ min_page_scale,
+ max_page_scale);
+ scroll_layer->SetScrollDelta(gfx::Vector2d());
+ scroll_layer->SetScrollOffset(gfx::Vector2d(20, 20));
+
+ float page_scale_delta = 1.f;
+ host_impl_->ScrollBegin(gfx::Point(10, 10), InputHandler::Gesture);
+ host_impl_->PinchGestureBegin();
+ host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point(10, 10));
+ host_impl_->ScrollBy(gfx::Point(10, 10), gfx::Vector2d(-10, -10));
+ host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point(20, 20));
+ host_impl_->PinchGestureEnd();
+ host_impl_->ScrollEnd();
+
+ scoped_ptr<ScrollAndScaleSet> scroll_info =
+ host_impl_->ProcessScrollDeltas();
+ EXPECT_EQ(scroll_info->page_scale_delta, page_scale_delta);
+ ExpectContains(*scroll_info, scroll_layer->id(), gfx::Vector2d(-10, -10));
+ }
+
+ // Two-finger panning should work when starting fully zoomed out.
+ {
+ host_impl_->active_tree()->SetPageScaleFactorAndLimits(0.5f,
+ 0.5f,
+ 4.f);
+ scroll_layer->SetScrollDelta(gfx::Vector2d());
+ scroll_layer->SetScrollOffset(gfx::Vector2d(0, 0));
+ host_impl_->active_tree()->UpdateMaxScrollOffset();
+
+ host_impl_->ScrollBegin(gfx::Point(0, 0), InputHandler::Gesture);
+ host_impl_->PinchGestureBegin();
+ host_impl_->PinchGestureUpdate(2.f, gfx::Point(0, 0));
+ host_impl_->PinchGestureUpdate(1.f, gfx::Point(0, 0));
+ host_impl_->ScrollBy(gfx::Point(0, 0), gfx::Vector2d(10, 10));
+ host_impl_->PinchGestureUpdate(1.f, gfx::Point(10, 10));
+ host_impl_->PinchGestureEnd();
+ host_impl_->ScrollEnd();
+
+ scoped_ptr<ScrollAndScaleSet> scroll_info =
+ host_impl_->ProcessScrollDeltas();
+ EXPECT_EQ(scroll_info->page_scale_delta, 2.f);
+ ExpectContains(*scroll_info, scroll_layer->id(), gfx::Vector2d(20, 20));
+ }
+}
+
+TEST_F(LayerTreeHostImplTest, PageScaleAnimation) {
+ SetupScrollAndContentsLayers(gfx::Size(100, 100));
+ host_impl_->SetViewportSize(gfx::Size(50, 50));
+ InitializeRendererAndDrawFrame();
+
+ LayerImpl* scroll_layer = host_impl_->RootScrollLayer();
+ DCHECK(scroll_layer);
+
+ float min_page_scale = 0.5f;
+ float max_page_scale = 4.f;
+ base::TimeTicks start_time = base::TimeTicks() +
+ base::TimeDelta::FromSeconds(1);
+ base::TimeDelta duration = base::TimeDelta::FromMilliseconds(100);
+ base::TimeTicks halfway_through_animation = start_time + duration / 2;
+ base::TimeTicks end_time = start_time + duration;
+
+ // Non-anchor zoom-in
+ {
+ host_impl_->active_tree()->SetPageScaleFactorAndLimits(1.f,
+ min_page_scale,
+ max_page_scale);
+ scroll_layer->SetScrollOffset(gfx::Vector2d(50, 50));
+
+ host_impl_->StartPageScaleAnimation(gfx::Vector2d(),
+ false,
+ 2.f,
+ start_time,
+ duration);
+ host_impl_->Animate(halfway_through_animation, base::Time());
+ EXPECT_TRUE(did_request_redraw_);
+ host_impl_->Animate(end_time, base::Time());
+ EXPECT_TRUE(did_request_commit_);
+
+ scoped_ptr<ScrollAndScaleSet> scroll_info =
+ host_impl_->ProcessScrollDeltas();
+ EXPECT_EQ(scroll_info->page_scale_delta, 2);
+ ExpectContains(*scroll_info, scroll_layer->id(), gfx::Vector2d(-50, -50));
+ }
+
+ // Anchor zoom-out
+ {
+ host_impl_->active_tree()->SetPageScaleFactorAndLimits(1.f,
+ min_page_scale,
+ max_page_scale);
+ scroll_layer->SetScrollOffset(gfx::Vector2d(50, 50));
+
+ host_impl_->StartPageScaleAnimation(gfx::Vector2d(25, 25),
+ true,
+ min_page_scale,
+ start_time, duration);
+ host_impl_->Animate(end_time, base::Time());
+ EXPECT_TRUE(did_request_redraw_);
+ EXPECT_TRUE(did_request_commit_);
+
+ scoped_ptr<ScrollAndScaleSet> scroll_info =
+ host_impl_->ProcessScrollDeltas();
+ EXPECT_EQ(scroll_info->page_scale_delta, min_page_scale);
+ // Pushed to (0,0) via clamping against contents layer size.
+ ExpectContains(*scroll_info, scroll_layer->id(), gfx::Vector2d(-50, -50));
+ }
+}
+
+TEST_F(LayerTreeHostImplTest, PageScaleAnimationNoOp) {
+ SetupScrollAndContentsLayers(gfx::Size(100, 100));
+ host_impl_->SetViewportSize(gfx::Size(50, 50));
+ InitializeRendererAndDrawFrame();
+
+ LayerImpl* scroll_layer = host_impl_->RootScrollLayer();
+ DCHECK(scroll_layer);
+
+ float min_page_scale = 0.5f;
+ float max_page_scale = 4.f;
+ base::TimeTicks start_time = base::TimeTicks() +
+ base::TimeDelta::FromSeconds(1);
+ base::TimeDelta duration = base::TimeDelta::FromMilliseconds(100);
+ base::TimeTicks halfway_through_animation = start_time + duration / 2;
+ base::TimeTicks end_time = start_time + duration;
+
+ // Anchor zoom with unchanged page scale should not change scroll or scale.
+ {
+ host_impl_->active_tree()->SetPageScaleFactorAndLimits(1.f,
+ min_page_scale,
+ max_page_scale);
+ scroll_layer->SetScrollOffset(gfx::Vector2d(50, 50));
+
+ host_impl_->StartPageScaleAnimation(gfx::Vector2d(),
+ true,
+ 1.f,
+ start_time,
+ duration);
+ host_impl_->Animate(halfway_through_animation, base::Time());
+ EXPECT_TRUE(did_request_redraw_);
+ host_impl_->Animate(end_time, base::Time());
+ EXPECT_TRUE(did_request_commit_);
+
+ scoped_ptr<ScrollAndScaleSet> scroll_info =
+ host_impl_->ProcessScrollDeltas();
+ EXPECT_EQ(scroll_info->page_scale_delta, 1);
+ ExpectNone(*scroll_info, scroll_layer->id());
+ }
+}
+
+class LayerTreeHostImplOverridePhysicalTime : public LayerTreeHostImpl {
+ public:
+ LayerTreeHostImplOverridePhysicalTime(
+ const LayerTreeSettings& settings,
+ LayerTreeHostImplClient* client,
+ Proxy* proxy,
+ RenderingStatsInstrumentation* rendering_stats_instrumentation)
+ : LayerTreeHostImpl(settings,
+ client,
+ proxy,
+ rendering_stats_instrumentation) {}
+
+
+ virtual base::TimeTicks CurrentPhysicalTimeTicks() const OVERRIDE {
+ return fake_current_physical_time_;
+ }
+
+ void SetCurrentPhysicalTimeTicksForTest(base::TimeTicks fake_now) {
+ fake_current_physical_time_ = fake_now;
+ }
+
+ private:
+ base::TimeTicks fake_current_physical_time_;
+};
+
+TEST_F(LayerTreeHostImplTest, ScrollbarLinearFadeScheduling) {
+ LayerTreeSettings settings;
+ settings.use_linear_fade_scrollbar_animator = true;
+ settings.scrollbar_linear_fade_delay_ms = 20;
+ settings.scrollbar_linear_fade_length_ms = 20;
+
+ gfx::Size viewport_size(10, 10);
+ gfx::Size content_size(100, 100);
+
+ LayerTreeHostImplOverridePhysicalTime* host_impl_override_time =
+ new LayerTreeHostImplOverridePhysicalTime(
+ settings, this, &proxy_, &stats_instrumentation_);
+ host_impl_ = make_scoped_ptr<LayerTreeHostImpl>(host_impl_override_time);
+ host_impl_->InitializeRenderer(CreateOutputSurface());
+ host_impl_->SetViewportSize(viewport_size);
+
+ scoped_ptr<LayerImpl> root =
+ LayerImpl::Create(host_impl_->active_tree(), 1);
+ root->SetBounds(viewport_size);
+
+ scoped_ptr<LayerImpl> scroll =
+ LayerImpl::Create(host_impl_->active_tree(), 2);
+ scroll->SetScrollable(true);
+ scroll->SetScrollOffset(gfx::Vector2d());
+ scroll->SetMaxScrollOffset(gfx::Vector2d(content_size.width(),
+ content_size.height()));
+ scroll->SetBounds(content_size);
+ scroll->SetContentBounds(content_size);
+
+ scoped_ptr<LayerImpl> contents =
+ LayerImpl::Create(host_impl_->active_tree(), 3);
+ contents->SetDrawsContent(true);
+ contents->SetBounds(content_size);
+ contents->SetContentBounds(content_size);
+
+ scoped_ptr<ScrollbarLayerImpl> scrollbar = ScrollbarLayerImpl::Create(
+ host_impl_->active_tree(),
+ 4,
+ VERTICAL);
+ scroll->SetVerticalScrollbarLayer(scrollbar.get());
+
+ scroll->AddChild(contents.Pass());
+ root->AddChild(scroll.Pass());
+ root->AddChild(scrollbar.PassAs<LayerImpl>());
+
+ host_impl_->active_tree()->SetRootLayer(root.Pass());
+ host_impl_->active_tree()->DidBecomeActive();
+ InitializeRendererAndDrawFrame();
+
+ base::TimeTicks fake_now = base::TimeTicks::Now();
+ host_impl_override_time->SetCurrentPhysicalTimeTicksForTest(fake_now);
+
+ // If no scroll happened recently, StartScrollbarAnimation should have no
+ // effect.
+ host_impl_->StartScrollbarAnimation();
+ EXPECT_EQ(base::TimeDelta(), requested_scrollbar_animation_delay_);
+ EXPECT_FALSE(did_request_redraw_);
+
+ // After a scroll, a fade animation should be scheduled about 20ms from now.
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel);
+ host_impl_->ScrollEnd();
+ host_impl_->StartScrollbarAnimation();
+ EXPECT_LT(base::TimeDelta::FromMilliseconds(19),
+ requested_scrollbar_animation_delay_);
+ EXPECT_FALSE(did_request_redraw_);
+ requested_scrollbar_animation_delay_ = base::TimeDelta();
+
+ // After the fade begins, we should start getting redraws instead of a
+ // scheduled animation.
+ fake_now += base::TimeDelta::FromMilliseconds(25);
+ host_impl_override_time->SetCurrentPhysicalTimeTicksForTest(fake_now);
+ host_impl_->StartScrollbarAnimation();
+ EXPECT_EQ(base::TimeDelta(), requested_scrollbar_animation_delay_);
+ EXPECT_TRUE(did_request_redraw_);
+ did_request_redraw_ = false;
+
+ // If no scroll happened recently, StartScrollbarAnimation should have no
+ // effect.
+ fake_now += base::TimeDelta::FromMilliseconds(25);
+ host_impl_override_time->SetCurrentPhysicalTimeTicksForTest(fake_now);
+ host_impl_->StartScrollbarAnimation();
+ EXPECT_EQ(base::TimeDelta(), requested_scrollbar_animation_delay_);
+ EXPECT_FALSE(did_request_redraw_);
+
+ // Setting the scroll offset outside a scroll should also cause the scrollbar
+ // to appear and to schedule a fade.
+ host_impl_->RootScrollLayer()->SetScrollOffset(gfx::Vector2d(5, 5));
+ host_impl_->StartScrollbarAnimation();
+ EXPECT_LT(base::TimeDelta::FromMilliseconds(19),
+ requested_scrollbar_animation_delay_);
+ EXPECT_FALSE(did_request_redraw_);
+ requested_scrollbar_animation_delay_ = base::TimeDelta();
+
+ // None of the above should have called CurrentFrameTimeTicks, so if we call
+ // it now we should get the current time.
+ fake_now += base::TimeDelta::FromMilliseconds(10);
+ host_impl_override_time->SetCurrentPhysicalTimeTicksForTest(fake_now);
+ EXPECT_EQ(fake_now, host_impl_->CurrentFrameTimeTicks());
+}
+
+TEST_F(LayerTreeHostImplTest, CompositorFrameMetadata) {
+ SetupScrollAndContentsLayers(gfx::Size(100, 100));
+ host_impl_->SetViewportSize(gfx::Size(50, 50));
+ host_impl_->active_tree()->SetPageScaleFactorAndLimits(1.f, 0.5f, 4.f);
+ InitializeRendererAndDrawFrame();
+ {
+ CompositorFrameMetadata metadata =
+ host_impl_->MakeCompositorFrameMetadata();
+ EXPECT_EQ(gfx::Vector2dF(), metadata.root_scroll_offset);
+ EXPECT_EQ(1.f, metadata.page_scale_factor);
+ EXPECT_EQ(gfx::SizeF(50.f, 50.f), metadata.viewport_size);
+ EXPECT_EQ(gfx::SizeF(100.f, 100.f), metadata.root_layer_size);
+ EXPECT_EQ(0.5f, metadata.min_page_scale_factor);
+ EXPECT_EQ(4.f, metadata.max_page_scale_factor);
+ }
+
+ // Scrolling should update metadata immediately.
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel));
+ host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 10));
+ {
+ CompositorFrameMetadata metadata =
+ host_impl_->MakeCompositorFrameMetadata();
+ EXPECT_EQ(gfx::Vector2dF(0.f, 10.f), metadata.root_scroll_offset);
+ }
+ host_impl_->ScrollEnd();
+ {
+ CompositorFrameMetadata metadata =
+ host_impl_->MakeCompositorFrameMetadata();
+ EXPECT_EQ(gfx::Vector2dF(0.f, 10.f), metadata.root_scroll_offset);
+ }
+
+ // Page scale should update metadata correctly (shrinking only the viewport).
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture);
+ host_impl_->PinchGestureBegin();
+ host_impl_->PinchGestureUpdate(2.f, gfx::Point());
+ host_impl_->PinchGestureEnd();
+ host_impl_->ScrollEnd();
+ {
+ CompositorFrameMetadata metadata =
+ host_impl_->MakeCompositorFrameMetadata();
+ EXPECT_EQ(gfx::Vector2dF(0.f, 10.f), metadata.root_scroll_offset);
+ EXPECT_EQ(2.f, metadata.page_scale_factor);
+ EXPECT_EQ(gfx::SizeF(25.f, 25.f), metadata.viewport_size);
+ EXPECT_EQ(gfx::SizeF(100.f, 100.f), metadata.root_layer_size);
+ EXPECT_EQ(0.5f, metadata.min_page_scale_factor);
+ EXPECT_EQ(4.f, metadata.max_page_scale_factor);
+ }
+
+ // Likewise if set from the main thread.
+ host_impl_->ProcessScrollDeltas();
+ host_impl_->active_tree()->SetPageScaleFactorAndLimits(4.f, 0.5f, 4.f);
+ host_impl_->active_tree()->SetPageScaleDelta(1.f);
+ {
+ CompositorFrameMetadata metadata =
+ host_impl_->MakeCompositorFrameMetadata();
+ EXPECT_EQ(gfx::Vector2dF(0.f, 10.f), metadata.root_scroll_offset);
+ EXPECT_EQ(4.f, metadata.page_scale_factor);
+ EXPECT_EQ(gfx::SizeF(12.5f, 12.5f), metadata.viewport_size);
+ EXPECT_EQ(gfx::SizeF(100.f, 100.f), metadata.root_layer_size);
+ EXPECT_EQ(0.5f, metadata.min_page_scale_factor);
+ EXPECT_EQ(4.f, metadata.max_page_scale_factor);
+ }
+}
+
+class DidDrawCheckLayer : public TiledLayerImpl {
+ public:
+ static scoped_ptr<LayerImpl> Create(LayerTreeImpl* tree_impl, int id) {
+ return scoped_ptr<LayerImpl>(new DidDrawCheckLayer(tree_impl, id));
+ }
+
+ virtual bool WillDraw(DrawMode draw_mode, ResourceProvider* provider)
+ OVERRIDE {
+ will_draw_called_ = true;
+ if (will_draw_returns_false_)
+ return false;
+ return TiledLayerImpl::WillDraw(draw_mode, provider);
+ }
+
+ virtual void AppendQuads(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data) OVERRIDE {
+ append_quads_called_ = true;
+ TiledLayerImpl::AppendQuads(quad_sink, append_quads_data);
+ }
+
+ virtual void DidDraw(ResourceProvider* provider) OVERRIDE {
+ did_draw_called_ = true;
+ TiledLayerImpl::DidDraw(provider);
+ }
+
+ bool will_draw_called() const { return will_draw_called_; }
+ bool append_quads_called() const { return append_quads_called_; }
+ bool did_draw_called() const { return did_draw_called_; }
+
+ void set_will_draw_returns_false() { will_draw_returns_false_ = true; }
+
+ void ClearDidDrawCheck() {
+ will_draw_called_ = false;
+ append_quads_called_ = false;
+ did_draw_called_ = false;
+ }
+
+ protected:
+ DidDrawCheckLayer(LayerTreeImpl* tree_impl, int id)
+ : TiledLayerImpl(tree_impl, id),
+ will_draw_returns_false_(false),
+ will_draw_called_(false),
+ append_quads_called_(false),
+ did_draw_called_(false) {
+ SetAnchorPoint(gfx::PointF());
+ SetBounds(gfx::Size(10, 10));
+ SetContentBounds(gfx::Size(10, 10));
+ SetDrawsContent(true);
+ set_skips_draw(false);
+ draw_properties().visible_content_rect = gfx::Rect(0, 0, 10, 10);
+
+ scoped_ptr<LayerTilingData> tiler =
+ LayerTilingData::Create(gfx::Size(100, 100),
+ LayerTilingData::HAS_BORDER_TEXELS);
+ tiler->SetBounds(content_bounds());
+ SetTilingData(*tiler.get());
+ }
+
+ private:
+ bool will_draw_returns_false_;
+ bool will_draw_called_;
+ bool append_quads_called_;
+ bool did_draw_called_;
+};
+
+TEST_F(LayerTreeHostImplTest, WillDrawReturningFalseDoesNotCall) {
+ // The root layer is always drawn, so run this test on a child layer that
+ // will be masked out by the root layer's bounds.
+ host_impl_->active_tree()->SetRootLayer(
+ DidDrawCheckLayer::Create(host_impl_->active_tree(), 1));
+ DidDrawCheckLayer* root = static_cast<DidDrawCheckLayer*>(
+ host_impl_->active_tree()->root_layer());
+
+ root->AddChild(DidDrawCheckLayer::Create(host_impl_->active_tree(), 2));
+ DidDrawCheckLayer* layer =
+ static_cast<DidDrawCheckLayer*>(root->children()[0]);
+
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect(10, 10)));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+
+ EXPECT_TRUE(layer->will_draw_called());
+ EXPECT_TRUE(layer->append_quads_called());
+ EXPECT_TRUE(layer->did_draw_called());
+ }
+
+ {
+ LayerTreeHostImpl::FrameData frame;
+
+ layer->set_will_draw_returns_false();
+ layer->ClearDidDrawCheck();
+
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect(10, 10)));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+
+ EXPECT_TRUE(layer->will_draw_called());
+ EXPECT_FALSE(layer->append_quads_called());
+ EXPECT_FALSE(layer->did_draw_called());
+ }
+}
+
+TEST_F(LayerTreeHostImplTest, DidDrawNotCalledOnHiddenLayer) {
+ // The root layer is always drawn, so run this test on a child layer that
+ // will be masked out by the root layer's bounds.
+ host_impl_->active_tree()->SetRootLayer(
+ DidDrawCheckLayer::Create(host_impl_->active_tree(), 1));
+ DidDrawCheckLayer* root = static_cast<DidDrawCheckLayer*>(
+ host_impl_->active_tree()->root_layer());
+ root->SetMasksToBounds(true);
+
+ root->AddChild(DidDrawCheckLayer::Create(host_impl_->active_tree(), 2));
+ DidDrawCheckLayer* layer =
+ static_cast<DidDrawCheckLayer*>(root->children()[0]);
+ // Ensure visible_content_rect for layer is empty.
+ layer->SetPosition(gfx::PointF(100.f, 100.f));
+ layer->SetBounds(gfx::Size(10, 10));
+ layer->SetContentBounds(gfx::Size(10, 10));
+
+ LayerTreeHostImpl::FrameData frame;
+
+ EXPECT_FALSE(layer->will_draw_called());
+ EXPECT_FALSE(layer->did_draw_called());
+
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+
+ EXPECT_FALSE(layer->will_draw_called());
+ EXPECT_FALSE(layer->did_draw_called());
+
+ EXPECT_TRUE(layer->visible_content_rect().IsEmpty());
+
+ // Ensure visible_content_rect for layer is not empty
+ layer->SetPosition(gfx::PointF());
+
+ EXPECT_FALSE(layer->will_draw_called());
+ EXPECT_FALSE(layer->did_draw_called());
+
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+
+ EXPECT_TRUE(layer->will_draw_called());
+ EXPECT_TRUE(layer->did_draw_called());
+
+ EXPECT_FALSE(layer->visible_content_rect().IsEmpty());
+}
+
+TEST_F(LayerTreeHostImplTest, WillDrawNotCalledOnOccludedLayer) {
+ gfx::Size big_size(1000, 1000);
+ host_impl_->SetViewportSize(big_size);
+
+ host_impl_->active_tree()->SetRootLayer(
+ DidDrawCheckLayer::Create(host_impl_->active_tree(), 1));
+ DidDrawCheckLayer* root =
+ static_cast<DidDrawCheckLayer*>(host_impl_->active_tree()->root_layer());
+
+ root->AddChild(DidDrawCheckLayer::Create(host_impl_->active_tree(), 2));
+ DidDrawCheckLayer* occluded_layer =
+ static_cast<DidDrawCheckLayer*>(root->children()[0]);
+
+ root->AddChild(DidDrawCheckLayer::Create(host_impl_->active_tree(), 3));
+ DidDrawCheckLayer* top_layer =
+ static_cast<DidDrawCheckLayer*>(root->children()[1]);
+ // This layer covers the occluded_layer above. Make this layer large so it can
+ // occlude.
+ top_layer->SetBounds(big_size);
+ top_layer->SetContentBounds(big_size);
+ top_layer->SetContentsOpaque(true);
+
+ LayerTreeHostImpl::FrameData frame;
+
+ EXPECT_FALSE(occluded_layer->will_draw_called());
+ EXPECT_FALSE(occluded_layer->did_draw_called());
+ EXPECT_FALSE(top_layer->will_draw_called());
+ EXPECT_FALSE(top_layer->did_draw_called());
+
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+
+ EXPECT_FALSE(occluded_layer->will_draw_called());
+ EXPECT_FALSE(occluded_layer->did_draw_called());
+ EXPECT_TRUE(top_layer->will_draw_called());
+ EXPECT_TRUE(top_layer->did_draw_called());
+}
+
+TEST_F(LayerTreeHostImplTest, DidDrawCalledOnAllLayers) {
+ host_impl_->active_tree()->SetRootLayer(
+ DidDrawCheckLayer::Create(host_impl_->active_tree(), 1));
+ DidDrawCheckLayer* root =
+ static_cast<DidDrawCheckLayer*>(host_impl_->active_tree()->root_layer());
+
+ root->AddChild(DidDrawCheckLayer::Create(host_impl_->active_tree(), 2));
+ DidDrawCheckLayer* layer1 =
+ static_cast<DidDrawCheckLayer*>(root->children()[0]);
+
+ layer1->AddChild(DidDrawCheckLayer::Create(host_impl_->active_tree(), 3));
+ DidDrawCheckLayer* layer2 =
+ static_cast<DidDrawCheckLayer*>(layer1->children()[0]);
+
+ layer1->SetOpacity(0.3f);
+ layer1->SetPreserves3d(false);
+
+ EXPECT_FALSE(root->did_draw_called());
+ EXPECT_FALSE(layer1->did_draw_called());
+ EXPECT_FALSE(layer2->did_draw_called());
+
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+
+ EXPECT_TRUE(root->did_draw_called());
+ EXPECT_TRUE(layer1->did_draw_called());
+ EXPECT_TRUE(layer2->did_draw_called());
+
+ EXPECT_NE(root->render_surface(), layer1->render_surface());
+ EXPECT_TRUE(!!layer1->render_surface());
+}
+
+class MissingTextureAnimatingLayer : public DidDrawCheckLayer {
+ public:
+ static scoped_ptr<LayerImpl> Create(LayerTreeImpl* tree_impl,
+ int id,
+ bool tile_missing,
+ bool skips_draw,
+ bool animating,
+ ResourceProvider* resource_provider) {
+ return scoped_ptr<LayerImpl>(new MissingTextureAnimatingLayer(
+ tree_impl,
+ id,
+ tile_missing,
+ skips_draw,
+ animating,
+ resource_provider));
+ }
+
+ private:
+ MissingTextureAnimatingLayer(LayerTreeImpl* tree_impl,
+ int id,
+ bool tile_missing,
+ bool skips_draw,
+ bool animating,
+ ResourceProvider* resource_provider)
+ : DidDrawCheckLayer(tree_impl, id) {
+ scoped_ptr<LayerTilingData> tiling_data =
+ LayerTilingData::Create(gfx::Size(10, 10),
+ LayerTilingData::NO_BORDER_TEXELS);
+ tiling_data->SetBounds(bounds());
+ SetTilingData(*tiling_data.get());
+ set_skips_draw(skips_draw);
+ if (!tile_missing) {
+ ResourceProvider::ResourceId resource =
+ resource_provider->CreateResource(gfx::Size(1, 1),
+ GL_RGBA,
+ ResourceProvider::TextureUsageAny);
+ resource_provider->AllocateForTesting(resource);
+ PushTileProperties(0, 0, resource, gfx::Rect(), false);
+ }
+ if (animating)
+ AddAnimatedTransformToLayer(this, 10.0, 3, 0);
+ }
+};
+
+TEST_F(LayerTreeHostImplTest, PrepareToDrawFailsWhenAnimationUsesCheckerboard) {
+ // When the texture is not missing, we draw as usual.
+ host_impl_->active_tree()->SetRootLayer(
+ DidDrawCheckLayer::Create(host_impl_->active_tree(), 1));
+ DidDrawCheckLayer* root =
+ static_cast<DidDrawCheckLayer*>(host_impl_->active_tree()->root_layer());
+ root->AddChild(
+ MissingTextureAnimatingLayer::Create(host_impl_->active_tree(),
+ 2,
+ false,
+ false,
+ true,
+ host_impl_->resource_provider()));
+
+ LayerTreeHostImpl::FrameData frame;
+
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+
+ // When a texture is missing and we're not animating, we draw as usual with
+ // checkerboarding.
+ host_impl_->active_tree()->SetRootLayer(
+ DidDrawCheckLayer::Create(host_impl_->active_tree(), 3));
+ root =
+ static_cast<DidDrawCheckLayer*>(host_impl_->active_tree()->root_layer());
+ root->AddChild(
+ MissingTextureAnimatingLayer::Create(host_impl_->active_tree(),
+ 4,
+ true,
+ false,
+ false,
+ host_impl_->resource_provider()));
+
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+
+ // When a texture is missing and we're animating, we don't want to draw
+ // anything.
+ host_impl_->active_tree()->SetRootLayer(
+ DidDrawCheckLayer::Create(host_impl_->active_tree(), 5));
+ root =
+ static_cast<DidDrawCheckLayer*>(host_impl_->active_tree()->root_layer());
+ root->AddChild(
+ MissingTextureAnimatingLayer::Create(host_impl_->active_tree(),
+ 6,
+ true,
+ false,
+ true,
+ host_impl_->resource_provider()));
+
+ EXPECT_FALSE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+
+ // When the layer skips draw and we're animating, we still draw the frame.
+ host_impl_->active_tree()->SetRootLayer(
+ DidDrawCheckLayer::Create(host_impl_->active_tree(), 7));
+ root =
+ static_cast<DidDrawCheckLayer*>(host_impl_->active_tree()->root_layer());
+ root->AddChild(
+ MissingTextureAnimatingLayer::Create(host_impl_->active_tree(),
+ 8,
+ false,
+ true,
+ true,
+ host_impl_->resource_provider()));
+
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+}
+
+TEST_F(LayerTreeHostImplTest, ScrollRootIgnored) {
+ scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl_->active_tree(), 1);
+ root->SetScrollable(false);
+ host_impl_->active_tree()->SetRootLayer(root.Pass());
+ InitializeRendererAndDrawFrame();
+
+ // Scroll event is ignored because layer is not scrollable.
+ EXPECT_EQ(InputHandler::ScrollIgnored,
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel));
+ EXPECT_FALSE(did_request_redraw_);
+ EXPECT_FALSE(did_request_commit_);
+}
+
+TEST_F(LayerTreeHostImplTest, ScrollNonScrollableRootWithTopControls) {
+ LayerTreeSettings settings;
+ settings.calculate_top_controls_position = true;
+ settings.top_controls_height = 50;
+
+ host_impl_ = LayerTreeHostImpl::Create(settings,
+ this,
+ &proxy_,
+ &stats_instrumentation_);
+ host_impl_->InitializeRenderer(CreateOutputSurface());
+ host_impl_->SetViewportSize(gfx::Size(10, 10));
+
+ gfx::Size layer_size(5, 5);
+ scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl_->active_tree(), 1);
+ root->SetScrollable(true);
+ root->SetMaxScrollOffset(gfx::Vector2d(layer_size.width(),
+ layer_size.height()));
+ root->SetBounds(layer_size);
+ root->SetContentBounds(layer_size);
+ root->SetPosition(gfx::PointF());
+ root->SetAnchorPoint(gfx::PointF());
+ root->SetDrawsContent(false);
+ host_impl_->active_tree()->SetRootLayer(root.Pass());
+ host_impl_->active_tree()->FindRootScrollLayer();
+ InitializeRendererAndDrawFrame();
+
+ EXPECT_EQ(InputHandler::ScrollIgnored,
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture));
+
+ host_impl_->top_controls_manager()->ScrollBegin();
+ host_impl_->top_controls_manager()->ScrollBy(gfx::Vector2dF(0.f, 50.f));
+ host_impl_->top_controls_manager()->ScrollEnd();
+ EXPECT_EQ(host_impl_->top_controls_manager()->content_top_offset(), 0.f);
+
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(),
+ InputHandler::Gesture));
+}
+
+TEST_F(LayerTreeHostImplTest, ScrollNonCompositedRoot) {
+ // Test the configuration where a non-composited root layer is embedded in a
+ // scrollable outer layer.
+ gfx::Size surface_size(10, 10);
+
+ scoped_ptr<LayerImpl> content_layer =
+ LayerImpl::Create(host_impl_->active_tree(), 1);
+ content_layer->SetDrawsContent(true);
+ content_layer->SetPosition(gfx::PointF());
+ content_layer->SetAnchorPoint(gfx::PointF());
+ content_layer->SetBounds(surface_size);
+ content_layer->SetContentBounds(gfx::Size(surface_size.width() * 2,
+ surface_size.height() * 2));
+ content_layer->SetContentsScale(2.f, 2.f);
+
+ scoped_ptr<LayerImpl> scroll_layer =
+ LayerImpl::Create(host_impl_->active_tree(), 2);
+ scroll_layer->SetScrollable(true);
+ scroll_layer->SetMaxScrollOffset(gfx::Vector2d(surface_size.width(),
+ surface_size.height()));
+ scroll_layer->SetBounds(surface_size);
+ scroll_layer->SetContentBounds(surface_size);
+ scroll_layer->SetPosition(gfx::PointF());
+ scroll_layer->SetAnchorPoint(gfx::PointF());
+ scroll_layer->AddChild(content_layer.Pass());
+
+ host_impl_->active_tree()->SetRootLayer(scroll_layer.Pass());
+ host_impl_->SetViewportSize(surface_size);
+ InitializeRendererAndDrawFrame();
+
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(5, 5),
+ InputHandler::Wheel));
+ host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 10));
+ host_impl_->ScrollEnd();
+ EXPECT_TRUE(did_request_redraw_);
+ EXPECT_TRUE(did_request_commit_);
+}
+
+TEST_F(LayerTreeHostImplTest, ScrollChildCallsCommitAndRedraw) {
+ gfx::Size surface_size(10, 10);
+ scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl_->active_tree(), 1);
+ root->SetBounds(surface_size);
+ root->SetContentBounds(surface_size);
+ root->AddChild(CreateScrollableLayer(2, surface_size));
+ host_impl_->active_tree()->SetRootLayer(root.Pass());
+ host_impl_->SetViewportSize(surface_size);
+ InitializeRendererAndDrawFrame();
+
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(5, 5),
+ InputHandler::Wheel));
+ host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 10));
+ host_impl_->ScrollEnd();
+ EXPECT_TRUE(did_request_redraw_);
+ EXPECT_TRUE(did_request_commit_);
+}
+
+TEST_F(LayerTreeHostImplTest, ScrollMissesChild) {
+ gfx::Size surface_size(10, 10);
+ scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl_->active_tree(), 1);
+ root->AddChild(CreateScrollableLayer(2, surface_size));
+ host_impl_->active_tree()->SetRootLayer(root.Pass());
+ host_impl_->SetViewportSize(surface_size);
+ InitializeRendererAndDrawFrame();
+
+ // Scroll event is ignored because the input coordinate is outside the layer
+ // boundaries.
+ EXPECT_EQ(InputHandler::ScrollIgnored,
+ host_impl_->ScrollBegin(gfx::Point(15, 5),
+ InputHandler::Wheel));
+ EXPECT_FALSE(did_request_redraw_);
+ EXPECT_FALSE(did_request_commit_);
+}
+
+TEST_F(LayerTreeHostImplTest, ScrollMissesBackfacingChild) {
+ gfx::Size surface_size(10, 10);
+ scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl_->active_tree(), 1);
+ scoped_ptr<LayerImpl> child = CreateScrollableLayer(2, surface_size);
+ host_impl_->SetViewportSize(surface_size);
+
+ gfx::Transform matrix;
+ matrix.RotateAboutXAxis(180.0);
+ child->SetTransform(matrix);
+ child->SetDoubleSided(false);
+
+ root->AddChild(child.Pass());
+ host_impl_->active_tree()->SetRootLayer(root.Pass());
+ InitializeRendererAndDrawFrame();
+
+ // Scroll event is ignored because the scrollable layer is not facing the
+ // viewer and there is nothing scrollable behind it.
+ EXPECT_EQ(InputHandler::ScrollIgnored,
+ host_impl_->ScrollBegin(gfx::Point(5, 5),
+ InputHandler::Wheel));
+ EXPECT_FALSE(did_request_redraw_);
+ EXPECT_FALSE(did_request_commit_);
+}
+
+TEST_F(LayerTreeHostImplTest, ScrollBlockedByContentLayer) {
+ gfx::Size surface_size(10, 10);
+ scoped_ptr<LayerImpl> content_layer = CreateScrollableLayer(1, surface_size);
+ content_layer->SetShouldScrollOnMainThread(true);
+ content_layer->SetScrollable(false);
+
+ scoped_ptr<LayerImpl> scroll_layer = CreateScrollableLayer(2, surface_size);
+ scroll_layer->AddChild(content_layer.Pass());
+
+ host_impl_->active_tree()->SetRootLayer(scroll_layer.Pass());
+ host_impl_->SetViewportSize(surface_size);
+ InitializeRendererAndDrawFrame();
+
+ // Scrolling fails because the content layer is asking to be scrolled on the
+ // main thread.
+ EXPECT_EQ(InputHandler::ScrollOnMainThread,
+ host_impl_->ScrollBegin(gfx::Point(5, 5),
+ InputHandler::Wheel));
+}
+
+TEST_F(LayerTreeHostImplTest, ScrollRootAndChangePageScaleOnMainThread) {
+ gfx::Size surface_size(10, 10);
+ float page_scale = 2.f;
+ scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl_->active_tree(), 1);
+ scoped_ptr<LayerImpl> root_scrolling = CreateScrollableLayer(2, surface_size);
+ root->AddChild(root_scrolling.Pass());
+ host_impl_->active_tree()->SetRootLayer(root.Pass());
+ host_impl_->active_tree()->DidBecomeActive();
+ host_impl_->SetViewportSize(surface_size);
+ InitializeRendererAndDrawFrame();
+
+ LayerImpl* root_scroll = host_impl_->active_tree()->RootScrollLayer();
+
+ gfx::Vector2d scroll_delta(0, 10);
+ gfx::Vector2d expected_scroll_delta = scroll_delta;
+ gfx::Vector2d expected_max_scroll = root_scroll->max_scroll_offset();
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(5, 5),
+ InputHandler::Wheel));
+ host_impl_->ScrollBy(gfx::Point(), scroll_delta);
+ host_impl_->ScrollEnd();
+
+ // Set new page scale from main thread.
+ host_impl_->active_tree()->SetPageScaleFactorAndLimits(page_scale,
+ page_scale,
+ page_scale);
+
+ scoped_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
+ ExpectContains(*scroll_info.get(), root_scroll->id(), expected_scroll_delta);
+
+ // The scroll range should also have been updated.
+ EXPECT_EQ(expected_max_scroll, root_scroll->max_scroll_offset());
+
+ // The page scale delta remains constant because the impl thread did not
+ // scale.
+ EXPECT_EQ(1.f, host_impl_->active_tree()->page_scale_delta());
+}
+
+TEST_F(LayerTreeHostImplTest, ScrollRootAndChangePageScaleOnImplThread) {
+ gfx::Size surface_size(10, 10);
+ float page_scale = 2.f;
+ scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl_->active_tree(), 1);
+ scoped_ptr<LayerImpl> root_scrolling = CreateScrollableLayer(2, surface_size);
+ root->AddChild(root_scrolling.Pass());
+ host_impl_->active_tree()->SetRootLayer(root.Pass());
+ host_impl_->active_tree()->DidBecomeActive();
+ host_impl_->SetViewportSize(surface_size);
+ host_impl_->active_tree()->SetPageScaleFactorAndLimits(1.f, 1.f, page_scale);
+ InitializeRendererAndDrawFrame();
+
+ LayerImpl* root_scroll = host_impl_->active_tree()->RootScrollLayer();
+
+ gfx::Vector2d scroll_delta(0, 10);
+ gfx::Vector2d expected_scroll_delta = scroll_delta;
+ gfx::Vector2d expected_max_scroll = root_scroll->max_scroll_offset();
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(5, 5),
+ InputHandler::Wheel));
+ host_impl_->ScrollBy(gfx::Point(), scroll_delta);
+ host_impl_->ScrollEnd();
+
+ // Set new page scale on impl thread by pinching.
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture);
+ host_impl_->PinchGestureBegin();
+ host_impl_->PinchGestureUpdate(page_scale, gfx::Point());
+ host_impl_->PinchGestureEnd();
+ host_impl_->ScrollEnd();
+ DrawOneFrame();
+
+ // The scroll delta is not scaled because the main thread did not scale.
+ scoped_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
+ ExpectContains(*scroll_info.get(), root_scroll->id(), expected_scroll_delta);
+
+ // The scroll range should also have been updated.
+ EXPECT_EQ(expected_max_scroll, root_scroll->max_scroll_offset());
+
+ // The page scale delta should match the new scale on the impl side.
+ EXPECT_EQ(page_scale, host_impl_->active_tree()->total_page_scale_factor());
+}
+
+TEST_F(LayerTreeHostImplTest, PageScaleDeltaAppliedToRootScrollLayerOnly) {
+ gfx::Size surface_size(10, 10);
+ float default_page_scale = 1.f;
+ gfx::Transform default_page_scale_matrix;
+ default_page_scale_matrix.Scale(default_page_scale, default_page_scale);
+
+ float new_page_scale = 2.f;
+ gfx::Transform new_page_scale_matrix;
+ new_page_scale_matrix.Scale(new_page_scale, new_page_scale);
+
+ // Create a normal scrollable root layer and another scrollable child layer.
+ LayerImpl* scroll = SetupScrollAndContentsLayers(surface_size);
+ LayerImpl* root = host_impl_->active_tree()->root_layer();
+ LayerImpl* child = scroll->children()[0];
+
+ scoped_ptr<LayerImpl> scrollable_child =
+ CreateScrollableLayer(4, surface_size);
+ child->AddChild(scrollable_child.Pass());
+ LayerImpl* grand_child = child->children()[0];
+
+ // Set new page scale on impl thread by pinching.
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture);
+ host_impl_->PinchGestureBegin();
+ host_impl_->PinchGestureUpdate(new_page_scale, gfx::Point());
+ host_impl_->PinchGestureEnd();
+ host_impl_->ScrollEnd();
+ DrawOneFrame();
+
+ EXPECT_EQ(1.f, root->contents_scale_x());
+ EXPECT_EQ(1.f, root->contents_scale_y());
+ EXPECT_EQ(1.f, scroll->contents_scale_x());
+ EXPECT_EQ(1.f, scroll->contents_scale_y());
+ EXPECT_EQ(1.f, child->contents_scale_x());
+ EXPECT_EQ(1.f, child->contents_scale_y());
+ EXPECT_EQ(1.f, grand_child->contents_scale_x());
+ EXPECT_EQ(1.f, grand_child->contents_scale_y());
+
+ // Make sure all the layers are drawn with the page scale delta applied, i.e.,
+ // the page scale delta on the root layer is applied hierarchically.
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+
+ EXPECT_EQ(1.f, root->draw_transform().matrix().getDouble(0, 0));
+ EXPECT_EQ(1.f, root->draw_transform().matrix().getDouble(1, 1));
+ EXPECT_EQ(new_page_scale, scroll->draw_transform().matrix().getDouble(0, 0));
+ EXPECT_EQ(new_page_scale, scroll->draw_transform().matrix().getDouble(1, 1));
+ EXPECT_EQ(new_page_scale, child->draw_transform().matrix().getDouble(0, 0));
+ EXPECT_EQ(new_page_scale, child->draw_transform().matrix().getDouble(1, 1));
+ EXPECT_EQ(new_page_scale,
+ grand_child->draw_transform().matrix().getDouble(0, 0));
+ EXPECT_EQ(new_page_scale,
+ grand_child->draw_transform().matrix().getDouble(1, 1));
+}
+
+TEST_F(LayerTreeHostImplTest, ScrollChildAndChangePageScaleOnMainThread) {
+ gfx::Size surface_size(10, 10);
+ scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl_->active_tree(), 1);
+ scoped_ptr<LayerImpl> root_scrolling =
+ LayerImpl::Create(host_impl_->active_tree(), 2);
+ root_scrolling->SetBounds(surface_size);
+ root_scrolling->SetContentBounds(surface_size);
+ root_scrolling->SetScrollable(true);
+ root->AddChild(root_scrolling.Pass());
+ int child_scroll_layer_id = 3;
+ scoped_ptr<LayerImpl> child_scrolling =
+ CreateScrollableLayer(child_scroll_layer_id, surface_size);
+ LayerImpl* child = child_scrolling.get();
+ root->AddChild(child_scrolling.Pass());
+ host_impl_->active_tree()->SetRootLayer(root.Pass());
+ host_impl_->active_tree()->DidBecomeActive();
+ host_impl_->SetViewportSize(surface_size);
+ InitializeRendererAndDrawFrame();
+
+ gfx::Vector2d scroll_delta(0, 10);
+ gfx::Vector2d expected_scroll_delta(scroll_delta);
+ gfx::Vector2d expected_max_scroll(child->max_scroll_offset());
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(5, 5),
+ InputHandler::Wheel));
+ host_impl_->ScrollBy(gfx::Point(), scroll_delta);
+ host_impl_->ScrollEnd();
+
+ float page_scale = 2.f;
+ host_impl_->active_tree()->SetPageScaleFactorAndLimits(page_scale,
+ 1.f,
+ page_scale);
+
+ DrawOneFrame();
+
+ scoped_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
+ ExpectContains(
+ *scroll_info.get(), child_scroll_layer_id, expected_scroll_delta);
+
+ // The scroll range should not have changed.
+ EXPECT_EQ(child->max_scroll_offset(), expected_max_scroll);
+
+ // The page scale delta remains constant because the impl thread did not
+ // scale.
+ EXPECT_EQ(1.f, host_impl_->active_tree()->page_scale_delta());
+}
+
+TEST_F(LayerTreeHostImplTest, ScrollChildBeyondLimit) {
+ // Scroll a child layer beyond its maximum scroll range and make sure the
+ // parent layer is scrolled on the axis on which the child was unable to
+ // scroll.
+ gfx::Size surface_size(10, 10);
+ scoped_ptr<LayerImpl> root = CreateScrollableLayer(1, surface_size);
+
+ scoped_ptr<LayerImpl> grand_child = CreateScrollableLayer(3, surface_size);
+ grand_child->SetScrollOffset(gfx::Vector2d(0, 5));
+
+ scoped_ptr<LayerImpl> child = CreateScrollableLayer(2, surface_size);
+ child->SetScrollOffset(gfx::Vector2d(3, 0));
+ child->AddChild(grand_child.Pass());
+
+ root->AddChild(child.Pass());
+ host_impl_->active_tree()->SetRootLayer(root.Pass());
+ host_impl_->active_tree()->DidBecomeActive();
+ host_impl_->SetViewportSize(surface_size);
+ InitializeRendererAndDrawFrame();
+ {
+ gfx::Vector2d scroll_delta(-8, -7);
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(),
+ InputHandler::Wheel));
+ host_impl_->ScrollBy(gfx::Point(), scroll_delta);
+ host_impl_->ScrollEnd();
+
+ scoped_ptr<ScrollAndScaleSet> scroll_info =
+ host_impl_->ProcessScrollDeltas();
+
+ // The grand child should have scrolled up to its limit.
+ LayerImpl* child = host_impl_->active_tree()->root_layer()->children()[0];
+ LayerImpl* grand_child = child->children()[0];
+ ExpectContains(*scroll_info.get(), grand_child->id(), gfx::Vector2d(0, -5));
+
+ // The child should have only scrolled on the other axis.
+ ExpectContains(*scroll_info.get(), child->id(), gfx::Vector2d(-3, 0));
+ }
+}
+
+TEST_F(LayerTreeHostImplTest, ScrollWithoutBubbling) {
+ // Scroll a child layer beyond its maximum scroll range and make sure the
+ // the scroll doesn't bubble up to the parent layer.
+ gfx::Size surface_size(10, 10);
+ scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl_->active_tree(), 1);
+ scoped_ptr<LayerImpl> root_scrolling = CreateScrollableLayer(2, surface_size);
+
+ scoped_ptr<LayerImpl> grand_child = CreateScrollableLayer(4, surface_size);
+ grand_child->SetScrollOffset(gfx::Vector2d(0, 2));
+
+ scoped_ptr<LayerImpl> child = CreateScrollableLayer(3, surface_size);
+ child->SetScrollOffset(gfx::Vector2d(0, 3));
+ child->AddChild(grand_child.Pass());
+
+ root_scrolling->AddChild(child.Pass());
+ root->AddChild(root_scrolling.Pass());
+ host_impl_->active_tree()->SetRootLayer(root.Pass());
+ host_impl_->active_tree()->DidBecomeActive();
+ host_impl_->SetViewportSize(surface_size);
+ InitializeRendererAndDrawFrame();
+ {
+ gfx::Vector2d scroll_delta(0, -10);
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(),
+ InputHandler::NonBubblingGesture));
+ host_impl_->ScrollBy(gfx::Point(), scroll_delta);
+ host_impl_->ScrollEnd();
+
+ scoped_ptr<ScrollAndScaleSet> scroll_info =
+ host_impl_->ProcessScrollDeltas();
+
+ // The grand child should have scrolled up to its limit.
+ LayerImpl* child =
+ host_impl_->active_tree()->root_layer()->children()[0]->children()[0];
+ LayerImpl* grand_child = child->children()[0];
+ ExpectContains(*scroll_info.get(), grand_child->id(), gfx::Vector2d(0, -2));
+
+ // The child should not have scrolled.
+ ExpectNone(*scroll_info.get(), child->id());
+
+ // The next time we scroll we should only scroll the parent.
+ scroll_delta = gfx::Vector2d(0, -3);
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(5, 5),
+ InputHandler::NonBubblingGesture));
+ EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), grand_child);
+ host_impl_->ScrollBy(gfx::Point(), scroll_delta);
+ EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), child);
+ host_impl_->ScrollEnd();
+
+ scroll_info = host_impl_->ProcessScrollDeltas();
+
+ // The child should have scrolled up to its limit.
+ ExpectContains(*scroll_info.get(), child->id(), gfx::Vector2d(0, -3));
+
+ // The grand child should not have scrolled.
+ ExpectContains(*scroll_info.get(), grand_child->id(), gfx::Vector2d(0, -2));
+
+ // After scrolling the parent, another scroll on the opposite direction
+ // should still scroll the child.
+ scroll_delta = gfx::Vector2d(0, 7);
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(5, 5),
+ InputHandler::NonBubblingGesture));
+ EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), grand_child);
+ host_impl_->ScrollBy(gfx::Point(), scroll_delta);
+ EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), grand_child);
+ host_impl_->ScrollEnd();
+
+ scroll_info = host_impl_->ProcessScrollDeltas();
+
+ // The grand child should have scrolled.
+ ExpectContains(*scroll_info.get(), grand_child->id(), gfx::Vector2d(0, 5));
+
+ // The child should not have scrolled.
+ ExpectContains(*scroll_info.get(), child->id(), gfx::Vector2d(0, -3));
+
+
+ // Scrolling should be adjusted from viewport space.
+ host_impl_->active_tree()->SetPageScaleFactorAndLimits(2.f, 2.f, 2.f);
+ host_impl_->active_tree()->SetPageScaleDelta(1.f);
+
+ scroll_delta = gfx::Vector2d(0, -2);
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(1, 1),
+ InputHandler::NonBubblingGesture));
+ EXPECT_EQ(grand_child, host_impl_->CurrentlyScrollingLayer());
+ host_impl_->ScrollBy(gfx::Point(), scroll_delta);
+ host_impl_->ScrollEnd();
+
+ scroll_info = host_impl_->ProcessScrollDeltas();
+
+ // Should have scrolled by half the amount in layer space (5 - 2/2)
+ ExpectContains(*scroll_info.get(), grand_child->id(), gfx::Vector2d(0, 4));
+ }
+}
+
+TEST_F(LayerTreeHostImplTest, ScrollEventBubbling) {
+ // When we try to scroll a non-scrollable child layer, the scroll delta
+ // should be applied to one of its ancestors if possible.
+ gfx::Size surface_size(10, 10);
+ gfx::Size content_size(20, 20);
+ scoped_ptr<LayerImpl> root = CreateScrollableLayer(1, content_size);
+ scoped_ptr<LayerImpl> child = CreateScrollableLayer(2, content_size);
+
+ child->SetScrollable(false);
+ root->AddChild(child.Pass());
+
+ host_impl_->SetViewportSize(surface_size);
+ host_impl_->active_tree()->SetRootLayer(root.Pass());
+ host_impl_->active_tree()->DidBecomeActive();
+ InitializeRendererAndDrawFrame();
+ {
+ gfx::Vector2d scroll_delta(0, 4);
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(5, 5),
+ InputHandler::Wheel));
+ host_impl_->ScrollBy(gfx::Point(), scroll_delta);
+ host_impl_->ScrollEnd();
+
+ scoped_ptr<ScrollAndScaleSet> scroll_info =
+ host_impl_->ProcessScrollDeltas();
+
+ // Only the root should have scrolled.
+ ASSERT_EQ(scroll_info->scrolls.size(), 1u);
+ ExpectContains(*scroll_info.get(),
+ host_impl_->active_tree()->root_layer()->id(),
+ scroll_delta);
+ }
+}
+
+TEST_F(LayerTreeHostImplTest, ScrollBeforeRedraw) {
+ gfx::Size surface_size(10, 10);
+ host_impl_->active_tree()->SetRootLayer(
+ CreateScrollableLayer(1, surface_size));
+ host_impl_->active_tree()->DidBecomeActive();
+ host_impl_->SetViewportSize(surface_size);
+
+ // Draw one frame and then immediately rebuild the layer tree to mimic a tree
+ // synchronization.
+ InitializeRendererAndDrawFrame();
+ host_impl_->active_tree()->DetachLayerTree();
+ host_impl_->active_tree()->SetRootLayer(
+ CreateScrollableLayer(2, surface_size));
+ host_impl_->active_tree()->DidBecomeActive();
+
+ // Scrolling should still work even though we did not draw yet.
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(5, 5),
+ InputHandler::Wheel));
+}
+
+TEST_F(LayerTreeHostImplTest, ScrollAxisAlignedRotatedLayer) {
+ LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
+
+ // Rotate the root layer 90 degrees counter-clockwise about its center.
+ gfx::Transform rotate_transform;
+ rotate_transform.Rotate(-90.0);
+ host_impl_->active_tree()->root_layer()->SetTransform(rotate_transform);
+
+ gfx::Size surface_size(50, 50);
+ host_impl_->SetViewportSize(surface_size);
+ InitializeRendererAndDrawFrame();
+
+ // Scroll to the right in screen coordinates with a gesture.
+ gfx::Vector2d gesture_scroll_delta(10, 0);
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(),
+ InputHandler::Gesture));
+ host_impl_->ScrollBy(gfx::Point(), gesture_scroll_delta);
+ host_impl_->ScrollEnd();
+
+ // The layer should have scrolled down in its local coordinates.
+ scoped_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
+ ExpectContains(*scroll_info.get(),
+ scroll_layer->id(),
+ gfx::Vector2d(0, gesture_scroll_delta.x()));
+
+ // Reset and scroll down with the wheel.
+ scroll_layer->SetScrollDelta(gfx::Vector2dF());
+ gfx::Vector2d wheel_scroll_delta(0, 10);
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(),
+ InputHandler::Wheel));
+ host_impl_->ScrollBy(gfx::Point(), wheel_scroll_delta);
+ host_impl_->ScrollEnd();
+
+ // The layer should have scrolled down in its local coordinates.
+ scroll_info = host_impl_->ProcessScrollDeltas();
+ ExpectContains(*scroll_info.get(),
+ scroll_layer->id(),
+ wheel_scroll_delta);
+}
+
+TEST_F(LayerTreeHostImplTest, ScrollNonAxisAlignedRotatedLayer) {
+ LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
+ int child_layer_id = 4;
+ float child_layer_angle = -20.f;
+
+ // Create a child layer that is rotated to a non-axis-aligned angle.
+ scoped_ptr<LayerImpl> child = CreateScrollableLayer(
+ child_layer_id,
+ scroll_layer->content_bounds());
+ gfx::Transform rotate_transform;
+ rotate_transform.Translate(-50.0, -50.0);
+ rotate_transform.Rotate(child_layer_angle);
+ rotate_transform.Translate(50.0, 50.0);
+ child->SetTransform(rotate_transform);
+
+ // Only allow vertical scrolling.
+ child->SetMaxScrollOffset(gfx::Vector2d(0, child->content_bounds().height()));
+ scroll_layer->AddChild(child.Pass());
+
+ gfx::Size surface_size(50, 50);
+ host_impl_->SetViewportSize(surface_size);
+ InitializeRendererAndDrawFrame();
+ {
+ // Scroll down in screen coordinates with a gesture.
+ gfx::Vector2d gesture_scroll_delta(0, 10);
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(),
+ InputHandler::Gesture));
+ host_impl_->ScrollBy(gfx::Point(), gesture_scroll_delta);
+ host_impl_->ScrollEnd();
+
+ // The child layer should have scrolled down in its local coordinates an
+ // amount proportional to the angle between it and the input scroll delta.
+ gfx::Vector2d expected_scroll_delta(
+ 0,
+ gesture_scroll_delta.y() *
+ std::cos(MathUtil::Deg2Rad(child_layer_angle)));
+ scoped_ptr<ScrollAndScaleSet> scroll_info =
+ host_impl_->ProcessScrollDeltas();
+ ExpectContains(*scroll_info.get(), child_layer_id, expected_scroll_delta);
+
+ // The root scroll layer should not have scrolled, because the input delta
+ // was close to the layer's axis of movement.
+ EXPECT_EQ(scroll_info->scrolls.size(), 1u);
+ }
+ {
+ // Now reset and scroll the same amount horizontally.
+ scroll_layer->children()[1]->SetScrollDelta(
+ gfx::Vector2dF());
+ gfx::Vector2d gesture_scroll_delta(10, 0);
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(),
+ InputHandler::Gesture));
+ host_impl_->ScrollBy(gfx::Point(), gesture_scroll_delta);
+ host_impl_->ScrollEnd();
+
+ // The child layer should have scrolled down in its local coordinates an
+ // amount proportional to the angle between it and the input scroll delta.
+ gfx::Vector2d expected_scroll_delta(
+ 0,
+ -gesture_scroll_delta.x() *
+ std::sin(MathUtil::Deg2Rad(child_layer_angle)));
+ scoped_ptr<ScrollAndScaleSet> scroll_info =
+ host_impl_->ProcessScrollDeltas();
+ ExpectContains(*scroll_info.get(), child_layer_id, expected_scroll_delta);
+
+ // The root scroll layer should have scrolled more, since the input scroll
+ // delta was mostly orthogonal to the child layer's vertical scroll axis.
+ gfx::Vector2d expected_root_scroll_delta(
+ gesture_scroll_delta.x() *
+ std::pow(std::cos(MathUtil::Deg2Rad(child_layer_angle)), 2),
+ 0);
+ ExpectContains(*scroll_info.get(),
+ scroll_layer->id(),
+ expected_root_scroll_delta);
+ }
+}
+
+TEST_F(LayerTreeHostImplTest, ScrollScaledLayer) {
+ LayerImpl* scroll_layer =
+ SetupScrollAndContentsLayers(gfx::Size(100, 100));
+
+ // Scale the layer to twice its normal size.
+ int scale = 2;
+ gfx::Transform scale_transform;
+ scale_transform.Scale(scale, scale);
+ scroll_layer->SetTransform(scale_transform);
+
+ gfx::Size surface_size(50, 50);
+ host_impl_->SetViewportSize(surface_size);
+ InitializeRendererAndDrawFrame();
+
+ // Scroll down in screen coordinates with a gesture.
+ gfx::Vector2d scroll_delta(0, 10);
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture));
+ host_impl_->ScrollBy(gfx::Point(), scroll_delta);
+ host_impl_->ScrollEnd();
+
+ // The layer should have scrolled down in its local coordinates, but half the
+ // amount.
+ scoped_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
+ ExpectContains(*scroll_info.get(),
+ scroll_layer->id(),
+ gfx::Vector2d(0, scroll_delta.y() / scale));
+
+ // Reset and scroll down with the wheel.
+ scroll_layer->SetScrollDelta(gfx::Vector2dF());
+ gfx::Vector2d wheel_scroll_delta(0, 10);
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel));
+ host_impl_->ScrollBy(gfx::Point(), wheel_scroll_delta);
+ host_impl_->ScrollEnd();
+
+ // The scale should not have been applied to the scroll delta.
+ scroll_info = host_impl_->ProcessScrollDeltas();
+ ExpectContains(*scroll_info.get(),
+ scroll_layer->id(),
+ wheel_scroll_delta);
+}
+
+class TestScrollOffsetDelegate : public LayerScrollOffsetDelegate {
+ public:
+ TestScrollOffsetDelegate() {}
+ virtual ~TestScrollOffsetDelegate() {}
+
+ virtual void SetTotalScrollOffset(gfx::Vector2dF new_value) OVERRIDE {
+ last_set_scroll_offset_ = new_value;
+ }
+
+ virtual gfx::Vector2dF GetTotalScrollOffset() OVERRIDE {
+ return getter_return_value_;
+ }
+
+ gfx::Vector2dF last_set_scroll_offset() {
+ return last_set_scroll_offset_;
+ }
+
+ void set_getter_return_value(gfx::Vector2dF value) {
+ getter_return_value_ = value;
+ }
+
+ private:
+ gfx::Vector2dF last_set_scroll_offset_;
+ gfx::Vector2dF getter_return_value_;
+};
+
+TEST_F(LayerTreeHostImplTest, RootLayerScrollOffsetDelegation) {
+ TestScrollOffsetDelegate scroll_delegate;
+ LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
+
+ // Setting the delegate results in the current scroll offset being set.
+ gfx::Vector2dF initial_scroll_delta(10.f, 10.f);
+ scroll_layer->SetScrollOffset(gfx::Vector2d());
+ scroll_layer->SetScrollDelta(initial_scroll_delta);
+ host_impl_->SetRootLayerScrollOffsetDelegate(&scroll_delegate);
+ EXPECT_EQ(initial_scroll_delta.ToString(),
+ scroll_delegate.last_set_scroll_offset().ToString());
+
+ // Scrolling should be relative to the offset as returned by the delegate.
+ gfx::Vector2dF scroll_delta(0.f, 10.f);
+ gfx::Vector2dF current_offset(7.f, 8.f);
+
+ scroll_delegate.set_getter_return_value(current_offset);
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture));
+
+ host_impl_->ScrollBy(gfx::Point(), scroll_delta);
+ EXPECT_EQ(current_offset + scroll_delta,
+ scroll_delegate.last_set_scroll_offset());
+
+ current_offset = gfx::Vector2dF(42.f, 41.f);
+ scroll_delegate.set_getter_return_value(current_offset);
+ host_impl_->ScrollBy(gfx::Point(), scroll_delta);
+ EXPECT_EQ(current_offset + scroll_delta,
+ scroll_delegate.last_set_scroll_offset());
+ host_impl_->ScrollEnd();
+
+ // Un-setting the delegate should propagate the delegate's current offset to
+ // the root scrollable layer.
+ current_offset = gfx::Vector2dF(13.f, 12.f);
+ scroll_delegate.set_getter_return_value(current_offset);
+ host_impl_->SetRootLayerScrollOffsetDelegate(NULL);
+
+ EXPECT_EQ(current_offset.ToString(),
+ scroll_layer->TotalScrollOffset().ToString());
+}
+
+TEST_F(LayerTreeHostImplTest, OverscrollRoot) {
+ SetupScrollAndContentsLayers(gfx::Size(100, 100));
+ host_impl_->SetViewportSize(gfx::Size(50, 50));
+ host_impl_->active_tree()->SetPageScaleFactorAndLimits(1.f, 0.5f, 4.f);
+ InitializeRendererAndDrawFrame();
+ EXPECT_EQ(gfx::Vector2dF(), host_impl_->accumulated_root_overscroll());
+ EXPECT_EQ(gfx::Vector2dF(), host_impl_->current_fling_velocity());
+
+ // In-bounds scrolling does not affect overscroll.
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel));
+ host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 10));
+ EXPECT_EQ(gfx::Vector2dF(), host_impl_->accumulated_root_overscroll());
+ EXPECT_EQ(gfx::Vector2dF(), host_impl_->current_fling_velocity());
+
+ // Overscroll events are reflected immediately.
+ host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 50));
+ EXPECT_EQ(gfx::Vector2dF(0, 10), host_impl_->accumulated_root_overscroll());
+ EXPECT_EQ(gfx::Vector2dF(), host_impl_->current_fling_velocity());
+
+ // In-bounds scrolling resets accumulated overscroll for the scrolled axes.
+ host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, -50));
+ EXPECT_EQ(gfx::Vector2dF(0, 0), host_impl_->accumulated_root_overscroll());
+ host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, -10));
+ EXPECT_EQ(gfx::Vector2dF(0, -10), host_impl_->accumulated_root_overscroll());
+ host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(10, 0));
+ EXPECT_EQ(gfx::Vector2dF(0, -10), host_impl_->accumulated_root_overscroll());
+ host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(-15, 0));
+ EXPECT_EQ(gfx::Vector2dF(-5, -10), host_impl_->accumulated_root_overscroll());
+ host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 60));
+ EXPECT_EQ(gfx::Vector2dF(-5, 10), host_impl_->accumulated_root_overscroll());
+ host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(10, -60));
+ EXPECT_EQ(gfx::Vector2dF(0, -10), host_impl_->accumulated_root_overscroll());
+
+ // Overscroll accumulates within the scope of ScrollBegin/ScrollEnd as long
+ // as no scroll occurs.
+ host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, -20));
+ EXPECT_EQ(gfx::Vector2dF(0, -30), host_impl_->accumulated_root_overscroll());
+ host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, -20));
+ EXPECT_EQ(gfx::Vector2dF(0, -50), host_impl_->accumulated_root_overscroll());
+ // Overscroll resets on valid scroll.
+ host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 10));
+ EXPECT_EQ(gfx::Vector2dF(0, 0), host_impl_->accumulated_root_overscroll());
+ host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, -20));
+ EXPECT_EQ(gfx::Vector2dF(0, -10), host_impl_->accumulated_root_overscroll());
+ host_impl_->ScrollEnd();
+
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel));
+ // Fling velocity is reflected immediately.
+ host_impl_->NotifyCurrentFlingVelocity(gfx::Vector2dF(10, 0));
+ EXPECT_EQ(gfx::Vector2dF(10, 0), host_impl_->current_fling_velocity());
+ host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, -20));
+ EXPECT_EQ(gfx::Vector2dF(0, -20), host_impl_->accumulated_root_overscroll());
+ EXPECT_EQ(gfx::Vector2dF(10, 0), host_impl_->current_fling_velocity());
+}
+
+
+TEST_F(LayerTreeHostImplTest, OverscrollChildWithoutBubbling) {
+ // Scroll child layers beyond their maximum scroll range and make sure root
+ // overscroll does not accumulate.
+ gfx::Size surface_size(10, 10);
+ scoped_ptr<LayerImpl> root = CreateScrollableLayer(1, surface_size);
+
+ scoped_ptr<LayerImpl> grand_child = CreateScrollableLayer(3, surface_size);
+ grand_child->SetScrollOffset(gfx::Vector2d(0, 2));
+
+ scoped_ptr<LayerImpl> child = CreateScrollableLayer(2, surface_size);
+ child->SetScrollOffset(gfx::Vector2d(0, 3));
+ child->AddChild(grand_child.Pass());
+
+ root->AddChild(child.Pass());
+ host_impl_->active_tree()->SetRootLayer(root.Pass());
+ host_impl_->active_tree()->DidBecomeActive();
+ host_impl_->SetViewportSize(surface_size);
+ InitializeRendererAndDrawFrame();
+ {
+ gfx::Vector2d scroll_delta(0, -10);
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(),
+ InputHandler::NonBubblingGesture));
+ host_impl_->ScrollBy(gfx::Point(), scroll_delta);
+ EXPECT_EQ(gfx::Vector2dF(), host_impl_->accumulated_root_overscroll());
+ host_impl_->ScrollEnd();
+
+ LayerImpl* child = host_impl_->active_tree()->root_layer()->children()[0];
+ LayerImpl* grand_child = child->children()[0];
+
+ // The next time we scroll we should only scroll the parent, but overscroll
+ // should still not reach the root layer.
+ scroll_delta = gfx::Vector2d(0, -30);
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(5, 5),
+ InputHandler::NonBubblingGesture));
+ EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), grand_child);
+ EXPECT_EQ(gfx::Vector2dF(), host_impl_->accumulated_root_overscroll());
+ host_impl_->ScrollBy(gfx::Point(), scroll_delta);
+ EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), child);
+ EXPECT_EQ(gfx::Vector2dF(), host_impl_->accumulated_root_overscroll());
+ host_impl_->ScrollEnd();
+
+ // After scrolling the parent, another scroll on the opposite direction
+ // should scroll the child, resetting the fling velocity.
+ scroll_delta = gfx::Vector2d(0, 70);
+ host_impl_->NotifyCurrentFlingVelocity(gfx::Vector2dF(10, 0));
+ EXPECT_EQ(gfx::Vector2dF(10, 0), host_impl_->current_fling_velocity());
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(5, 5),
+ InputHandler::NonBubblingGesture));
+ EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), grand_child);
+ host_impl_->ScrollBy(gfx::Point(), scroll_delta);
+ EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), grand_child);
+ EXPECT_EQ(gfx::Vector2dF(), host_impl_->accumulated_root_overscroll());
+ EXPECT_EQ(gfx::Vector2dF(), host_impl_->current_fling_velocity());
+ host_impl_->ScrollEnd();
+ }
+}
+
+TEST_F(LayerTreeHostImplTest, OverscrollChildEventBubbling) {
+ // When we try to scroll a non-scrollable child layer, the scroll delta
+ // should be applied to one of its ancestors if possible. Overscroll should
+ // be reflected only when it has bubbled up to the root scrolling layer.
+ gfx::Size surface_size(10, 10);
+ gfx::Size content_size(20, 20);
+ scoped_ptr<LayerImpl> root = CreateScrollableLayer(1, content_size);
+ scoped_ptr<LayerImpl> child = CreateScrollableLayer(2, content_size);
+
+ child->SetScrollable(false);
+ root->AddChild(child.Pass());
+
+ host_impl_->SetViewportSize(surface_size);
+ host_impl_->active_tree()->SetRootLayer(root.Pass());
+ host_impl_->active_tree()->DidBecomeActive();
+ InitializeRendererAndDrawFrame();
+ {
+ gfx::Vector2d scroll_delta(0, 8);
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ host_impl_->ScrollBegin(gfx::Point(5, 5),
+ InputHandler::Wheel));
+ host_impl_->ScrollBy(gfx::Point(), scroll_delta);
+ EXPECT_EQ(gfx::Vector2dF(), host_impl_->accumulated_root_overscroll());
+ host_impl_->ScrollBy(gfx::Point(), scroll_delta);
+ EXPECT_EQ(gfx::Vector2dF(0, 6), host_impl_->accumulated_root_overscroll());
+ host_impl_->ScrollBy(gfx::Point(), scroll_delta);
+ EXPECT_EQ(gfx::Vector2dF(0, 14), host_impl_->accumulated_root_overscroll());
+ host_impl_->ScrollEnd();
+ }
+}
+
+
+class BlendStateTrackerContext: public TestWebGraphicsContext3D {
+ public:
+ BlendStateTrackerContext() : blend_(false) {}
+
+ virtual void enable(WebKit::WGC3Denum cap) OVERRIDE {
+ if (cap == GL_BLEND)
+ blend_ = true;
+ }
+
+ virtual void disable(WebKit::WGC3Denum cap) OVERRIDE {
+ if (cap == GL_BLEND)
+ blend_ = false;
+ }
+
+ bool blend() const { return blend_; }
+
+ private:
+ bool blend_;
+};
+
+class BlendStateCheckLayer : public LayerImpl {
+ public:
+ static scoped_ptr<LayerImpl> Create(LayerTreeImpl* tree_impl,
+ int id,
+ ResourceProvider* resource_provider) {
+ return scoped_ptr<LayerImpl>(new BlendStateCheckLayer(tree_impl,
+ id,
+ resource_provider));
+ }
+
+ virtual void AppendQuads(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data) OVERRIDE {
+ quads_appended_ = true;
+
+ gfx::Rect opaque_rect;
+ if (contents_opaque())
+ opaque_rect = quad_rect_;
+ else
+ opaque_rect = opaque_content_rect_;
+
+ SharedQuadState* shared_quad_state =
+ quad_sink->UseSharedQuadState(CreateSharedQuadState());
+ scoped_ptr<TileDrawQuad> test_blending_draw_quad = TileDrawQuad::Create();
+ test_blending_draw_quad->SetNew(shared_quad_state,
+ quad_rect_,
+ opaque_rect,
+ resource_id_,
+ gfx::RectF(0.f, 0.f, 1.f, 1.f),
+ gfx::Size(1, 1),
+ false);
+ test_blending_draw_quad->visible_rect = quad_visible_rect_;
+ EXPECT_EQ(blend_, test_blending_draw_quad->ShouldDrawWithBlending());
+ EXPECT_EQ(has_render_surface_, !!render_surface());
+ quad_sink->Append(test_blending_draw_quad.PassAs<DrawQuad>(),
+ append_quads_data);
+ }
+
+ void SetExpectation(bool blend, bool has_render_surface) {
+ blend_ = blend;
+ has_render_surface_ = has_render_surface;
+ quads_appended_ = false;
+ }
+
+ bool quads_appended() const { return quads_appended_; }
+
+ void SetQuadRect(gfx::Rect rect) { quad_rect_ = rect; }
+ void SetQuadVisibleRect(gfx::Rect rect) { quad_visible_rect_ = rect; }
+ void SetOpaqueContentRect(gfx::Rect rect) { opaque_content_rect_ = rect; }
+
+ private:
+ BlendStateCheckLayer(LayerTreeImpl* tree_impl,
+ int id,
+ ResourceProvider* resource_provider)
+ : LayerImpl(tree_impl, id),
+ blend_(false),
+ has_render_surface_(false),
+ quads_appended_(false),
+ quad_rect_(5, 5, 5, 5),
+ quad_visible_rect_(5, 5, 5, 5),
+ resource_id_(resource_provider->CreateResource(
+ gfx::Size(1, 1),
+ GL_RGBA,
+ ResourceProvider::TextureUsageAny)) {
+ resource_provider->AllocateForTesting(resource_id_);
+ SetAnchorPoint(gfx::PointF());
+ SetBounds(gfx::Size(10, 10));
+ SetContentBounds(gfx::Size(10, 10));
+ SetDrawsContent(true);
+ }
+
+ bool blend_;
+ bool has_render_surface_;
+ bool quads_appended_;
+ gfx::Rect quad_rect_;
+ gfx::Rect opaque_content_rect_;
+ gfx::Rect quad_visible_rect_;
+ ResourceProvider::ResourceId resource_id_;
+};
+
+TEST_F(LayerTreeHostImplTest, BlendingOffWhenDrawingOpaqueLayers) {
+ {
+ scoped_ptr<LayerImpl> root =
+ LayerImpl::Create(host_impl_->active_tree(), 1);
+ root->SetAnchorPoint(gfx::PointF());
+ root->SetBounds(gfx::Size(10, 10));
+ root->SetContentBounds(root->bounds());
+ root->SetDrawsContent(false);
+ host_impl_->active_tree()->SetRootLayer(root.Pass());
+ }
+ LayerImpl* root = host_impl_->active_tree()->root_layer();
+
+ root->AddChild(
+ BlendStateCheckLayer::Create(host_impl_->active_tree(),
+ 2,
+ host_impl_->resource_provider()));
+ BlendStateCheckLayer* layer1 =
+ static_cast<BlendStateCheckLayer*>(root->children()[0]);
+ layer1->SetPosition(gfx::PointF(2.f, 2.f));
+
+ LayerTreeHostImpl::FrameData frame;
+
+ // Opaque layer, drawn without blending.
+ layer1->SetContentsOpaque(true);
+ layer1->SetExpectation(false, false);
+ layer1->set_update_rect(gfx::RectF(layer1->content_bounds()));
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ EXPECT_TRUE(layer1->quads_appended());
+ host_impl_->DidDrawAllLayers(frame);
+
+ // Layer with translucent content and painting, so drawn with blending.
+ layer1->SetContentsOpaque(false);
+ layer1->SetExpectation(true, false);
+ layer1->set_update_rect(gfx::RectF(layer1->content_bounds()));
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ EXPECT_TRUE(layer1->quads_appended());
+ host_impl_->DidDrawAllLayers(frame);
+
+ // Layer with translucent opacity, drawn with blending.
+ layer1->SetContentsOpaque(true);
+ layer1->SetOpacity(0.5f);
+ layer1->SetExpectation(true, false);
+ layer1->set_update_rect(gfx::RectF(layer1->content_bounds()));
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ EXPECT_TRUE(layer1->quads_appended());
+ host_impl_->DidDrawAllLayers(frame);
+
+ // Layer with translucent opacity and painting, drawn with blending.
+ layer1->SetContentsOpaque(true);
+ layer1->SetOpacity(0.5f);
+ layer1->SetExpectation(true, false);
+ layer1->set_update_rect(gfx::RectF(layer1->content_bounds()));
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ EXPECT_TRUE(layer1->quads_appended());
+ host_impl_->DidDrawAllLayers(frame);
+
+ layer1->AddChild(
+ BlendStateCheckLayer::Create(host_impl_->active_tree(),
+ 3,
+ host_impl_->resource_provider()));
+ BlendStateCheckLayer* layer2 =
+ static_cast<BlendStateCheckLayer*>(layer1->children()[0]);
+ layer2->SetPosition(gfx::PointF(4.f, 4.f));
+
+ // 2 opaque layers, drawn without blending.
+ layer1->SetContentsOpaque(true);
+ layer1->SetOpacity(1.f);
+ layer1->SetExpectation(false, false);
+ layer1->set_update_rect(gfx::RectF(layer1->content_bounds()));
+ layer2->SetContentsOpaque(true);
+ layer2->SetOpacity(1.f);
+ layer2->SetExpectation(false, false);
+ layer2->set_update_rect(gfx::RectF(layer1->content_bounds()));
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ EXPECT_TRUE(layer1->quads_appended());
+ EXPECT_TRUE(layer2->quads_appended());
+ host_impl_->DidDrawAllLayers(frame);
+
+ // Parent layer with translucent content, drawn with blending.
+ // Child layer with opaque content, drawn without blending.
+ layer1->SetContentsOpaque(false);
+ layer1->SetExpectation(true, false);
+ layer1->set_update_rect(gfx::RectF(layer1->content_bounds()));
+ layer2->SetExpectation(false, false);
+ layer2->set_update_rect(gfx::RectF(layer1->content_bounds()));
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ EXPECT_TRUE(layer1->quads_appended());
+ EXPECT_TRUE(layer2->quads_appended());
+ host_impl_->DidDrawAllLayers(frame);
+
+ // Parent layer with translucent content but opaque painting, drawn without
+ // blending.
+ // Child layer with opaque content, drawn without blending.
+ layer1->SetContentsOpaque(true);
+ layer1->SetExpectation(false, false);
+ layer1->set_update_rect(gfx::RectF(layer1->content_bounds()));
+ layer2->SetExpectation(false, false);
+ layer2->set_update_rect(gfx::RectF(layer1->content_bounds()));
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ EXPECT_TRUE(layer1->quads_appended());
+ EXPECT_TRUE(layer2->quads_appended());
+ host_impl_->DidDrawAllLayers(frame);
+
+ // Parent layer with translucent opacity and opaque content. Since it has a
+ // drawing child, it's drawn to a render surface which carries the opacity,
+ // so it's itself drawn without blending.
+ // Child layer with opaque content, drawn without blending (parent surface
+ // carries the inherited opacity).
+ layer1->SetContentsOpaque(true);
+ layer1->SetOpacity(0.5f);
+ layer1->SetExpectation(false, true);
+ layer1->set_update_rect(gfx::RectF(layer1->content_bounds()));
+ layer2->SetExpectation(false, false);
+ layer2->set_update_rect(gfx::RectF(layer1->content_bounds()));
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ EXPECT_TRUE(layer1->quads_appended());
+ EXPECT_TRUE(layer2->quads_appended());
+ host_impl_->DidDrawAllLayers(frame);
+
+ // Draw again, but with child non-opaque, to make sure
+ // layer1 not culled.
+ layer1->SetContentsOpaque(true);
+ layer1->SetOpacity(1.f);
+ layer1->SetExpectation(false, false);
+ layer1->set_update_rect(gfx::RectF(layer1->content_bounds()));
+ layer2->SetContentsOpaque(true);
+ layer2->SetOpacity(0.5f);
+ layer2->SetExpectation(true, false);
+ layer2->set_update_rect(gfx::RectF(layer1->content_bounds()));
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ EXPECT_TRUE(layer1->quads_appended());
+ EXPECT_TRUE(layer2->quads_appended());
+ host_impl_->DidDrawAllLayers(frame);
+
+ // A second way of making the child non-opaque.
+ layer1->SetContentsOpaque(true);
+ layer1->SetOpacity(1.f);
+ layer1->SetExpectation(false, false);
+ layer1->set_update_rect(gfx::RectF(layer1->content_bounds()));
+ layer2->SetContentsOpaque(false);
+ layer2->SetOpacity(1.f);
+ layer2->SetExpectation(true, false);
+ layer2->set_update_rect(gfx::RectF(layer1->content_bounds()));
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ EXPECT_TRUE(layer1->quads_appended());
+ EXPECT_TRUE(layer2->quads_appended());
+ host_impl_->DidDrawAllLayers(frame);
+
+ // And when the layer says its not opaque but is painted opaque, it is not
+ // blended.
+ layer1->SetContentsOpaque(true);
+ layer1->SetOpacity(1.f);
+ layer1->SetExpectation(false, false);
+ layer1->set_update_rect(gfx::RectF(layer1->content_bounds()));
+ layer2->SetContentsOpaque(true);
+ layer2->SetOpacity(1.f);
+ layer2->SetExpectation(false, false);
+ layer2->set_update_rect(gfx::RectF(layer1->content_bounds()));
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ EXPECT_TRUE(layer1->quads_appended());
+ EXPECT_TRUE(layer2->quads_appended());
+ host_impl_->DidDrawAllLayers(frame);
+
+ // Layer with partially opaque contents, drawn with blending.
+ layer1->SetContentsOpaque(false);
+ layer1->SetQuadRect(gfx::Rect(5, 5, 5, 5));
+ layer1->SetQuadVisibleRect(gfx::Rect(5, 5, 5, 5));
+ layer1->SetOpaqueContentRect(gfx::Rect(5, 5, 2, 5));
+ layer1->SetExpectation(true, false);
+ layer1->set_update_rect(gfx::RectF(layer1->content_bounds()));
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ EXPECT_TRUE(layer1->quads_appended());
+ host_impl_->DidDrawAllLayers(frame);
+
+ // Layer with partially opaque contents partially culled, drawn with blending.
+ layer1->SetContentsOpaque(false);
+ layer1->SetQuadRect(gfx::Rect(5, 5, 5, 5));
+ layer1->SetQuadVisibleRect(gfx::Rect(5, 5, 5, 2));
+ layer1->SetOpaqueContentRect(gfx::Rect(5, 5, 2, 5));
+ layer1->SetExpectation(true, false);
+ layer1->set_update_rect(gfx::RectF(layer1->content_bounds()));
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ EXPECT_TRUE(layer1->quads_appended());
+ host_impl_->DidDrawAllLayers(frame);
+
+ // Layer with partially opaque contents culled, drawn with blending.
+ layer1->SetContentsOpaque(false);
+ layer1->SetQuadRect(gfx::Rect(5, 5, 5, 5));
+ layer1->SetQuadVisibleRect(gfx::Rect(7, 5, 3, 5));
+ layer1->SetOpaqueContentRect(gfx::Rect(5, 5, 2, 5));
+ layer1->SetExpectation(true, false);
+ layer1->set_update_rect(gfx::RectF(layer1->content_bounds()));
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ EXPECT_TRUE(layer1->quads_appended());
+ host_impl_->DidDrawAllLayers(frame);
+
+ // Layer with partially opaque contents and translucent contents culled, drawn
+ // without blending.
+ layer1->SetContentsOpaque(false);
+ layer1->SetQuadRect(gfx::Rect(5, 5, 5, 5));
+ layer1->SetQuadVisibleRect(gfx::Rect(5, 5, 2, 5));
+ layer1->SetOpaqueContentRect(gfx::Rect(5, 5, 2, 5));
+ layer1->SetExpectation(false, false);
+ layer1->set_update_rect(gfx::RectF(layer1->content_bounds()));
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ EXPECT_TRUE(layer1->quads_appended());
+ host_impl_->DidDrawAllLayers(frame);
+}
+
+class LayerTreeHostImplViewportCoveredTest : public LayerTreeHostImplTest {
+ public:
+ void CreateLayerTreeHostImpl(bool always_draw) {
+ LayerTreeSettings settings;
+ settings.minimum_occlusion_tracking_size = gfx::Size();
+ settings.impl_side_painting = true;
+ host_impl_ = LayerTreeHostImpl::Create(
+ settings, this, &proxy_, &stats_instrumentation_);
+ scoped_ptr<OutputSurface> output_surface;
+ if (always_draw) {
+ output_surface = FakeOutputSurface::CreateAlwaysDrawAndSwap3d()
+ .PassAs<OutputSurface>();
+ } else {
+ output_surface = CreateFakeOutputSurface();
+ }
+ host_impl_->InitializeRenderer(output_surface.Pass());
+ viewport_size_ = gfx::Size(1000, 1000);
+ }
+
+ void SetupActiveTreeLayers() {
+ host_impl_->active_tree()->set_background_color(SK_ColorGRAY);
+ host_impl_->active_tree()->SetRootLayer(
+ LayerImpl::Create(host_impl_->active_tree(), 1));
+ host_impl_->active_tree()->root_layer()->AddChild(
+ BlendStateCheckLayer::Create(host_impl_->active_tree(),
+ 2,
+ host_impl_->resource_provider()));
+ child_ = static_cast<BlendStateCheckLayer*>(
+ host_impl_->active_tree()->root_layer()->children()[0]);
+ child_->SetExpectation(false, false);
+ child_->SetContentsOpaque(true);
+ }
+
+ // Expect no gutter rects.
+ void TestLayerCoversFullViewport() {
+ gfx::Rect layer_rect(viewport_size_);
+ child_->SetPosition(layer_rect.origin());
+ child_->SetBounds(layer_rect.size());
+ child_->SetContentBounds(layer_rect.size());
+ child_->SetQuadRect(gfx::Rect(layer_rect.size()));
+ child_->SetQuadVisibleRect(gfx::Rect(layer_rect.size()));
+
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ ASSERT_EQ(1u, frame.render_passes.size());
+
+ size_t num_gutter_quads = 0;
+ for (size_t i = 0; i < frame.render_passes[0]->quad_list.size(); ++i)
+ num_gutter_quads += (frame.render_passes[0]->quad_list[i]->material ==
+ DrawQuad::SOLID_COLOR) ? 1 : 0;
+ EXPECT_EQ(0u, num_gutter_quads);
+ EXPECT_EQ(1u, frame.render_passes[0]->quad_list.size());
+
+ LayerTestCommon::VerifyQuadsExactlyCoverRect(
+ frame.render_passes[0]->quad_list, gfx::Rect(viewport_size_));
+ host_impl_->DidDrawAllLayers(frame);
+ }
+
+ // Expect fullscreen gutter rect.
+ void TestEmptyLayer() {
+ gfx::Rect layer_rect(0, 0, 0, 0);
+ child_->SetPosition(layer_rect.origin());
+ child_->SetBounds(layer_rect.size());
+ child_->SetContentBounds(layer_rect.size());
+ child_->SetQuadRect(gfx::Rect(layer_rect.size()));
+ child_->SetQuadVisibleRect(gfx::Rect(layer_rect.size()));
+
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ ASSERT_EQ(1u, frame.render_passes.size());
+
+ size_t num_gutter_quads = 0;
+ for (size_t i = 0; i < frame.render_passes[0]->quad_list.size(); ++i)
+ num_gutter_quads += (frame.render_passes[0]->quad_list[i]->material ==
+ DrawQuad::SOLID_COLOR) ? 1 : 0;
+ EXPECT_EQ(1u, num_gutter_quads);
+ EXPECT_EQ(1u, frame.render_passes[0]->quad_list.size());
+
+ LayerTestCommon::VerifyQuadsExactlyCoverRect(
+ frame.render_passes[0]->quad_list, gfx::Rect(viewport_size_));
+ host_impl_->DidDrawAllLayers(frame);
+ }
+
+ // Expect four surrounding gutter rects.
+ void TestLayerInMiddleOfViewport() {
+ gfx::Rect layer_rect(500, 500, 200, 200);
+ child_->SetPosition(layer_rect.origin());
+ child_->SetBounds(layer_rect.size());
+ child_->SetContentBounds(layer_rect.size());
+ child_->SetQuadRect(gfx::Rect(layer_rect.size()));
+ child_->SetQuadVisibleRect(gfx::Rect(layer_rect.size()));
+
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ ASSERT_EQ(1u, frame.render_passes.size());
+
+ size_t num_gutter_quads = 0;
+ for (size_t i = 0; i < frame.render_passes[0]->quad_list.size(); ++i)
+ num_gutter_quads += (frame.render_passes[0]->quad_list[i]->material ==
+ DrawQuad::SOLID_COLOR) ? 1 : 0;
+ EXPECT_EQ(4u, num_gutter_quads);
+ EXPECT_EQ(5u, frame.render_passes[0]->quad_list.size());
+
+ LayerTestCommon::VerifyQuadsExactlyCoverRect(
+ frame.render_passes[0]->quad_list, gfx::Rect(viewport_size_));
+ host_impl_->DidDrawAllLayers(frame);
+ }
+
+ // Expect no gutter rects.
+ void TestLayerIsLargerThanViewport() {
+ gfx::Rect layer_rect(viewport_size_.width() + 10,
+ viewport_size_.height() + 10);
+ child_->SetPosition(layer_rect.origin());
+ child_->SetBounds(layer_rect.size());
+ child_->SetContentBounds(layer_rect.size());
+ child_->SetQuadRect(gfx::Rect(layer_rect.size()));
+ child_->SetQuadVisibleRect(gfx::Rect(layer_rect.size()));
+
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ ASSERT_EQ(1u, frame.render_passes.size());
+
+ size_t num_gutter_quads = 0;
+ for (size_t i = 0; i < frame.render_passes[0]->quad_list.size(); ++i)
+ num_gutter_quads += (frame.render_passes[0]->quad_list[i]->material ==
+ DrawQuad::SOLID_COLOR) ? 1 : 0;
+ EXPECT_EQ(0u, num_gutter_quads);
+ EXPECT_EQ(1u, frame.render_passes[0]->quad_list.size());
+
+ host_impl_->DidDrawAllLayers(frame);
+ }
+
+ virtual void DidActivatePendingTree() OVERRIDE {
+ did_activate_pending_tree_ = true;
+ }
+
+ protected:
+ gfx::Size viewport_size_;
+ BlendStateCheckLayer* child_;
+ bool did_activate_pending_tree_;
+};
+
+TEST_F(LayerTreeHostImplViewportCoveredTest, ViewportCovered) {
+ bool always_draw = false;
+ CreateLayerTreeHostImpl(always_draw);
+
+ host_impl_->SetViewportSize(viewport_size_);
+ SetupActiveTreeLayers();
+ TestLayerCoversFullViewport();
+ TestEmptyLayer();
+ TestLayerInMiddleOfViewport();
+ TestLayerIsLargerThanViewport();
+}
+
+TEST_F(LayerTreeHostImplViewportCoveredTest, ActiveTreeGrowViewportInvalid) {
+ bool always_draw = true;
+ CreateLayerTreeHostImpl(always_draw);
+
+ // Pending tree to force active_tree size invalid. Not used otherwise.
+ host_impl_->CreatePendingTree();
+ host_impl_->SetViewportSize(viewport_size_);
+ EXPECT_TRUE(host_impl_->active_tree()->ViewportSizeInvalid());
+
+ SetupActiveTreeLayers();
+ TestEmptyLayer();
+ TestLayerInMiddleOfViewport();
+ TestLayerIsLargerThanViewport();
+}
+
+TEST_F(LayerTreeHostImplViewportCoveredTest, ActiveTreeShrinkViewportInvalid) {
+ bool always_draw = true;
+ CreateLayerTreeHostImpl(always_draw);
+
+ // Set larger viewport and activate it to active tree.
+ host_impl_->CreatePendingTree();
+ gfx::Size larger_viewport(viewport_size_.width() + 100,
+ viewport_size_.height() + 100);
+ host_impl_->SetViewportSize(larger_viewport);
+ EXPECT_TRUE(host_impl_->active_tree()->ViewportSizeInvalid());
+ did_activate_pending_tree_ = false;
+ host_impl_->ActivatePendingTreeIfNeeded();
+ EXPECT_TRUE(did_activate_pending_tree_);
+ EXPECT_FALSE(host_impl_->active_tree()->ViewportSizeInvalid());
+
+ // Shrink pending tree viewport without activating.
+ host_impl_->CreatePendingTree();
+ host_impl_->SetViewportSize(viewport_size_);
+ EXPECT_TRUE(host_impl_->active_tree()->ViewportSizeInvalid());
+
+ SetupActiveTreeLayers();
+ TestEmptyLayer();
+ TestLayerInMiddleOfViewport();
+ TestLayerIsLargerThanViewport();
+}
+
+class ReshapeTrackerContext: public TestWebGraphicsContext3D {
+ public:
+ ReshapeTrackerContext()
+ : reshape_called_(false),
+ last_reshape_width_(-1),
+ last_reshape_height_(-1),
+ last_reshape_scale_factor_(-1.f) {
+ }
+
+ virtual void reshapeWithScaleFactor(
+ int width, int height, float scale_factor) OVERRIDE {
+ reshape_called_ = true;
+ last_reshape_width_ = width;
+ last_reshape_height_ = height;
+ last_reshape_scale_factor_ = scale_factor;
+ }
+
+ bool reshape_called() const { return reshape_called_; }
+ void clear_reshape_called() { reshape_called_ = false; }
+ int last_reshape_width() { return last_reshape_width_; }
+ int last_reshape_height() { return last_reshape_height_; }
+ int last_reshape_scale_factor() { return last_reshape_scale_factor_; }
+
+ private:
+ bool reshape_called_;
+ int last_reshape_width_;
+ int last_reshape_height_;
+ float last_reshape_scale_factor_;
+};
+
+class FakeDrawableLayerImpl: public LayerImpl {
+ public:
+ static scoped_ptr<LayerImpl> Create(LayerTreeImpl* tree_impl, int id) {
+ return scoped_ptr<LayerImpl>(new FakeDrawableLayerImpl(tree_impl, id));
+ }
+ protected:
+ FakeDrawableLayerImpl(LayerTreeImpl* tree_impl, int id)
+ : LayerImpl(tree_impl, id) {}
+};
+
+// Only reshape when we know we are going to draw. Otherwise, the reshape
+// can leave the window at the wrong size if we never draw and the proper
+// viewport size is never set.
+TEST_F(LayerTreeHostImplTest, ReshapeNotCalledUntilDraw) {
+ scoped_ptr<OutputSurface> output_surface = FakeOutputSurface::Create3d(
+ scoped_ptr<WebKit::WebGraphicsContext3D>(new ReshapeTrackerContext))
+ .PassAs<OutputSurface>();
+ ReshapeTrackerContext* reshape_tracker =
+ static_cast<ReshapeTrackerContext*>(output_surface->context3d());
+ host_impl_->InitializeRenderer(output_surface.Pass());
+
+ scoped_ptr<LayerImpl> root =
+ FakeDrawableLayerImpl::Create(host_impl_->active_tree(), 1);
+ root->SetAnchorPoint(gfx::PointF());
+ root->SetBounds(gfx::Size(10, 10));
+ root->SetContentBounds(gfx::Size(10, 10));
+ root->SetDrawsContent(true);
+ host_impl_->active_tree()->SetRootLayer(root.Pass());
+ EXPECT_FALSE(reshape_tracker->reshape_called());
+ reshape_tracker->clear_reshape_called();
+
+ LayerTreeHostImpl::FrameData frame;
+ host_impl_->SetViewportSize(gfx::Size(10, 10));
+ host_impl_->SetDeviceScaleFactor(1.f);
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ EXPECT_TRUE(reshape_tracker->reshape_called());
+ EXPECT_EQ(reshape_tracker->last_reshape_width(), 10);
+ EXPECT_EQ(reshape_tracker->last_reshape_height(), 10);
+ EXPECT_EQ(reshape_tracker->last_reshape_scale_factor(), 1.f);
+ host_impl_->DidDrawAllLayers(frame);
+ reshape_tracker->clear_reshape_called();
+
+ host_impl_->SetViewportSize(gfx::Size(20, 30));
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ EXPECT_TRUE(reshape_tracker->reshape_called());
+ EXPECT_EQ(reshape_tracker->last_reshape_width(), 20);
+ EXPECT_EQ(reshape_tracker->last_reshape_height(), 30);
+ EXPECT_EQ(reshape_tracker->last_reshape_scale_factor(), 1.f);
+ host_impl_->DidDrawAllLayers(frame);
+ reshape_tracker->clear_reshape_called();
+
+ host_impl_->SetDeviceScaleFactor(2.f);
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ EXPECT_TRUE(reshape_tracker->reshape_called());
+ EXPECT_EQ(reshape_tracker->last_reshape_width(), 20);
+ EXPECT_EQ(reshape_tracker->last_reshape_height(), 30);
+ EXPECT_EQ(reshape_tracker->last_reshape_scale_factor(), 2.f);
+ host_impl_->DidDrawAllLayers(frame);
+ reshape_tracker->clear_reshape_called();
+}
+
+class SwapTrackerContext : public TestWebGraphicsContext3D {
+ public:
+ SwapTrackerContext() : last_update_type_(NoUpdate) {}
+
+ virtual void prepareTexture() OVERRIDE {
+ update_rect_ = gfx::Rect(width_, height_);
+ last_update_type_ = PrepareTexture;
+ }
+
+ virtual void postSubBufferCHROMIUM(int x, int y, int width, int height)
+ OVERRIDE {
+ update_rect_ = gfx::Rect(x, y, width, height);
+ last_update_type_ = PostSubBuffer;
+ }
+
+ virtual WebKit::WebString getString(WebKit::WGC3Denum name) OVERRIDE {
+ if (name == GL_EXTENSIONS) {
+ return WebKit::WebString(
+ "GL_CHROMIUM_post_sub_buffer GL_CHROMIUM_set_visibility");
+ }
+
+ return WebKit::WebString();
+ }
+
+ gfx::Rect update_rect() const { return update_rect_; }
+
+ enum UpdateType {
+ NoUpdate = 0,
+ PrepareTexture,
+ PostSubBuffer
+ };
+
+ UpdateType last_update_type() {
+ return last_update_type_;
+ }
+
+ private:
+ gfx::Rect update_rect_;
+ UpdateType last_update_type_;
+};
+
+// Make sure damage tracking propagates all the way to the graphics context,
+// where it should request to swap only the sub-buffer that is damaged.
+TEST_F(LayerTreeHostImplTest, PartialSwapReceivesDamageRect) {
+ scoped_ptr<OutputSurface> output_surface =
+ FakeOutputSurface::Create3d(scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new SwapTrackerContext)).PassAs<OutputSurface>();
+ SwapTrackerContext* swap_tracker =
+ static_cast<SwapTrackerContext*>(output_surface->context3d());
+
+ // This test creates its own LayerTreeHostImpl, so
+ // that we can force partial swap enabled.
+ LayerTreeSettings settings;
+ settings.partial_swap_enabled = true;
+ scoped_ptr<LayerTreeHostImpl> layer_tree_host_impl =
+ LayerTreeHostImpl::Create(settings,
+ this,
+ &proxy_,
+ &stats_instrumentation_);
+ layer_tree_host_impl->InitializeRenderer(output_surface.Pass());
+ layer_tree_host_impl->SetViewportSize(gfx::Size(500, 500));
+
+ scoped_ptr<LayerImpl> root =
+ FakeDrawableLayerImpl::Create(layer_tree_host_impl->active_tree(), 1);
+ scoped_ptr<LayerImpl> child =
+ FakeDrawableLayerImpl::Create(layer_tree_host_impl->active_tree(), 2);
+ child->SetPosition(gfx::PointF(12.f, 13.f));
+ child->SetAnchorPoint(gfx::PointF());
+ child->SetBounds(gfx::Size(14, 15));
+ child->SetContentBounds(gfx::Size(14, 15));
+ child->SetDrawsContent(true);
+ root->SetAnchorPoint(gfx::PointF());
+ root->SetBounds(gfx::Size(500, 500));
+ root->SetContentBounds(gfx::Size(500, 500));
+ root->SetDrawsContent(true);
+ root->AddChild(child.Pass());
+ layer_tree_host_impl->active_tree()->SetRootLayer(root.Pass());
+
+ LayerTreeHostImpl::FrameData frame;
+
+ // First frame, the entire screen should get swapped.
+ EXPECT_TRUE(layer_tree_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+ layer_tree_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ layer_tree_host_impl->DidDrawAllLayers(frame);
+ layer_tree_host_impl->SwapBuffers(frame);
+ gfx::Rect actual_swap_rect = swap_tracker->update_rect();
+ gfx::Rect expected_swap_rect = gfx::Rect(0, 0, 500, 500);
+ EXPECT_EQ(expected_swap_rect.x(), actual_swap_rect.x());
+ EXPECT_EQ(expected_swap_rect.y(), actual_swap_rect.y());
+ EXPECT_EQ(expected_swap_rect.width(), actual_swap_rect.width());
+ EXPECT_EQ(expected_swap_rect.height(), actual_swap_rect.height());
+ EXPECT_EQ(swap_tracker->last_update_type(),
+ SwapTrackerContext::PrepareTexture);
+ // Second frame, only the damaged area should get swapped. Damage should be
+ // the union of old and new child rects.
+ // expected damage rect: gfx::Rect(26, 28);
+ // expected swap rect: vertically flipped, with origin at bottom left corner.
+ layer_tree_host_impl->active_tree()->root_layer()->children()[0]->SetPosition(
+ gfx::PointF());
+ EXPECT_TRUE(layer_tree_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+ layer_tree_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+ layer_tree_host_impl->SwapBuffers(frame);
+ actual_swap_rect = swap_tracker->update_rect();
+ expected_swap_rect = gfx::Rect(0, 500-28, 26, 28);
+ EXPECT_EQ(expected_swap_rect.x(), actual_swap_rect.x());
+ EXPECT_EQ(expected_swap_rect.y(), actual_swap_rect.y());
+ EXPECT_EQ(expected_swap_rect.width(), actual_swap_rect.width());
+ EXPECT_EQ(expected_swap_rect.height(), actual_swap_rect.height());
+ EXPECT_EQ(swap_tracker->last_update_type(),
+ SwapTrackerContext::PostSubBuffer);
+
+ // Make sure that partial swap is constrained to the viewport dimensions
+ // expected damage rect: gfx::Rect(500, 500);
+ // expected swap rect: flipped damage rect, but also clamped to viewport
+ layer_tree_host_impl->SetViewportSize(gfx::Size(10, 10));
+ // This will damage everything.
+ layer_tree_host_impl->active_tree()->root_layer()->SetBackgroundColor(
+ SK_ColorBLACK);
+ EXPECT_TRUE(layer_tree_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+ layer_tree_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+ layer_tree_host_impl->SwapBuffers(frame);
+ actual_swap_rect = swap_tracker->update_rect();
+ expected_swap_rect = gfx::Rect(10, 10);
+ EXPECT_EQ(expected_swap_rect.x(), actual_swap_rect.x());
+ EXPECT_EQ(expected_swap_rect.y(), actual_swap_rect.y());
+ EXPECT_EQ(expected_swap_rect.width(), actual_swap_rect.width());
+ EXPECT_EQ(expected_swap_rect.height(), actual_swap_rect.height());
+ EXPECT_EQ(swap_tracker->last_update_type(),
+ SwapTrackerContext::PrepareTexture);
+}
+
+TEST_F(LayerTreeHostImplTest, RootLayerDoesntCreateExtraSurface) {
+ scoped_ptr<LayerImpl> root =
+ FakeDrawableLayerImpl::Create(host_impl_->active_tree(), 1);
+ scoped_ptr<LayerImpl> child =
+ FakeDrawableLayerImpl::Create(host_impl_->active_tree(), 2);
+ child->SetAnchorPoint(gfx::PointF());
+ child->SetBounds(gfx::Size(10, 10));
+ child->SetContentBounds(gfx::Size(10, 10));
+ child->SetDrawsContent(true);
+ root->SetAnchorPoint(gfx::PointF());
+ root->SetBounds(gfx::Size(10, 10));
+ root->SetContentBounds(gfx::Size(10, 10));
+ root->SetDrawsContent(true);
+ root->SetForceRenderSurface(true);
+ root->AddChild(child.Pass());
+
+ host_impl_->active_tree()->SetRootLayer(root.Pass());
+
+ LayerTreeHostImpl::FrameData frame;
+
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ EXPECT_EQ(1u, frame.render_surface_layer_list->size());
+ EXPECT_EQ(1u, frame.render_passes.size());
+ host_impl_->DidDrawAllLayers(frame);
+}
+
+class FakeLayerWithQuads : public LayerImpl {
+ public:
+ static scoped_ptr<LayerImpl> Create(LayerTreeImpl* tree_impl, int id) {
+ return scoped_ptr<LayerImpl>(new FakeLayerWithQuads(tree_impl, id));
+ }
+
+ virtual void AppendQuads(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data) OVERRIDE {
+ SharedQuadState* shared_quad_state =
+ quad_sink->UseSharedQuadState(CreateSharedQuadState());
+
+ SkColor gray = SkColorSetRGB(100, 100, 100);
+ gfx::Rect quad_rect(content_bounds());
+ scoped_ptr<SolidColorDrawQuad> my_quad = SolidColorDrawQuad::Create();
+ my_quad->SetNew(shared_quad_state, quad_rect, gray, false);
+ quad_sink->Append(my_quad.PassAs<DrawQuad>(), append_quads_data);
+ }
+
+ private:
+ FakeLayerWithQuads(LayerTreeImpl* tree_impl, int id)
+ : LayerImpl(tree_impl, id) {}
+};
+
+class MockContext : public TestWebGraphicsContext3D {
+ public:
+ MOCK_METHOD1(useProgram, void(WebKit::WebGLId program));
+ MOCK_METHOD5(uniform4f, void(WebKit::WGC3Dint location,
+ WebKit::WGC3Dfloat x,
+ WebKit::WGC3Dfloat y,
+ WebKit::WGC3Dfloat z,
+ WebKit::WGC3Dfloat w));
+ MOCK_METHOD4(uniformMatrix4fv, void(WebKit::WGC3Dint location,
+ WebKit::WGC3Dsizei count,
+ WebKit::WGC3Dboolean transpose,
+ const WebKit::WGC3Dfloat* value));
+ MOCK_METHOD4(drawElements, void(WebKit::WGC3Denum mode,
+ WebKit::WGC3Dsizei count,
+ WebKit::WGC3Denum type,
+ WebKit::WGC3Dintptr offset));
+ MOCK_METHOD1(getString, WebKit::WebString(WebKit::WGC3Denum name));
+ MOCK_METHOD0(getRequestableExtensionsCHROMIUM, WebKit::WebString());
+ MOCK_METHOD1(enable, void(WebKit::WGC3Denum cap));
+ MOCK_METHOD1(disable, void(WebKit::WGC3Denum cap));
+ MOCK_METHOD4(scissor, void(WebKit::WGC3Dint x,
+ WebKit::WGC3Dint y,
+ WebKit::WGC3Dsizei width,
+ WebKit::WGC3Dsizei height));
+};
+
+class MockContextHarness {
+ private:
+ MockContext* context_;
+
+ public:
+ explicit MockContextHarness(MockContext* context)
+ : context_(context) {
+ // Catch "uninteresting" calls
+ EXPECT_CALL(*context_, useProgram(_))
+ .Times(0);
+
+ EXPECT_CALL(*context_, drawElements(_, _, _, _))
+ .Times(0);
+
+ // These are not asserted
+ EXPECT_CALL(*context_, uniformMatrix4fv(_, _, _, _))
+ .WillRepeatedly(Return());
+
+ EXPECT_CALL(*context_, uniform4f(_, _, _, _, _))
+ .WillRepeatedly(Return());
+
+ // Any other strings are empty
+ EXPECT_CALL(*context_, getString(_))
+ .WillRepeatedly(Return(WebKit::WebString()));
+
+ // Support for partial swap, if needed
+ EXPECT_CALL(*context_, getString(GL_EXTENSIONS))
+ .WillRepeatedly(Return(
+ WebKit::WebString("GL_CHROMIUM_post_sub_buffer")));
+
+ EXPECT_CALL(*context_, getRequestableExtensionsCHROMIUM())
+ .WillRepeatedly(Return(
+ WebKit::WebString("GL_CHROMIUM_post_sub_buffer")));
+
+ // Any un-sanctioned calls to enable() are OK
+ EXPECT_CALL(*context_, enable(_))
+ .WillRepeatedly(Return());
+
+ // Any un-sanctioned calls to disable() are OK
+ EXPECT_CALL(*context_, disable(_))
+ .WillRepeatedly(Return());
+ }
+
+ void MustDrawSolidQuad() {
+ EXPECT_CALL(*context_, drawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0))
+ .WillOnce(Return())
+ .RetiresOnSaturation();
+
+ EXPECT_CALL(*context_, useProgram(_))
+ .WillOnce(Return())
+ .RetiresOnSaturation();
+ }
+
+ void MustSetScissor(int x, int y, int width, int height) {
+ EXPECT_CALL(*context_, enable(GL_SCISSOR_TEST))
+ .WillRepeatedly(Return());
+
+ EXPECT_CALL(*context_, scissor(x, y, width, height))
+ .Times(AtLeast(1))
+ .WillRepeatedly(Return());
+ }
+
+ void MustSetNoScissor() {
+ EXPECT_CALL(*context_, disable(GL_SCISSOR_TEST))
+ .WillRepeatedly(Return());
+
+ EXPECT_CALL(*context_, enable(GL_SCISSOR_TEST))
+ .Times(0);
+
+ EXPECT_CALL(*context_, scissor(_, _, _, _))
+ .Times(0);
+ }
+};
+
+TEST_F(LayerTreeHostImplTest, NoPartialSwap) {
+ scoped_ptr<OutputSurface> output_surface =
+ FakeOutputSurface::Create3d(scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new MockContext)).PassAs<OutputSurface>();
+ MockContext* mock_context =
+ static_cast<MockContext*>(output_surface->context3d());
+ MockContextHarness harness(mock_context);
+
+ // Run test case
+ CreateLayerTreeHost(false, output_surface.Pass());
+ SetupRootLayerImpl(FakeLayerWithQuads::Create(host_impl_->active_tree(), 1));
+
+ // Without partial swap, and no clipping, no scissor is set.
+ harness.MustDrawSolidQuad();
+ harness.MustSetNoScissor();
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+ }
+ Mock::VerifyAndClearExpectations(&mock_context);
+
+ // Without partial swap, but a layer does clip its subtree, one scissor is
+ // set.
+ host_impl_->active_tree()->root_layer()->SetMasksToBounds(true);
+ harness.MustDrawSolidQuad();
+ harness.MustSetScissor(0, 0, 10, 10);
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+ }
+ Mock::VerifyAndClearExpectations(&mock_context);
+}
+
+TEST_F(LayerTreeHostImplTest, PartialSwap) {
+ scoped_ptr<OutputSurface> output_surface =
+ FakeOutputSurface::Create3d(scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new MockContext)).PassAs<OutputSurface>();
+ MockContext* mock_context =
+ static_cast<MockContext*>(output_surface->context3d());
+ MockContextHarness harness(mock_context);
+
+ CreateLayerTreeHost(true, output_surface.Pass());
+ SetupRootLayerImpl(FakeLayerWithQuads::Create(host_impl_->active_tree(), 1));
+
+ // The first frame is not a partially-swapped one.
+ harness.MustSetScissor(0, 0, 10, 10);
+ harness.MustDrawSolidQuad();
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+ }
+ Mock::VerifyAndClearExpectations(&mock_context);
+
+ // Damage a portion of the frame.
+ host_impl_->active_tree()->root_layer()->set_update_rect(
+ gfx::Rect(0, 0, 2, 3));
+
+ // The second frame will be partially-swapped (the y coordinates are flipped).
+ harness.MustSetScissor(0, 7, 2, 3);
+ harness.MustDrawSolidQuad();
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+ }
+ Mock::VerifyAndClearExpectations(&mock_context);
+}
+
+class PartialSwapContext : public TestWebGraphicsContext3D {
+ public:
+ virtual WebKit::WebString getString(WebKit::WGC3Denum name) OVERRIDE {
+ if (name == GL_EXTENSIONS)
+ return WebKit::WebString("GL_CHROMIUM_post_sub_buffer");
+ return WebKit::WebString();
+ }
+
+ virtual WebKit::WebString getRequestableExtensionsCHROMIUM() OVERRIDE {
+ return WebKit::WebString("GL_CHROMIUM_post_sub_buffer");
+ }
+
+ // Unlimited texture size.
+ virtual void getIntegerv(WebKit::WGC3Denum pname, WebKit::WGC3Dint* value)
+ OVERRIDE {
+ if (pname == GL_MAX_TEXTURE_SIZE)
+ *value = 8192;
+ else if (pname == GL_ACTIVE_TEXTURE)
+ *value = GL_TEXTURE0;
+ }
+};
+
+static scoped_ptr<LayerTreeHostImpl> SetupLayersForOpacity(
+ bool partial_swap,
+ LayerTreeHostImplClient* client,
+ Proxy* proxy,
+ RenderingStatsInstrumentation* stats_instrumentation) {
+ scoped_ptr<OutputSurface> output_surface =
+ FakeOutputSurface::Create3d(scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new PartialSwapContext)).PassAs<OutputSurface>();
+
+ LayerTreeSettings settings;
+ settings.partial_swap_enabled = partial_swap;
+ scoped_ptr<LayerTreeHostImpl> my_host_impl =
+ LayerTreeHostImpl::Create(settings, client, proxy, stats_instrumentation);
+ my_host_impl->InitializeRenderer(output_surface.Pass());
+ my_host_impl->SetViewportSize(gfx::Size(100, 100));
+
+ /*
+ Layers are created as follows:
+
+ +--------------------+
+ | 1 |
+ | +-----------+ |
+ | | 2 | |
+ | | +-------------------+
+ | | | 3 |
+ | | +-------------------+
+ | | | |
+ | +-----------+ |
+ | |
+ | |
+ +--------------------+
+
+ Layers 1, 2 have render surfaces
+ */
+ scoped_ptr<LayerImpl> root =
+ LayerImpl::Create(my_host_impl->active_tree(), 1);
+ scoped_ptr<LayerImpl> child =
+ LayerImpl::Create(my_host_impl->active_tree(), 2);
+ scoped_ptr<LayerImpl> grand_child =
+ FakeLayerWithQuads::Create(my_host_impl->active_tree(), 3);
+
+ gfx::Rect root_rect(0, 0, 100, 100);
+ gfx::Rect child_rect(10, 10, 50, 50);
+ gfx::Rect grand_child_rect(5, 5, 150, 150);
+
+ root->CreateRenderSurface();
+ root->SetAnchorPoint(gfx::PointF());
+ root->SetPosition(root_rect.origin());
+ root->SetBounds(root_rect.size());
+ root->SetContentBounds(root->bounds());
+ root->draw_properties().visible_content_rect = root_rect;
+ root->SetDrawsContent(false);
+ root->render_surface()->SetContentRect(gfx::Rect(root_rect.size()));
+
+ child->SetAnchorPoint(gfx::PointF());
+ child->SetPosition(gfx::PointF(child_rect.x(), child_rect.y()));
+ child->SetOpacity(0.5f);
+ child->SetBounds(gfx::Size(child_rect.width(), child_rect.height()));
+ child->SetContentBounds(child->bounds());
+ child->draw_properties().visible_content_rect = child_rect;
+ child->SetDrawsContent(false);
+ child->SetForceRenderSurface(true);
+
+ grand_child->SetAnchorPoint(gfx::PointF());
+ grand_child->SetPosition(grand_child_rect.origin());
+ grand_child->SetBounds(grand_child_rect.size());
+ grand_child->SetContentBounds(grand_child->bounds());
+ grand_child->draw_properties().visible_content_rect = grand_child_rect;
+ grand_child->SetDrawsContent(true);
+
+ child->AddChild(grand_child.Pass());
+ root->AddChild(child.Pass());
+
+ my_host_impl->active_tree()->SetRootLayer(root.Pass());
+ return my_host_impl.Pass();
+}
+
+TEST_F(LayerTreeHostImplTest, ContributingLayerEmptyScissorPartialSwap) {
+ scoped_ptr<LayerTreeHostImpl> my_host_impl =
+ SetupLayersForOpacity(true, this, &proxy_, &stats_instrumentation_);
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Verify all quads have been computed
+ ASSERT_EQ(2U, frame.render_passes.size());
+ ASSERT_EQ(1U, frame.render_passes[0]->quad_list.size());
+ ASSERT_EQ(1U, frame.render_passes[1]->quad_list.size());
+ EXPECT_EQ(DrawQuad::SOLID_COLOR,
+ frame.render_passes[0]->quad_list[0]->material);
+ EXPECT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[1]->quad_list[0]->material);
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+}
+
+TEST_F(LayerTreeHostImplTest, ContributingLayerEmptyScissorNoPartialSwap) {
+ scoped_ptr<LayerTreeHostImpl> my_host_impl =
+ SetupLayersForOpacity(false, this, &proxy_, &stats_instrumentation_);
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Verify all quads have been computed
+ ASSERT_EQ(2U, frame.render_passes.size());
+ ASSERT_EQ(1U, frame.render_passes[0]->quad_list.size());
+ ASSERT_EQ(1U, frame.render_passes[1]->quad_list.size());
+ EXPECT_EQ(DrawQuad::SOLID_COLOR,
+ frame.render_passes[0]->quad_list[0]->material);
+ EXPECT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[1]->quad_list[0]->material);
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+}
+
+// Fake WebKit::WebGraphicsContext3D that tracks the number of textures in use.
+class TrackingWebGraphicsContext3D : public TestWebGraphicsContext3D {
+ public:
+ TrackingWebGraphicsContext3D()
+ : TestWebGraphicsContext3D(),
+ num_textures_(0) {}
+
+ virtual WebKit::WebGLId createTexture() OVERRIDE {
+ WebKit::WebGLId id = TestWebGraphicsContext3D::createTexture();
+
+ textures_[id] = true;
+ ++num_textures_;
+ return id;
+ }
+
+ virtual void deleteTexture(WebKit::WebGLId id) OVERRIDE {
+ if (textures_.find(id) == textures_.end())
+ return;
+
+ textures_[id] = false;
+ --num_textures_;
+ }
+
+ virtual WebKit::WebString getString(WebKit::WGC3Denum name) OVERRIDE {
+ if (name == GL_EXTENSIONS) {
+ return WebKit::WebString(
+ "GL_CHROMIUM_iosurface GL_ARB_texture_rectangle");
+ }
+
+ return WebKit::WebString();
+ }
+
+ unsigned num_textures() const { return num_textures_; }
+
+ private:
+ base::hash_map<WebKit::WebGLId, bool> textures_;
+ unsigned num_textures_;
+};
+
+TEST_F(LayerTreeHostImplTest, LayersFreeTextures) {
+ scoped_ptr<TestWebGraphicsContext3D> context =
+ TestWebGraphicsContext3D::Create();
+ TestWebGraphicsContext3D* context3d = context.get();
+ scoped_ptr<OutputSurface> output_surface = FakeOutputSurface::Create3d(
+ context.PassAs<WebKit::WebGraphicsContext3D>()).PassAs<OutputSurface>();
+ host_impl_->InitializeRenderer(output_surface.Pass());
+
+ scoped_ptr<LayerImpl> root_layer =
+ LayerImpl::Create(host_impl_->active_tree(), 1);
+ root_layer->SetBounds(gfx::Size(10, 10));
+ root_layer->SetAnchorPoint(gfx::PointF());
+
+ scoped_refptr<VideoFrame> softwareFrame =
+ media::VideoFrame::CreateColorFrame(
+ gfx::Size(4, 4), 0x80, 0x80, 0x80, base::TimeDelta());
+ FakeVideoFrameProvider provider;
+ provider.set_frame(softwareFrame);
+ scoped_ptr<VideoLayerImpl> video_layer =
+ VideoLayerImpl::Create(host_impl_->active_tree(), 4, &provider);
+ video_layer->SetBounds(gfx::Size(10, 10));
+ video_layer->SetAnchorPoint(gfx::PointF());
+ video_layer->SetContentBounds(gfx::Size(10, 10));
+ video_layer->SetDrawsContent(true);
+ root_layer->AddChild(video_layer.PassAs<LayerImpl>());
+
+ scoped_ptr<IOSurfaceLayerImpl> io_surface_layer =
+ IOSurfaceLayerImpl::Create(host_impl_->active_tree(), 5);
+ io_surface_layer->SetBounds(gfx::Size(10, 10));
+ io_surface_layer->SetAnchorPoint(gfx::PointF());
+ io_surface_layer->SetContentBounds(gfx::Size(10, 10));
+ io_surface_layer->SetDrawsContent(true);
+ io_surface_layer->SetIOSurfaceProperties(1, gfx::Size(10, 10));
+ root_layer->AddChild(io_surface_layer.PassAs<LayerImpl>());
+
+ host_impl_->active_tree()->SetRootLayer(root_layer.Pass());
+
+ EXPECT_EQ(0u, context3d->NumTextures());
+
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+ host_impl_->SwapBuffers(frame);
+
+ EXPECT_GT(context3d->NumTextures(), 0u);
+
+ // Kill the layer tree.
+ host_impl_->active_tree()->SetRootLayer(
+ LayerImpl::Create(host_impl_->active_tree(), 100));
+ // There should be no textures left in use after.
+ EXPECT_EQ(0u, context3d->NumTextures());
+}
+
+class MockDrawQuadsToFillScreenContext : public TestWebGraphicsContext3D {
+ public:
+ MOCK_METHOD1(useProgram, void(WebKit::WebGLId program));
+ MOCK_METHOD4(drawElements, void(WebKit::WGC3Denum mode,
+ WebKit::WGC3Dsizei count,
+ WebKit::WGC3Denum type,
+ WebKit::WGC3Dintptr offset));
+};
+
+TEST_F(LayerTreeHostImplTest, HasTransparentBackground) {
+ scoped_ptr<OutputSurface> output_surface =
+ FakeOutputSurface::Create3d(scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new MockDrawQuadsToFillScreenContext)).PassAs<OutputSurface>();
+ MockDrawQuadsToFillScreenContext* mock_context =
+ static_cast<MockDrawQuadsToFillScreenContext*>(
+ output_surface->context3d());
+
+ // Run test case
+ CreateLayerTreeHost(false, output_surface.Pass());
+ SetupRootLayerImpl(LayerImpl::Create(host_impl_->active_tree(), 1));
+ host_impl_->active_tree()->set_background_color(SK_ColorWHITE);
+
+ // Verify one quad is drawn when transparent background set is not set.
+ host_impl_->active_tree()->set_has_transparent_background(false);
+ EXPECT_CALL(*mock_context, useProgram(_))
+ .Times(1);
+ EXPECT_CALL(*mock_context, drawElements(_, _, _, _))
+ .Times(1);
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+ Mock::VerifyAndClearExpectations(&mock_context);
+
+ // Verify no quads are drawn when transparent background is set.
+ host_impl_->active_tree()->set_has_transparent_background(true);
+ host_impl_->SetFullRootLayerDamage();
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+ Mock::VerifyAndClearExpectations(&mock_context);
+}
+
+static void AddDrawingLayerTo(LayerImpl* parent,
+ int id,
+ gfx::Rect layer_rect,
+ LayerImpl** result) {
+ scoped_ptr<LayerImpl> layer =
+ FakeLayerWithQuads::Create(parent->layer_tree_impl(), id);
+ LayerImpl* layer_ptr = layer.get();
+ layer_ptr->SetAnchorPoint(gfx::PointF());
+ layer_ptr->SetPosition(gfx::PointF(layer_rect.origin()));
+ layer_ptr->SetBounds(layer_rect.size());
+ layer_ptr->SetContentBounds(layer_rect.size());
+ layer_ptr->SetDrawsContent(true); // only children draw content
+ layer_ptr->SetContentsOpaque(true);
+ parent->AddChild(layer.Pass());
+ if (result)
+ *result = layer_ptr;
+}
+
+static void SetupLayersForTextureCaching(
+ LayerTreeHostImpl* layer_tree_host_impl,
+ LayerImpl*& root_ptr,
+ LayerImpl*& intermediate_layer_ptr,
+ LayerImpl*& surface_layer_ptr,
+ LayerImpl*& child_ptr,
+ gfx::Size root_size) {
+ scoped_ptr<OutputSurface> output_surface =
+ FakeOutputSurface::Create3d(scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new PartialSwapContext)).PassAs<OutputSurface>();
+
+ layer_tree_host_impl->InitializeRenderer(output_surface.Pass());
+ layer_tree_host_impl->SetViewportSize(root_size);
+
+ scoped_ptr<LayerImpl> root =
+ LayerImpl::Create(layer_tree_host_impl->active_tree(), 1);
+ root_ptr = root.get();
+
+ root->SetAnchorPoint(gfx::PointF());
+ root->SetPosition(gfx::PointF());
+ root->SetBounds(root_size);
+ root->SetContentBounds(root_size);
+ root->SetDrawsContent(true);
+ layer_tree_host_impl->active_tree()->SetRootLayer(root.Pass());
+
+ AddDrawingLayerTo(root_ptr,
+ 2,
+ gfx::Rect(10, 10, root_size.width(), root_size.height()),
+ &intermediate_layer_ptr);
+ // Only children draw content.
+ intermediate_layer_ptr->SetDrawsContent(false);
+
+ // Surface layer is the layer that changes its opacity
+ // It will contain other layers that draw content.
+ AddDrawingLayerTo(intermediate_layer_ptr,
+ 3,
+ gfx::Rect(10, 10, root_size.width(), root_size.height()),
+ &surface_layer_ptr);
+ // Only children draw content.
+ surface_layer_ptr->SetDrawsContent(false);
+ surface_layer_ptr->SetOpacity(0.5f);
+ surface_layer_ptr->SetForceRenderSurface(true);
+
+ // Child of the surface layer will produce some quads
+ AddDrawingLayerTo(surface_layer_ptr,
+ 4,
+ gfx::Rect(5,
+ 5,
+ root_size.width() - 25,
+ root_size.height() - 25),
+ &child_ptr);
+}
+
+class GLRendererWithReleaseTextures : public GLRenderer {
+ public:
+ using GLRenderer::ReleaseRenderPassTextures;
+};
+
+TEST_F(LayerTreeHostImplTest, TextureCachingWithOcclusion) {
+ LayerTreeSettings settings;
+ settings.minimum_occlusion_tracking_size = gfx::Size();
+ settings.cache_render_pass_contents = true;
+ scoped_ptr<LayerTreeHostImpl> my_host_impl =
+ LayerTreeHostImpl::Create(settings,
+ this,
+ &proxy_,
+ &stats_instrumentation_);
+
+ // Layers are structure as follows:
+ //
+ // R +-- S1 +- L10 (owning)
+ // | +- L11
+ // | +- L12
+ // |
+ // +-- S2 +- L20 (owning)
+ // +- L21
+ //
+ // Occlusion:
+ // L12 occludes L11 (internal)
+ // L20 occludes L10 (external)
+ // L21 occludes L20 (internal)
+
+ LayerImpl* root_ptr;
+ LayerImpl* layer_s1_ptr;
+ LayerImpl* layer_s2_ptr;
+
+ scoped_ptr<OutputSurface> output_surface =
+ FakeOutputSurface::Create3d(scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new PartialSwapContext)).PassAs<OutputSurface>();
+
+ gfx::Size root_size(1000, 1000);
+
+ my_host_impl->InitializeRenderer(output_surface.Pass());
+ my_host_impl->SetViewportSize(root_size);
+
+ scoped_ptr<LayerImpl> root =
+ LayerImpl::Create(my_host_impl->active_tree(), 1);
+ root_ptr = root.get();
+
+ root->SetAnchorPoint(gfx::PointF());
+ root->SetPosition(gfx::PointF());
+ root->SetBounds(root_size);
+ root->SetContentBounds(root_size);
+ root->SetDrawsContent(true);
+ root->SetMasksToBounds(true);
+ my_host_impl->active_tree()->SetRootLayer(root.Pass());
+
+ AddDrawingLayerTo(root_ptr, 2, gfx::Rect(300, 300, 300, 300), &layer_s1_ptr);
+ layer_s1_ptr->SetForceRenderSurface(true);
+
+ AddDrawingLayerTo(layer_s1_ptr, 3, gfx::Rect(10, 10, 10, 10), 0); // L11
+ AddDrawingLayerTo(layer_s1_ptr, 4, gfx::Rect(0, 0, 30, 30), 0); // L12
+
+ AddDrawingLayerTo(root_ptr, 5, gfx::Rect(550, 250, 300, 400), &layer_s2_ptr);
+ layer_s2_ptr->SetForceRenderSurface(true);
+
+ AddDrawingLayerTo(layer_s2_ptr, 6, gfx::Rect(20, 20, 5, 5), 0); // L21
+
+ // Initial draw - must receive all quads
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Must receive 3 render passes.
+ // For Root, there are 2 quads; for S1, there are 2 quads (1 is occluded);
+ // for S2, there is 2 quads.
+ ASSERT_EQ(3U, frame.render_passes.size());
+
+ EXPECT_EQ(2U, frame.render_passes[0]->quad_list.size());
+ EXPECT_EQ(2U, frame.render_passes[1]->quad_list.size());
+ EXPECT_EQ(2U, frame.render_passes[2]->quad_list.size());
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+
+ // "Unocclude" surface S1 and repeat draw.
+ // Must remove S2's render pass since it's cached;
+ // Must keep S1 quads because texture contained external occlusion.
+ gfx::Transform transform = layer_s2_ptr->transform();
+ transform.Translate(150.0, 150.0);
+ layer_s2_ptr->SetTransform(transform);
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Must receive 2 render passes.
+ // For Root, there are 2 quads
+ // For S1, the number of quads depends on what got unoccluded, so not
+ // asserted beyond being positive.
+ // For S2, there is no render pass
+ ASSERT_EQ(2U, frame.render_passes.size());
+
+ EXPECT_GT(frame.render_passes[0]->quad_list.size(), 0U);
+ EXPECT_EQ(2U, frame.render_passes[1]->quad_list.size());
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+
+ // "Re-occlude" surface S1 and repeat draw.
+ // Must remove S1's render pass since it is now available in full.
+ // S2 has no change so must also be removed.
+ transform = layer_s2_ptr->transform();
+ transform.Translate(-15.0, -15.0);
+ layer_s2_ptr->SetTransform(transform);
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Must receive 1 render pass - for the root.
+ ASSERT_EQ(1U, frame.render_passes.size());
+
+ EXPECT_EQ(2U, frame.render_passes[0]->quad_list.size());
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+}
+
+TEST_F(LayerTreeHostImplTest, TextureCachingWithOcclusionEarlyOut) {
+ LayerTreeSettings settings;
+ settings.minimum_occlusion_tracking_size = gfx::Size();
+ settings.cache_render_pass_contents = true;
+ scoped_ptr<LayerTreeHostImpl> my_host_impl =
+ LayerTreeHostImpl::Create(settings,
+ this,
+ &proxy_,
+ &stats_instrumentation_);
+
+ // Layers are structure as follows:
+ //
+ // R +-- S1 +- L10 (owning, non drawing)
+ // | +- L11 (corner, unoccluded)
+ // | +- L12 (corner, unoccluded)
+ // | +- L13 (corner, unoccluded)
+ // | +- L14 (corner, entirely occluded)
+ // |
+ // +-- S2 +- L20 (owning, drawing)
+ //
+
+ LayerImpl* root_ptr;
+ LayerImpl* layer_s1_ptr;
+ LayerImpl* layer_s2_ptr;
+
+ scoped_ptr<OutputSurface> output_surface =
+ FakeOutputSurface::Create3d(scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new PartialSwapContext)).PassAs<OutputSurface>();
+
+ gfx::Size root_size(1000, 1000);
+
+ my_host_impl->InitializeRenderer(output_surface.Pass());
+ my_host_impl->SetViewportSize(root_size);
+
+ scoped_ptr<LayerImpl> root =
+ LayerImpl::Create(my_host_impl->active_tree(), 1);
+ root_ptr = root.get();
+
+ root->SetAnchorPoint(gfx::PointF());
+ root->SetPosition(gfx::PointF());
+ root->SetBounds(root_size);
+ root->SetContentBounds(root_size);
+ root->SetDrawsContent(true);
+ root->SetMasksToBounds(true);
+ my_host_impl->active_tree()->SetRootLayer(root.Pass());
+
+ AddDrawingLayerTo(root_ptr, 2, gfx::Rect(0, 0, 800, 800), &layer_s1_ptr);
+ layer_s1_ptr->SetForceRenderSurface(true);
+ layer_s1_ptr->SetDrawsContent(false);
+
+ AddDrawingLayerTo(layer_s1_ptr, 3, gfx::Rect(0, 0, 300, 300), 0); // L11
+ AddDrawingLayerTo(layer_s1_ptr, 4, gfx::Rect(0, 500, 300, 300), 0); // L12
+ AddDrawingLayerTo(layer_s1_ptr, 5, gfx::Rect(500, 0, 300, 300), 0); // L13
+ AddDrawingLayerTo(layer_s1_ptr, 6, gfx::Rect(500, 500, 300, 300), 0); // L14
+ AddDrawingLayerTo(layer_s1_ptr, 9, gfx::Rect(500, 500, 300, 300), 0); // L14
+
+ AddDrawingLayerTo(root_ptr, 7, gfx::Rect(450, 450, 450, 450), &layer_s2_ptr);
+ layer_s2_ptr->SetForceRenderSurface(true);
+
+ // Initial draw - must receive all quads
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Must receive 3 render passes.
+ // For Root, there are 2 quads; for S1, there are 3 quads; for S2, there is
+ // 1 quad.
+ ASSERT_EQ(3U, frame.render_passes.size());
+
+ EXPECT_EQ(1U, frame.render_passes[0]->quad_list.size());
+
+ // L14 is culled, so only 3 quads.
+ EXPECT_EQ(3U, frame.render_passes[1]->quad_list.size());
+ EXPECT_EQ(2U, frame.render_passes[2]->quad_list.size());
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+
+ // "Unocclude" surface S1 and repeat draw.
+ // Must remove S2's render pass since it's cached;
+ // Must keep S1 quads because texture contained external occlusion.
+ gfx::Transform transform = layer_s2_ptr->transform();
+ transform.Translate(100.0, 100.0);
+ layer_s2_ptr->SetTransform(transform);
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Must receive 2 render passes.
+ // For Root, there are 2 quads
+ // For S1, the number of quads depends on what got unoccluded, so not
+ // asserted beyond being positive.
+ // For S2, there is no render pass
+ ASSERT_EQ(2U, frame.render_passes.size());
+
+ EXPECT_GT(frame.render_passes[0]->quad_list.size(), 0U);
+ EXPECT_EQ(2U, frame.render_passes[1]->quad_list.size());
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+
+ // "Re-occlude" surface S1 and repeat draw.
+ // Must remove S1's render pass since it is now available in full.
+ // S2 has no change so must also be removed.
+ transform = layer_s2_ptr->transform();
+ transform.Translate(-15.0, -15.0);
+ layer_s2_ptr->SetTransform(transform);
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Must receive 1 render pass - for the root.
+ ASSERT_EQ(1U, frame.render_passes.size());
+
+ EXPECT_EQ(2U, frame.render_passes[0]->quad_list.size());
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+}
+
+TEST_F(LayerTreeHostImplTest, TextureCachingWithOcclusionExternalOverInternal) {
+ LayerTreeSettings settings;
+ settings.minimum_occlusion_tracking_size = gfx::Size();
+ settings.cache_render_pass_contents = true;
+ scoped_ptr<LayerTreeHostImpl> my_host_impl =
+ LayerTreeHostImpl::Create(settings,
+ this,
+ &proxy_,
+ &stats_instrumentation_);
+
+ // Layers are structured as follows:
+ //
+ // R +-- S1 +- L10 (owning, drawing)
+ // | +- L11 (corner, occluded by L12)
+ // | +- L12 (opposite corner)
+ // |
+ // +-- S2 +- L20 (owning, drawing)
+ //
+
+ LayerImpl* root_ptr;
+ LayerImpl* layer_s1_ptr;
+ LayerImpl* layer_s2_ptr;
+
+ scoped_ptr<OutputSurface> output_surface =
+ FakeOutputSurface::Create3d(scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new PartialSwapContext)).PassAs<OutputSurface>();
+
+ gfx::Size root_size(1000, 1000);
+
+ my_host_impl->InitializeRenderer(output_surface.Pass());
+ my_host_impl->SetViewportSize(root_size);
+
+ scoped_ptr<LayerImpl> root =
+ LayerImpl::Create(my_host_impl->active_tree(), 1);
+ root_ptr = root.get();
+
+ root->SetAnchorPoint(gfx::PointF());
+ root->SetPosition(gfx::PointF());
+ root->SetBounds(root_size);
+ root->SetContentBounds(root_size);
+ root->SetDrawsContent(true);
+ root->SetMasksToBounds(true);
+ my_host_impl->active_tree()->SetRootLayer(root.Pass());
+
+ AddDrawingLayerTo(root_ptr, 2, gfx::Rect(0, 0, 400, 400), &layer_s1_ptr);
+ layer_s1_ptr->SetForceRenderSurface(true);
+
+ AddDrawingLayerTo(layer_s1_ptr, 3, gfx::Rect(0, 0, 300, 300), 0); // L11
+ AddDrawingLayerTo(layer_s1_ptr, 4, gfx::Rect(100, 0, 300, 300), 0); // L12
+
+ AddDrawingLayerTo(root_ptr, 7, gfx::Rect(200, 0, 300, 300), &layer_s2_ptr);
+ layer_s2_ptr->SetForceRenderSurface(true);
+
+ // Initial draw - must receive all quads
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Must receive 3 render passes.
+ // For Root, there are 2 quads; for S1, there are 3 quads; for S2, there is
+ // 1 quad.
+ ASSERT_EQ(3U, frame.render_passes.size());
+
+ EXPECT_EQ(1U, frame.render_passes[0]->quad_list.size());
+ EXPECT_EQ(3U, frame.render_passes[1]->quad_list.size());
+ EXPECT_EQ(2U, frame.render_passes[2]->quad_list.size());
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+
+ // "Unocclude" surface S1 and repeat draw.
+ // Must remove S2's render pass since it's cached;
+ // Must keep S1 quads because texture contained external occlusion.
+ gfx::Transform transform = layer_s2_ptr->transform();
+ transform.Translate(300.0, 0.0);
+ layer_s2_ptr->SetTransform(transform);
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Must receive 2 render passes.
+ // For Root, there are 2 quads
+ // For S1, the number of quads depends on what got unoccluded, so not
+ // asserted beyond being positive.
+ // For S2, there is no render pass
+ ASSERT_EQ(2U, frame.render_passes.size());
+
+ EXPECT_GT(frame.render_passes[0]->quad_list.size(), 0U);
+ EXPECT_EQ(2U, frame.render_passes[1]->quad_list.size());
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+}
+
+TEST_F(LayerTreeHostImplTest, TextureCachingWithOcclusionExternalNotAligned) {
+ LayerTreeSettings settings;
+ settings.cache_render_pass_contents = true;
+ scoped_ptr<LayerTreeHostImpl> my_host_impl =
+ LayerTreeHostImpl::Create(settings,
+ this,
+ &proxy_,
+ &stats_instrumentation_);
+
+ // Layers are structured as follows:
+ //
+ // R +-- S1 +- L10 (rotated, drawing)
+ // +- L11 (occupies half surface)
+
+ LayerImpl* root_ptr;
+ LayerImpl* layer_s1_ptr;
+
+ scoped_ptr<OutputSurface> output_surface =
+ FakeOutputSurface::Create3d(scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new PartialSwapContext)).PassAs<OutputSurface>();
+
+ gfx::Size root_size(1000, 1000);
+
+ my_host_impl->InitializeRenderer(output_surface.Pass());
+ my_host_impl->SetViewportSize(root_size);
+
+ scoped_ptr<LayerImpl> root =
+ LayerImpl::Create(my_host_impl->active_tree(), 1);
+ root_ptr = root.get();
+
+ root->SetAnchorPoint(gfx::PointF());
+ root->SetPosition(gfx::PointF());
+ root->SetBounds(root_size);
+ root->SetContentBounds(root_size);
+ root->SetDrawsContent(true);
+ root->SetMasksToBounds(true);
+ my_host_impl->active_tree()->SetRootLayer(root.Pass());
+
+ AddDrawingLayerTo(root_ptr, 2, gfx::Rect(0, 0, 400, 400), &layer_s1_ptr);
+ layer_s1_ptr->SetForceRenderSurface(true);
+ gfx::Transform transform = layer_s1_ptr->transform();
+ transform.Translate(200.0, 200.0);
+ transform.Rotate(45.0);
+ transform.Translate(-200.0, -200.0);
+ layer_s1_ptr->SetTransform(transform);
+
+ AddDrawingLayerTo(layer_s1_ptr, 3, gfx::Rect(200, 0, 200, 400), 0); // L11
+
+ // Initial draw - must receive all quads
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Must receive 2 render passes.
+ ASSERT_EQ(2U, frame.render_passes.size());
+
+ EXPECT_EQ(2U, frame.render_passes[0]->quad_list.size());
+ EXPECT_EQ(1U, frame.render_passes[1]->quad_list.size());
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+
+ // Change opacity and draw. Verify we used cached texture.
+ layer_s1_ptr->SetOpacity(0.2f);
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // One render pass must be gone due to cached texture.
+ ASSERT_EQ(1U, frame.render_passes.size());
+
+ EXPECT_EQ(1U, frame.render_passes[0]->quad_list.size());
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+}
+
+TEST_F(LayerTreeHostImplTest, TextureCachingWithOcclusionPartialSwap) {
+ LayerTreeSettings settings;
+ settings.minimum_occlusion_tracking_size = gfx::Size();
+ settings.partial_swap_enabled = true;
+ settings.cache_render_pass_contents = true;
+ scoped_ptr<LayerTreeHostImpl> my_host_impl =
+ LayerTreeHostImpl::Create(settings,
+ this,
+ &proxy_,
+ &stats_instrumentation_);
+
+ // Layers are structure as follows:
+ //
+ // R +-- S1 +- L10 (owning)
+ // | +- L11
+ // | +- L12
+ // |
+ // +-- S2 +- L20 (owning)
+ // +- L21
+ //
+ // Occlusion:
+ // L12 occludes L11 (internal)
+ // L20 occludes L10 (external)
+ // L21 occludes L20 (internal)
+
+ LayerImpl* root_ptr;
+ LayerImpl* layer_s1_ptr;
+ LayerImpl* layer_s2_ptr;
+
+ scoped_ptr<OutputSurface> output_surface =
+ FakeOutputSurface::Create3d(scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new PartialSwapContext)).PassAs<OutputSurface>();
+
+ gfx::Size root_size(1000, 1000);
+
+ my_host_impl->InitializeRenderer(output_surface.Pass());
+ my_host_impl->SetViewportSize(root_size);
+
+ scoped_ptr<LayerImpl> root =
+ LayerImpl::Create(my_host_impl->active_tree(), 1);
+ root_ptr = root.get();
+
+ root->SetAnchorPoint(gfx::PointF());
+ root->SetPosition(gfx::PointF());
+ root->SetBounds(root_size);
+ root->SetContentBounds(root_size);
+ root->SetDrawsContent(true);
+ root->SetMasksToBounds(true);
+ my_host_impl->active_tree()->SetRootLayer(root.Pass());
+
+ AddDrawingLayerTo(root_ptr, 2, gfx::Rect(300, 300, 300, 300), &layer_s1_ptr);
+ layer_s1_ptr->SetForceRenderSurface(true);
+
+ AddDrawingLayerTo(layer_s1_ptr, 3, gfx::Rect(10, 10, 10, 10), 0); // L11
+ AddDrawingLayerTo(layer_s1_ptr, 4, gfx::Rect(0, 0, 30, 30), 0); // L12
+
+ AddDrawingLayerTo(root_ptr, 5, gfx::Rect(550, 250, 300, 400), &layer_s2_ptr);
+ layer_s2_ptr->SetForceRenderSurface(true);
+
+ AddDrawingLayerTo(layer_s2_ptr, 6, gfx::Rect(20, 20, 5, 5), 0); // L21
+
+ // Initial draw - must receive all quads
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Must receive 3 render passes.
+ // For Root, there are 2 quads; for S1, there are 2 quads (one is occluded);
+ // for S2, there is 2 quads.
+ ASSERT_EQ(3U, frame.render_passes.size());
+
+ EXPECT_EQ(2U, frame.render_passes[0]->quad_list.size());
+ EXPECT_EQ(2U, frame.render_passes[1]->quad_list.size());
+ EXPECT_EQ(2U, frame.render_passes[2]->quad_list.size());
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+
+ // "Unocclude" surface S1 and repeat draw.
+ // Must remove S2's render pass since it's cached;
+ // Must keep S1 quads because texture contained external occlusion.
+ gfx::Transform transform = layer_s2_ptr->transform();
+ transform.Translate(150.0, 150.0);
+ layer_s2_ptr->SetTransform(transform);
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Must receive 2 render passes.
+ // For Root, there are 2 quads.
+ // For S1, there are 2 quads.
+ // For S2, there is no render pass
+ ASSERT_EQ(2U, frame.render_passes.size());
+
+ EXPECT_EQ(2U, frame.render_passes[0]->quad_list.size());
+ EXPECT_EQ(2U, frame.render_passes[1]->quad_list.size());
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+
+ // "Re-occlude" surface S1 and repeat draw.
+ // Must remove S1's render pass since it is now available in full.
+ // S2 has no change so must also be removed.
+ transform = layer_s2_ptr->transform();
+ transform.Translate(-15.0, -15.0);
+ layer_s2_ptr->SetTransform(transform);
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Root render pass only.
+ ASSERT_EQ(1U, frame.render_passes.size());
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+}
+
+TEST_F(LayerTreeHostImplTest, TextureCachingWithScissor) {
+ LayerTreeSettings settings;
+ settings.minimum_occlusion_tracking_size = gfx::Size();
+ settings.cache_render_pass_contents = true;
+ scoped_ptr<LayerTreeHostImpl> my_host_impl =
+ LayerTreeHostImpl::Create(settings,
+ this,
+ &proxy_,
+ &stats_instrumentation_);
+
+ /*
+ Layers are created as follows:
+
+ +--------------------+
+ | 1 |
+ | +-----------+ |
+ | | 2 | |
+ | | +-------------------+
+ | | | 3 |
+ | | +-------------------+
+ | | | |
+ | +-----------+ |
+ | |
+ | |
+ +--------------------+
+
+ Layers 1, 2 have render surfaces
+ */
+ scoped_ptr<LayerImpl> root =
+ LayerImpl::Create(my_host_impl->active_tree(), 1);
+ scoped_ptr<TiledLayerImpl> child =
+ TiledLayerImpl::Create(my_host_impl->active_tree(), 2);
+ scoped_ptr<LayerImpl> grand_child =
+ LayerImpl::Create(my_host_impl->active_tree(), 3);
+
+ gfx::Rect root_rect(0, 0, 100, 100);
+ gfx::Rect child_rect(10, 10, 50, 50);
+ gfx::Rect grand_child_rect(5, 5, 150, 150);
+
+ scoped_ptr<OutputSurface> output_surface =
+ FakeOutputSurface::Create3d(scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new PartialSwapContext)).PassAs<OutputSurface>();
+ my_host_impl->InitializeRenderer(output_surface.Pass());
+
+ root->SetAnchorPoint(gfx::PointF());
+ root->SetPosition(gfx::PointF(root_rect.x(), root_rect.y()));
+ root->SetBounds(gfx::Size(root_rect.width(), root_rect.height()));
+ root->SetContentBounds(root->bounds());
+ root->SetDrawsContent(true);
+ root->SetMasksToBounds(true);
+
+ child->SetAnchorPoint(gfx::PointF());
+ child->SetPosition(gfx::PointF(child_rect.x(), child_rect.y()));
+ child->SetOpacity(0.5f);
+ child->SetBounds(gfx::Size(child_rect.width(), child_rect.height()));
+ child->SetContentBounds(child->bounds());
+ child->SetDrawsContent(true);
+ child->set_skips_draw(false);
+
+ // child layer has 10x10 tiles.
+ scoped_ptr<LayerTilingData> tiler =
+ LayerTilingData::Create(gfx::Size(10, 10),
+ LayerTilingData::HAS_BORDER_TEXELS);
+ tiler->SetBounds(child->content_bounds());
+ child->SetTilingData(*tiler.get());
+
+ grand_child->SetAnchorPoint(gfx::PointF());
+ grand_child->SetPosition(grand_child_rect.origin());
+ grand_child->SetBounds(grand_child_rect.size());
+ grand_child->SetContentBounds(grand_child->bounds());
+ grand_child->SetDrawsContent(true);
+
+ TiledLayerImpl* child_ptr = child.get();
+ RenderPass::Id child_pass_id(child_ptr->id(), 0);
+
+ child->AddChild(grand_child.Pass());
+ root->AddChild(child.PassAs<LayerImpl>());
+ my_host_impl->active_tree()->SetRootLayer(root.Pass());
+ my_host_impl->SetViewportSize(root_rect.size());
+
+ EXPECT_FALSE(my_host_impl->renderer()->HaveCachedResourcesForRenderPassId(
+ child_pass_id));
+ {
+ LayerTreeHostImpl::FrameData frame;
+ host_impl_->SetFullRootLayerDamage();
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+
+ // We should have cached textures for surface 2.
+ EXPECT_TRUE(my_host_impl->renderer()->HaveCachedResourcesForRenderPassId(
+ child_pass_id));
+ {
+ LayerTreeHostImpl::FrameData frame;
+ host_impl_->SetFullRootLayerDamage();
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+
+ // We should still have cached textures for surface 2 after drawing with no
+ // damage.
+ EXPECT_TRUE(my_host_impl->renderer()->HaveCachedResourcesForRenderPassId(
+ child_pass_id));
+
+ // Damage a single tile of surface 2.
+ child_ptr->set_update_rect(gfx::Rect(10, 10, 10, 10));
+ {
+ LayerTreeHostImpl::FrameData frame;
+ host_impl_->SetFullRootLayerDamage();
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+
+ // We should have a cached texture for surface 2 again even though it was
+ // damaged.
+ EXPECT_TRUE(my_host_impl->renderer()->HaveCachedResourcesForRenderPassId(
+ child_pass_id));
+}
+
+TEST_F(LayerTreeHostImplTest, SurfaceTextureCaching) {
+ LayerTreeSettings settings;
+ settings.minimum_occlusion_tracking_size = gfx::Size();
+ settings.partial_swap_enabled = true;
+ settings.cache_render_pass_contents = true;
+ scoped_ptr<LayerTreeHostImpl> my_host_impl =
+ LayerTreeHostImpl::Create(settings,
+ this,
+ &proxy_,
+ &stats_instrumentation_);
+
+ LayerImpl* root_ptr;
+ LayerImpl* intermediate_layer_ptr;
+ LayerImpl* surface_layer_ptr;
+ LayerImpl* child_ptr;
+
+ SetupLayersForTextureCaching(my_host_impl.get(),
+ root_ptr,
+ intermediate_layer_ptr,
+ surface_layer_ptr,
+ child_ptr,
+ gfx::Size(100, 100));
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Must receive two render passes, each with one quad
+ ASSERT_EQ(2U, frame.render_passes.size());
+ EXPECT_EQ(1U, frame.render_passes[0]->quad_list.size());
+ EXPECT_EQ(1U, frame.render_passes[1]->quad_list.size());
+
+ EXPECT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[1]->quad_list[0]->material);
+ const RenderPassDrawQuad* quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[1]->quad_list[0]);
+ RenderPass* target_pass = frame.render_passes_by_id[quad->render_pass_id];
+ ASSERT_TRUE(target_pass);
+ EXPECT_FALSE(target_pass->damage_rect.IsEmpty());
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+
+ // Draw without any change
+ {
+ LayerTreeHostImpl::FrameData frame;
+ my_host_impl->SetFullRootLayerDamage();
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Must receive one render pass, as the other one should be culled
+ ASSERT_EQ(1U, frame.render_passes.size());
+
+ EXPECT_EQ(1U, frame.render_passes[0]->quad_list.size());
+ EXPECT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[0]->quad_list[0]->material);
+ const RenderPassDrawQuad* quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[0]->quad_list[0]);
+ EXPECT_TRUE(frame.render_passes_by_id.find(quad->render_pass_id) ==
+ frame.render_passes_by_id.end());
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+
+ // Change opacity and draw
+ surface_layer_ptr->SetOpacity(0.6f);
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Must receive one render pass, as the other one should be culled
+ ASSERT_EQ(1U, frame.render_passes.size());
+
+ EXPECT_EQ(1U, frame.render_passes[0]->quad_list.size());
+ EXPECT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[0]->quad_list[0]->material);
+ const RenderPassDrawQuad* quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[0]->quad_list[0]);
+ EXPECT_TRUE(frame.render_passes_by_id.find(quad->render_pass_id) ==
+ frame.render_passes_by_id.end());
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+
+ // Change less benign property and draw - should have contents changed flag
+ surface_layer_ptr->SetStackingOrderChanged(true);
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Must receive two render passes, each with one quad
+ ASSERT_EQ(2U, frame.render_passes.size());
+
+ EXPECT_EQ(1U, frame.render_passes[0]->quad_list.size());
+ EXPECT_EQ(DrawQuad::SOLID_COLOR,
+ frame.render_passes[0]->quad_list[0]->material);
+
+ EXPECT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[1]->quad_list[0]->material);
+ const RenderPassDrawQuad* quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[1]->quad_list[0]);
+ RenderPass* target_pass = frame.render_passes_by_id[quad->render_pass_id];
+ ASSERT_TRUE(target_pass);
+ EXPECT_FALSE(target_pass->damage_rect.IsEmpty());
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+
+ // Change opacity again, and evict the cached surface texture.
+ surface_layer_ptr->SetOpacity(0.5f);
+ static_cast<GLRendererWithReleaseTextures*>(
+ my_host_impl->renderer())->ReleaseRenderPassTextures();
+
+ // Change opacity and draw
+ surface_layer_ptr->SetOpacity(0.6f);
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Must receive two render passes
+ ASSERT_EQ(2U, frame.render_passes.size());
+
+ // Even though not enough properties changed, the entire thing must be
+ // redrawn as we don't have cached textures
+ EXPECT_EQ(1U, frame.render_passes[0]->quad_list.size());
+ EXPECT_EQ(1U, frame.render_passes[1]->quad_list.size());
+
+ EXPECT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[1]->quad_list[0]->material);
+ const RenderPassDrawQuad* quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[1]->quad_list[0]);
+ RenderPass* target_pass = frame.render_passes_by_id[quad->render_pass_id];
+ ASSERT_TRUE(target_pass);
+ EXPECT_TRUE(target_pass->damage_rect.IsEmpty());
+
+ // Was our surface evicted?
+ EXPECT_FALSE(my_host_impl->renderer()->HaveCachedResourcesForRenderPassId(
+ target_pass->id));
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+
+ // Draw without any change, to make sure the state is clear
+ {
+ LayerTreeHostImpl::FrameData frame;
+ my_host_impl->SetFullRootLayerDamage();
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Must receive one render pass, as the other one should be culled
+ ASSERT_EQ(1U, frame.render_passes.size());
+
+ EXPECT_EQ(1U, frame.render_passes[0]->quad_list.size());
+ EXPECT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[0]->quad_list[0]->material);
+ const RenderPassDrawQuad* quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[0]->quad_list[0]);
+ EXPECT_TRUE(frame.render_passes_by_id.find(quad->render_pass_id) ==
+ frame.render_passes_by_id.end());
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+
+ // Change location of the intermediate layer
+ gfx::Transform transform = intermediate_layer_ptr->transform();
+ transform.matrix().setDouble(0, 3, 1.0001);
+ intermediate_layer_ptr->SetTransform(transform);
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Must receive one render pass, as the other one should be culled.
+ ASSERT_EQ(1U, frame.render_passes.size());
+ EXPECT_EQ(1U, frame.render_passes[0]->quad_list.size());
+
+ EXPECT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[0]->quad_list[0]->material);
+ const RenderPassDrawQuad* quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[0]->quad_list[0]);
+ EXPECT_TRUE(frame.render_passes_by_id.find(quad->render_pass_id) ==
+ frame.render_passes_by_id.end());
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+}
+
+TEST_F(LayerTreeHostImplTest, SurfaceTextureCachingNoPartialSwap) {
+ LayerTreeSettings settings;
+ settings.minimum_occlusion_tracking_size = gfx::Size();
+ settings.cache_render_pass_contents = true;
+ scoped_ptr<LayerTreeHostImpl> my_host_impl =
+ LayerTreeHostImpl::Create(settings,
+ this,
+ &proxy_,
+ &stats_instrumentation_);
+
+ LayerImpl* root_ptr;
+ LayerImpl* intermediate_layer_ptr;
+ LayerImpl* surface_layer_ptr;
+ LayerImpl* child_ptr;
+
+ SetupLayersForTextureCaching(my_host_impl.get(),
+ root_ptr,
+ intermediate_layer_ptr,
+ surface_layer_ptr,
+ child_ptr,
+ gfx::Size(100, 100));
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Must receive two render passes, each with one quad
+ ASSERT_EQ(2U, frame.render_passes.size());
+ EXPECT_EQ(1U, frame.render_passes[0]->quad_list.size());
+ EXPECT_EQ(1U, frame.render_passes[1]->quad_list.size());
+
+ EXPECT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[1]->quad_list[0]->material);
+ const RenderPassDrawQuad* quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[1]->quad_list[0]);
+ RenderPass* target_pass = frame.render_passes_by_id[quad->render_pass_id];
+ EXPECT_FALSE(target_pass->damage_rect.IsEmpty());
+
+ EXPECT_FALSE(frame.render_passes[0]->damage_rect.IsEmpty());
+ EXPECT_FALSE(frame.render_passes[1]->damage_rect.IsEmpty());
+
+ EXPECT_FALSE(
+ frame.render_passes[0]->has_occlusion_from_outside_target_surface);
+ EXPECT_FALSE(
+ frame.render_passes[1]->has_occlusion_from_outside_target_surface);
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+
+ // Draw without any change
+ {
+ LayerTreeHostImpl::FrameData frame;
+ my_host_impl->SetFullRootLayerDamage();
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Even though there was no change, we set the damage to entire viewport.
+ // One of the passes should be culled as a result, since contents didn't
+ // change and we have cached texture.
+ ASSERT_EQ(1U, frame.render_passes.size());
+ EXPECT_EQ(1U, frame.render_passes[0]->quad_list.size());
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+
+ // Change opacity and draw
+ surface_layer_ptr->SetOpacity(0.6f);
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Must receive one render pass, as the other one should be culled
+ ASSERT_EQ(1U, frame.render_passes.size());
+
+ EXPECT_EQ(1U, frame.render_passes[0]->quad_list.size());
+ EXPECT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[0]->quad_list[0]->material);
+ const RenderPassDrawQuad* quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[0]->quad_list[0]);
+ EXPECT_TRUE(frame.render_passes_by_id.find(quad->render_pass_id) ==
+ frame.render_passes_by_id.end());
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+
+ // Change less benign property and draw - should have contents changed flag
+ surface_layer_ptr->SetStackingOrderChanged(true);
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Must receive two render passes, each with one quad
+ ASSERT_EQ(2U, frame.render_passes.size());
+
+ EXPECT_EQ(1U, frame.render_passes[0]->quad_list.size());
+ EXPECT_EQ(DrawQuad::SOLID_COLOR,
+ frame.render_passes[0]->quad_list[0]->material);
+
+ EXPECT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[1]->quad_list[0]->material);
+ const RenderPassDrawQuad* quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[1]->quad_list[0]);
+ RenderPass* target_pass = frame.render_passes_by_id[quad->render_pass_id];
+ ASSERT_TRUE(target_pass);
+ EXPECT_FALSE(target_pass->damage_rect.IsEmpty());
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+
+ // Change opacity again, and evict the cached surface texture.
+ surface_layer_ptr->SetOpacity(0.5f);
+ static_cast<GLRendererWithReleaseTextures*>(
+ my_host_impl->renderer())->ReleaseRenderPassTextures();
+
+ // Change opacity and draw
+ surface_layer_ptr->SetOpacity(0.6f);
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Must receive two render passes
+ ASSERT_EQ(2U, frame.render_passes.size());
+
+ // Even though not enough properties changed, the entire thing must be
+ // redrawn as we don't have cached textures
+ EXPECT_EQ(1U, frame.render_passes[0]->quad_list.size());
+ EXPECT_EQ(1U, frame.render_passes[1]->quad_list.size());
+
+ EXPECT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[1]->quad_list[0]->material);
+ const RenderPassDrawQuad* quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[1]->quad_list[0]);
+ RenderPass* target_pass = frame.render_passes_by_id[quad->render_pass_id];
+ ASSERT_TRUE(target_pass);
+ EXPECT_TRUE(target_pass->damage_rect.IsEmpty());
+
+ // Was our surface evicted?
+ EXPECT_FALSE(my_host_impl->renderer()->HaveCachedResourcesForRenderPassId(
+ target_pass->id));
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+
+ // Draw without any change, to make sure the state is clear
+ {
+ LayerTreeHostImpl::FrameData frame;
+ my_host_impl->SetFullRootLayerDamage();
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Even though there was no change, we set the damage to entire viewport.
+ // One of the passes should be culled as a result, since contents didn't
+ // change and we have cached texture.
+ ASSERT_EQ(1U, frame.render_passes.size());
+ EXPECT_EQ(1U, frame.render_passes[0]->quad_list.size());
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+
+ // Change location of the intermediate layer
+ gfx::Transform transform = intermediate_layer_ptr->transform();
+ transform.matrix().setDouble(0, 3, 1.0001);
+ intermediate_layer_ptr->SetTransform(transform);
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(my_host_impl->PrepareToDraw(&frame, gfx::Rect()));
+
+ // Must receive one render pass, as the other one should be culled.
+ ASSERT_EQ(1U, frame.render_passes.size());
+ EXPECT_EQ(1U, frame.render_passes[0]->quad_list.size());
+
+ EXPECT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[0]->quad_list[0]->material);
+ const RenderPassDrawQuad* quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[0]->quad_list[0]);
+ EXPECT_TRUE(frame.render_passes_by_id.find(quad->render_pass_id) ==
+ frame.render_passes_by_id.end());
+
+ my_host_impl->DrawLayers(&frame, base::TimeTicks::Now());
+ my_host_impl->DidDrawAllLayers(frame);
+ }
+}
+
+TEST_F(LayerTreeHostImplTest, ReleaseContentsTextureShouldTriggerCommit) {
+ set_reduce_memory_result(false);
+
+ // If changing the memory limit wouldn't result in changing what was
+ // committed, then no commit should be requested.
+ set_reduce_memory_result(false);
+ host_impl_->set_max_memory_needed_bytes(
+ host_impl_->memory_allocation_limit_bytes() - 1);
+ host_impl_->SetMemoryPolicy(ManagedMemoryPolicy(
+ host_impl_->memory_allocation_limit_bytes() - 1));
+ host_impl_->SetDiscardBackBufferWhenNotVisible(true);
+ EXPECT_FALSE(did_request_commit_);
+ did_request_commit_ = false;
+
+ // If changing the memory limit would result in changing what was
+ // committed, then a commit should be requested, even though nothing was
+ // evicted.
+ set_reduce_memory_result(false);
+ host_impl_->set_max_memory_needed_bytes(
+ host_impl_->memory_allocation_limit_bytes());
+ host_impl_->SetMemoryPolicy(ManagedMemoryPolicy(
+ host_impl_->memory_allocation_limit_bytes() - 1));
+ host_impl_->SetDiscardBackBufferWhenNotVisible(true);
+ EXPECT_TRUE(did_request_commit_);
+ did_request_commit_ = false;
+
+ // Especially if changing the memory limit caused evictions, we need
+ // to re-commit.
+ set_reduce_memory_result(true);
+ host_impl_->set_max_memory_needed_bytes(1);
+ host_impl_->SetMemoryPolicy(ManagedMemoryPolicy(
+ host_impl_->memory_allocation_limit_bytes() - 1));
+ host_impl_->SetDiscardBackBufferWhenNotVisible(true);
+ EXPECT_TRUE(did_request_commit_);
+ did_request_commit_ = false;
+
+ // But if we set it to the same value that it was before, we shouldn't
+ // re-commit.
+ host_impl_->SetMemoryPolicy(ManagedMemoryPolicy(
+ host_impl_->memory_allocation_limit_bytes()));
+ host_impl_->SetDiscardBackBufferWhenNotVisible(true);
+ EXPECT_FALSE(did_request_commit_);
+}
+
+struct RenderPassRemovalTestData : public LayerTreeHostImpl::FrameData {
+ ScopedPtrHashMap<RenderPass::Id, TestRenderPass> render_pass_cache;
+ scoped_ptr<SharedQuadState> shared_quad_state;
+};
+
+class TestRenderer : public GLRenderer, public RendererClient {
+ public:
+ static scoped_ptr<TestRenderer> Create(ResourceProvider* resource_provider,
+ OutputSurface* output_surface,
+ Proxy* proxy) {
+ scoped_ptr<TestRenderer> renderer(new TestRenderer(resource_provider,
+ output_surface,
+ proxy));
+ if (!renderer->Initialize())
+ return scoped_ptr<TestRenderer>();
+
+ return renderer.Pass();
+ }
+
+ void ClearCachedTextures() { textures_.clear(); }
+ void SetHaveCachedResourcesForRenderPassId(RenderPass::Id id) {
+ textures_.insert(id);
+ }
+
+ virtual bool HaveCachedResourcesForRenderPassId(RenderPass::Id id) const
+ OVERRIDE {
+ return textures_.count(id);
+ }
+
+ // RendererClient implementation.
+ virtual gfx::Rect DeviceViewport() const OVERRIDE {
+ return gfx::Rect(viewport_size_);
+ }
+ virtual float DeviceScaleFactor() const OVERRIDE {
+ return 1.f;
+ }
+ virtual const LayerTreeSettings& Settings() const OVERRIDE {
+ return settings_;
+ }
+ virtual void SetFullRootLayerDamage() OVERRIDE {}
+ virtual bool HasImplThread() const OVERRIDE { return false; }
+ virtual bool ShouldClearRootRenderPass() const OVERRIDE { return true; }
+ virtual CompositorFrameMetadata MakeCompositorFrameMetadata() const
+ OVERRIDE { return CompositorFrameMetadata(); }
+ virtual bool AllowPartialSwap() const OVERRIDE {
+ return true;
+ }
+ virtual bool ExternalStencilTestEnabled() const OVERRIDE { return false; }
+
+ protected:
+ TestRenderer(ResourceProvider* resource_provider,
+ OutputSurface* output_surface,
+ Proxy* proxy)
+ : GLRenderer(this, output_surface, resource_provider, 0) {}
+
+ private:
+ LayerTreeSettings settings_;
+ gfx::Size viewport_size_;
+ base::hash_set<RenderPass::Id> textures_;
+};
+
+static void ConfigureRenderPassTestData(const char* test_script,
+ RenderPassRemovalTestData* test_data,
+ TestRenderer* renderer) {
+ renderer->ClearCachedTextures();
+
+ // One shared state for all quads - we don't need the correct details
+ test_data->shared_quad_state = SharedQuadState::Create();
+ test_data->shared_quad_state->SetAll(gfx::Transform(),
+ gfx::Size(),
+ gfx::Rect(),
+ gfx::Rect(),
+ false,
+ 1.f);
+
+ const char* current_char = test_script;
+
+ // Pre-create root pass
+ RenderPass::Id root_render_pass_id =
+ RenderPass::Id(test_script[0], test_script[1]);
+ scoped_ptr<TestRenderPass> pass = TestRenderPass::Create();
+ pass->SetNew(root_render_pass_id, gfx::Rect(), gfx::Rect(), gfx::Transform());
+ test_data->render_pass_cache.add(root_render_pass_id, pass.Pass());
+ while (*current_char) {
+ int layer_id = *current_char;
+ current_char++;
+ ASSERT_TRUE(current_char);
+ int index = *current_char;
+ current_char++;
+
+ RenderPass::Id render_pass_id = RenderPass::Id(layer_id, index);
+
+ bool is_replica = false;
+ if (!test_data->render_pass_cache.contains(render_pass_id))
+ is_replica = true;
+
+ scoped_ptr<TestRenderPass> render_pass =
+ test_data->render_pass_cache.take(render_pass_id);
+
+ // Cycle through quad data and create all quads.
+ while (*current_char && *current_char != '\n') {
+ if (*current_char == 's') {
+ // Solid color draw quad.
+ scoped_ptr<SolidColorDrawQuad> quad = SolidColorDrawQuad::Create();
+ quad->SetNew(test_data->shared_quad_state.get(),
+ gfx::Rect(0, 0, 10, 10),
+ SK_ColorWHITE,
+ false);
+
+ render_pass->AppendQuad(quad.PassAs<DrawQuad>());
+ current_char++;
+ } else if ((*current_char >= 'A') && (*current_char <= 'Z')) {
+ // RenderPass draw quad.
+ int layer_id = *current_char;
+ current_char++;
+ ASSERT_TRUE(current_char);
+ int index = *current_char;
+ current_char++;
+ RenderPass::Id new_render_pass_id = RenderPass::Id(layer_id, index);
+ ASSERT_NE(root_render_pass_id, new_render_pass_id);
+ bool has_texture = false;
+ bool contents_changed = true;
+
+ if (*current_char == '[') {
+ current_char++;
+ while (*current_char && *current_char != ']') {
+ switch (*current_char) {
+ case 'c':
+ contents_changed = false;
+ break;
+ case 't':
+ has_texture = true;
+ break;
+ }
+ current_char++;
+ }
+ if (*current_char == ']')
+ current_char++;
+ }
+
+ if (test_data->render_pass_cache.find(new_render_pass_id) ==
+ test_data->render_pass_cache.end()) {
+ if (has_texture)
+ renderer->SetHaveCachedResourcesForRenderPassId(new_render_pass_id);
+
+ scoped_ptr<TestRenderPass> pass = TestRenderPass::Create();
+ pass->SetNew(new_render_pass_id,
+ gfx::Rect(),
+ gfx::Rect(),
+ gfx::Transform());
+ test_data->render_pass_cache.add(new_render_pass_id, pass.Pass());
+ }
+
+ gfx::Rect quad_rect = gfx::Rect(0, 0, 1, 1);
+ gfx::Rect contents_changed_rect =
+ contents_changed ? quad_rect : gfx::Rect();
+ scoped_ptr<RenderPassDrawQuad> quad = RenderPassDrawQuad::Create();
+ quad->SetNew(test_data->shared_quad_state.get(),
+ quad_rect,
+ new_render_pass_id,
+ is_replica,
+ 1,
+ contents_changed_rect,
+ gfx::RectF(0.f, 0.f, 1.f, 1.f),
+ FilterOperations(),
+ skia::RefPtr<SkImageFilter>(),
+ FilterOperations());
+ render_pass->AppendQuad(quad.PassAs<DrawQuad>());
+ }
+ }
+ test_data->render_passes_by_id[render_pass_id] = render_pass.get();
+ test_data->render_passes.insert(test_data->render_passes.begin(),
+ render_pass.PassAs<RenderPass>());
+ if (*current_char)
+ current_char++;
+ }
+}
+
+void DumpRenderPassTestData(const RenderPassRemovalTestData& test_data,
+ char* buffer) {
+ char* pos = buffer;
+ for (RenderPassList::const_reverse_iterator it =
+ test_data.render_passes.rbegin();
+ it != test_data.render_passes.rend();
+ ++it) {
+ const RenderPass* current_pass = *it;
+ *pos = current_pass->id.layer_id;
+ pos++;
+ *pos = current_pass->id.index;
+ pos++;
+
+ QuadList::const_iterator quad_list_iterator =
+ current_pass->quad_list.begin();
+ while (quad_list_iterator != current_pass->quad_list.end()) {
+ DrawQuad* current_quad = *quad_list_iterator;
+ switch (current_quad->material) {
+ case DrawQuad::SOLID_COLOR:
+ *pos = 's';
+ pos++;
+ break;
+ case DrawQuad::RENDER_PASS:
+ *pos = RenderPassDrawQuad::MaterialCast(current_quad)->
+ render_pass_id.layer_id;
+ pos++;
+ *pos = RenderPassDrawQuad::MaterialCast(current_quad)->
+ render_pass_id.index;
+ pos++;
+ break;
+ default:
+ *pos = 'x';
+ pos++;
+ break;
+ }
+
+ quad_list_iterator++;
+ }
+ *pos = '\n';
+ pos++;
+ }
+ *pos = '\0';
+}
+
+// Each RenderPassList is represented by a string which describes the
+// configuration.
+// The syntax of the string is as follows:
+//
+// RsssssX[c]ssYsssZ[t]ssW[ct]
+// Identifies the render pass------------------------^ ^^^ ^ ^ ^ ^ ^
+// These are solid color quads--------------------------+ | | | | |
+// Identifies RenderPassDrawQuad's RenderPass--------------+ | | | |
+// This quad's contents didn't change------------------------+ | | |
+// This quad's contents changed and it has no texture------------+ | |
+// This quad has texture but its contents changed----------------------+ |
+// This quad's contents didn't change and it has texture - will be removed---+
+//
+// Expected results have exactly the same syntax, except they do not use square
+// brackets, since we only check the structure, not attributes.
+//
+// Test case configuration consists of initialization script and expected
+// results, all in the same format.
+struct TestCase {
+ const char* name;
+ const char* init_script;
+ const char* expected_result;
+};
+
+TestCase remove_render_passes_cases[] = {
+ {
+ "Single root pass",
+ "R0ssss\n",
+ "R0ssss\n"
+ }, {
+ "Single pass - no quads",
+ "R0\n",
+ "R0\n"
+ }, {
+ "Two passes, no removal",
+ "R0ssssA0sss\n"
+ "A0ssss\n",
+ "R0ssssA0sss\n"
+ "A0ssss\n"
+ }, {
+ "Two passes, remove last",
+ "R0ssssA0[ct]sss\n"
+ "A0ssss\n",
+ "R0ssssA0sss\n"
+ }, {
+ "Have texture but contents changed - leave pass",
+ "R0ssssA0[t]sss\n"
+ "A0ssss\n",
+ "R0ssssA0sss\n"
+ "A0ssss\n"
+ }, {
+ "Contents didn't change but no texture - leave pass",
+ "R0ssssA0[c]sss\n"
+ "A0ssss\n",
+ "R0ssssA0sss\n"
+ "A0ssss\n"
+ }, {
+ "Replica: two quads reference the same pass; remove",
+ "R0ssssA0[ct]A0[ct]sss\n"
+ "A0ssss\n",
+ "R0ssssA0A0sss\n"
+ }, {
+ "Replica: two quads reference the same pass; leave",
+ "R0ssssA0[c]A0[c]sss\n"
+ "A0ssss\n",
+ "R0ssssA0A0sss\n"
+ "A0ssss\n",
+ }, {
+ "Many passes, remove all",
+ "R0ssssA0[ct]sss\n"
+ "A0sssB0[ct]C0[ct]s\n"
+ "B0sssD0[ct]ssE0[ct]F0[ct]\n"
+ "E0ssssss\n"
+ "C0G0[ct]\n"
+ "D0sssssss\n"
+ "F0sssssss\n"
+ "G0sss\n",
+
+ "R0ssssA0sss\n"
+ }, {
+ "Deep recursion, remove all",
+
+ "R0sssssA0[ct]ssss\n"
+ "A0ssssB0sss\n"
+ "B0C0\n"
+ "C0D0\n"
+ "D0E0\n"
+ "E0F0\n"
+ "F0G0\n"
+ "G0H0\n"
+ "H0sssI0sss\n"
+ "I0J0\n"
+ "J0ssss\n",
+
+ "R0sssssA0ssss\n"
+ }, {
+ "Wide recursion, remove all",
+ "R0A0[ct]B0[ct]C0[ct]D0[ct]E0[ct]F0[ct]G0[ct]H0[ct]I0[ct]J0[ct]\n"
+ "A0s\n"
+ "B0s\n"
+ "C0ssss\n"
+ "D0ssss\n"
+ "E0s\n"
+ "F0\n"
+ "G0s\n"
+ "H0s\n"
+ "I0s\n"
+ "J0ssss\n",
+
+ "R0A0B0C0D0E0F0G0H0I0J0\n"
+ }, {
+ "Remove passes regardless of cache state",
+ "R0ssssA0[ct]sss\n"
+ "A0sssB0C0s\n"
+ "B0sssD0[c]ssE0[t]F0\n"
+ "E0ssssss\n"
+ "C0G0\n"
+ "D0sssssss\n"
+ "F0sssssss\n"
+ "G0sss\n",
+
+ "R0ssssA0sss\n"
+ }, {
+ "Leave some passes, remove others",
+
+ "R0ssssA0[c]sss\n"
+ "A0sssB0[t]C0[ct]s\n"
+ "B0sssD0[c]ss\n"
+ "C0G0\n"
+ "D0sssssss\n"
+ "G0sss\n",
+
+ "R0ssssA0sss\n"
+ "A0sssB0C0s\n"
+ "B0sssD0ss\n"
+ "D0sssssss\n"
+ }, {
+ 0, 0, 0
+ }
+};
+
+static void VerifyRenderPassTestData(
+ const TestCase& test_case,
+ const RenderPassRemovalTestData& test_data) {
+ char actual_result[1024];
+ DumpRenderPassTestData(test_data, actual_result);
+ EXPECT_STREQ(test_case.expected_result, actual_result) << "In test case: " <<
+ test_case.name;
+}
+
+TEST_F(LayerTreeHostImplTest, TestRemoveRenderPasses) {
+ scoped_ptr<OutputSurface> output_surface(CreateOutputSurface());
+ ASSERT_TRUE(output_surface->context3d());
+ scoped_ptr<ResourceProvider> resource_provider =
+ ResourceProvider::Create(output_surface.get(), 0);
+
+ scoped_ptr<TestRenderer> renderer =
+ TestRenderer::Create(resource_provider.get(),
+ output_surface.get(),
+ &proxy_);
+
+ int test_case_index = 0;
+ while (remove_render_passes_cases[test_case_index].name) {
+ RenderPassRemovalTestData test_data;
+ ConfigureRenderPassTestData(
+ remove_render_passes_cases[test_case_index].init_script,
+ &test_data,
+ renderer.get());
+ LayerTreeHostImpl::RemoveRenderPasses(
+ LayerTreeHostImpl::CullRenderPassesWithCachedTextures(renderer.get()),
+ &test_data);
+ VerifyRenderPassTestData(remove_render_passes_cases[test_case_index],
+ test_data);
+ test_case_index++;
+ }
+}
+
+class LayerTreeHostImplTestWithDelegatingRenderer
+ : public LayerTreeHostImplTest {
+ protected:
+ virtual scoped_ptr<OutputSurface> CreateOutputSurface() OVERRIDE {
+ return FakeOutputSurface::CreateDelegating3d().PassAs<OutputSurface>();
+ }
+
+ void DrawFrameAndTestDamage(const gfx::RectF& expected_damage) {
+ bool expect_to_draw = !expected_damage.IsEmpty();
+
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ if (!expect_to_draw) {
+ // With no damage, we don't draw, and no quads are created.
+ ASSERT_EQ(0u, frame.render_passes.size());
+ } else {
+ ASSERT_EQ(1u, frame.render_passes.size());
+
+ // Verify the damage rect for the root render pass.
+ const RenderPass* root_render_pass = frame.render_passes.back();
+ EXPECT_RECT_EQ(expected_damage, root_render_pass->damage_rect);
+
+ // Verify the root and child layers' quads are generated and not being
+ // culled.
+ ASSERT_EQ(2u, root_render_pass->quad_list.size());
+
+ LayerImpl* child = host_impl_->active_tree()->root_layer()->children()[0];
+ gfx::RectF expected_child_visible_rect(child->content_bounds());
+ EXPECT_RECT_EQ(expected_child_visible_rect,
+ root_render_pass->quad_list[0]->visible_rect);
+
+ LayerImpl* root = host_impl_->active_tree()->root_layer();
+ gfx::RectF expected_root_visible_rect(root->content_bounds());
+ EXPECT_RECT_EQ(expected_root_visible_rect,
+ root_render_pass->quad_list[1]->visible_rect);
+ }
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+ EXPECT_EQ(expect_to_draw, host_impl_->SwapBuffers(frame));
+ }
+};
+
+TEST_F(LayerTreeHostImplTestWithDelegatingRenderer, FrameIncludesDamageRect) {
+ scoped_ptr<SolidColorLayerImpl> root =
+ SolidColorLayerImpl::Create(host_impl_->active_tree(), 1);
+ root->SetAnchorPoint(gfx::PointF());
+ root->SetPosition(gfx::PointF());
+ root->SetBounds(gfx::Size(10, 10));
+ root->SetContentBounds(gfx::Size(10, 10));
+ root->SetDrawsContent(true);
+
+ // Child layer is in the bottom right corner.
+ scoped_ptr<SolidColorLayerImpl> child =
+ SolidColorLayerImpl::Create(host_impl_->active_tree(), 2);
+ child->SetAnchorPoint(gfx::PointF(0.f, 0.f));
+ child->SetPosition(gfx::PointF(9.f, 9.f));
+ child->SetBounds(gfx::Size(1, 1));
+ child->SetContentBounds(gfx::Size(1, 1));
+ child->SetDrawsContent(true);
+ root->AddChild(child.PassAs<LayerImpl>());
+
+ host_impl_->active_tree()->SetRootLayer(root.PassAs<LayerImpl>());
+
+ // Draw a frame. In the first frame, the entire viewport should be damaged.
+ gfx::Rect full_frame_damage = gfx::Rect(host_impl_->device_viewport_size());
+ DrawFrameAndTestDamage(full_frame_damage);
+
+ // The second frame has damage that doesn't touch the child layer. Its quads
+ // should still be generated.
+ gfx::Rect small_damage = gfx::Rect(0, 0, 1, 1);
+ host_impl_->active_tree()->root_layer()->set_update_rect(small_damage);
+ DrawFrameAndTestDamage(small_damage);
+
+ // The third frame should have no damage, so no quads should be generated.
+ gfx::Rect no_damage;
+ DrawFrameAndTestDamage(no_damage);
+}
+
+class FakeMaskLayerImpl : public LayerImpl {
+ public:
+ static scoped_ptr<FakeMaskLayerImpl> Create(LayerTreeImpl* tree_impl,
+ int id) {
+ return make_scoped_ptr(new FakeMaskLayerImpl(tree_impl, id));
+ }
+
+ virtual ResourceProvider::ResourceId ContentsResourceId() const OVERRIDE {
+ return 0;
+ }
+
+ private:
+ FakeMaskLayerImpl(LayerTreeImpl* tree_impl, int id)
+ : LayerImpl(tree_impl, id) {}
+};
+
+TEST_F(LayerTreeHostImplTest, MaskLayerWithScaling) {
+ LayerTreeSettings settings;
+ settings.layer_transforms_should_scale_layer_contents = true;
+ host_impl_ = LayerTreeHostImpl::Create(settings,
+ this,
+ &proxy_,
+ &stats_instrumentation_);
+ host_impl_->InitializeRenderer(CreateOutputSurface());
+ host_impl_->SetViewportSize(gfx::Size(10, 10));
+
+ // Root
+ // |
+ // +-- Scaling Layer (adds a 2x scale)
+ // |
+ // +-- Content Layer
+ // +--Mask
+ scoped_ptr<LayerImpl> scoped_root =
+ LayerImpl::Create(host_impl_->active_tree(), 1);
+ LayerImpl* root = scoped_root.get();
+ host_impl_->active_tree()->SetRootLayer(scoped_root.Pass());
+
+ scoped_ptr<LayerImpl> scoped_scaling_layer =
+ LayerImpl::Create(host_impl_->active_tree(), 2);
+ LayerImpl* scaling_layer = scoped_scaling_layer.get();
+ root->AddChild(scoped_scaling_layer.Pass());
+
+ scoped_ptr<LayerImpl> scoped_content_layer =
+ LayerImpl::Create(host_impl_->active_tree(), 3);
+ LayerImpl* content_layer = scoped_content_layer.get();
+ scaling_layer->AddChild(scoped_content_layer.Pass());
+
+ scoped_ptr<FakeMaskLayerImpl> scoped_mask_layer =
+ FakeMaskLayerImpl::Create(host_impl_->active_tree(), 4);
+ FakeMaskLayerImpl* mask_layer = scoped_mask_layer.get();
+ content_layer->SetMaskLayer(scoped_mask_layer.PassAs<LayerImpl>());
+
+ gfx::Size root_size(100, 100);
+ root->SetBounds(root_size);
+ root->SetContentBounds(root_size);
+ root->SetPosition(gfx::PointF());
+ root->SetAnchorPoint(gfx::PointF());
+
+ gfx::Size scaling_layer_size(50, 50);
+ scaling_layer->SetBounds(scaling_layer_size);
+ scaling_layer->SetContentBounds(scaling_layer_size);
+ scaling_layer->SetPosition(gfx::PointF());
+ scaling_layer->SetAnchorPoint(gfx::PointF());
+ gfx::Transform scale;
+ scale.Scale(2.f, 2.f);
+ scaling_layer->SetTransform(scale);
+
+ content_layer->SetBounds(scaling_layer_size);
+ content_layer->SetContentBounds(scaling_layer_size);
+ content_layer->SetPosition(gfx::PointF());
+ content_layer->SetAnchorPoint(gfx::PointF());
+ content_layer->SetDrawsContent(true);
+
+ mask_layer->SetBounds(scaling_layer_size);
+ mask_layer->SetContentBounds(scaling_layer_size);
+ mask_layer->SetPosition(gfx::PointF());
+ mask_layer->SetAnchorPoint(gfx::PointF());
+ mask_layer->SetDrawsContent(true);
+
+
+ // Check that the tree scaling is correctly taken into account for the mask,
+ // that should fully map onto the quad.
+ float device_scale_factor = 1.f;
+ host_impl_->SetViewportSize(root_size);
+ host_impl_->SetDeviceScaleFactor(device_scale_factor);
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ ASSERT_EQ(1u, frame.render_passes.size());
+ ASSERT_EQ(1u, frame.render_passes[0]->quad_list.size());
+ ASSERT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[0]->quad_list[0]->material);
+ const RenderPassDrawQuad* render_pass_quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[0]->quad_list[0]);
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(),
+ render_pass_quad->rect.ToString());
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 1.f, 1.f).ToString(),
+ render_pass_quad->mask_uv_rect.ToString());
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+ }
+
+
+ // Applying a DSF should change the render surface size, but won't affect
+ // which part of the mask is used.
+ device_scale_factor = 2.f;
+ gfx::Size device_viewport =
+ gfx::ToFlooredSize(gfx::ScaleSize(root_size, device_scale_factor));
+ host_impl_->SetViewportSize(device_viewport);
+ host_impl_->SetDeviceScaleFactor(device_scale_factor);
+ host_impl_->active_tree()->set_needs_update_draw_properties();
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ ASSERT_EQ(1u, frame.render_passes.size());
+ ASSERT_EQ(1u, frame.render_passes[0]->quad_list.size());
+ ASSERT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[0]->quad_list[0]->material);
+ const RenderPassDrawQuad* render_pass_quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[0]->quad_list[0]);
+ EXPECT_EQ(gfx::Rect(0, 0, 200, 200).ToString(),
+ render_pass_quad->rect.ToString());
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 1.f, 1.f).ToString(),
+ render_pass_quad->mask_uv_rect.ToString());
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+ }
+
+
+ // Applying an equivalent content scale on the content layer and the mask
+ // should still result in the same part of the mask being used.
+ gfx::Size content_bounds =
+ gfx::ToRoundedSize(gfx::ScaleSize(scaling_layer_size,
+ device_scale_factor));
+ content_layer->SetContentBounds(content_bounds);
+ content_layer->SetContentsScale(device_scale_factor, device_scale_factor);
+ mask_layer->SetContentBounds(content_bounds);
+ mask_layer->SetContentsScale(device_scale_factor, device_scale_factor);
+ host_impl_->active_tree()->set_needs_update_draw_properties();
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ ASSERT_EQ(1u, frame.render_passes.size());
+ ASSERT_EQ(1u, frame.render_passes[0]->quad_list.size());
+ ASSERT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[0]->quad_list[0]->material);
+ const RenderPassDrawQuad* render_pass_quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[0]->quad_list[0]);
+ EXPECT_EQ(gfx::Rect(0, 0, 200, 200).ToString(),
+ render_pass_quad->rect.ToString());
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 1.f, 1.f).ToString(),
+ render_pass_quad->mask_uv_rect.ToString());
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+ }
+}
+
+TEST_F(LayerTreeHostImplTest, MaskLayerWithDifferentBounds) {
+ // The mask layer has bounds 100x100 but is attached to a layer with bounds
+ // 50x50.
+
+ scoped_ptr<LayerImpl> scoped_root =
+ LayerImpl::Create(host_impl_->active_tree(), 1);
+ LayerImpl* root = scoped_root.get();
+ host_impl_->active_tree()->SetRootLayer(scoped_root.Pass());
+
+ scoped_ptr<LayerImpl> scoped_content_layer =
+ LayerImpl::Create(host_impl_->active_tree(), 3);
+ LayerImpl* content_layer = scoped_content_layer.get();
+ root->AddChild(scoped_content_layer.Pass());
+
+ scoped_ptr<FakeMaskLayerImpl> scoped_mask_layer =
+ FakeMaskLayerImpl::Create(host_impl_->active_tree(), 4);
+ FakeMaskLayerImpl* mask_layer = scoped_mask_layer.get();
+ content_layer->SetMaskLayer(scoped_mask_layer.PassAs<LayerImpl>());
+
+ gfx::Size root_size(100, 100);
+ root->SetBounds(root_size);
+ root->SetContentBounds(root_size);
+ root->SetPosition(gfx::PointF());
+ root->SetAnchorPoint(gfx::PointF());
+
+ gfx::Size layer_size(50, 50);
+ content_layer->SetBounds(layer_size);
+ content_layer->SetContentBounds(layer_size);
+ content_layer->SetPosition(gfx::PointF());
+ content_layer->SetAnchorPoint(gfx::PointF());
+ content_layer->SetDrawsContent(true);
+
+ gfx::Size mask_size(100, 100);
+ mask_layer->SetBounds(mask_size);
+ mask_layer->SetContentBounds(mask_size);
+ mask_layer->SetPosition(gfx::PointF());
+ mask_layer->SetAnchorPoint(gfx::PointF());
+ mask_layer->SetDrawsContent(true);
+
+ // Check that the mask fills the surface.
+ float device_scale_factor = 1.f;
+ host_impl_->SetViewportSize(root_size);
+ host_impl_->SetDeviceScaleFactor(device_scale_factor);
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ ASSERT_EQ(1u, frame.render_passes.size());
+ ASSERT_EQ(1u, frame.render_passes[0]->quad_list.size());
+ ASSERT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[0]->quad_list[0]->material);
+ const RenderPassDrawQuad* render_pass_quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[0]->quad_list[0]);
+ EXPECT_EQ(gfx::Rect(0, 0, 50, 50).ToString(),
+ render_pass_quad->rect.ToString());
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 1.f, 1.f).ToString(),
+ render_pass_quad->mask_uv_rect.ToString());
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+ }
+
+ // Applying a DSF should change the render surface size, but won't affect
+ // which part of the mask is used.
+ device_scale_factor = 2.f;
+ gfx::Size device_viewport =
+ gfx::ToFlooredSize(gfx::ScaleSize(root_size, device_scale_factor));
+ host_impl_->SetViewportSize(device_viewport);
+ host_impl_->SetDeviceScaleFactor(device_scale_factor);
+ host_impl_->active_tree()->set_needs_update_draw_properties();
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ ASSERT_EQ(1u, frame.render_passes.size());
+ ASSERT_EQ(1u, frame.render_passes[0]->quad_list.size());
+ ASSERT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[0]->quad_list[0]->material);
+ const RenderPassDrawQuad* render_pass_quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[0]->quad_list[0]);
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(),
+ render_pass_quad->rect.ToString());
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 1.f, 1.f).ToString(),
+ render_pass_quad->mask_uv_rect.ToString());
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+ }
+
+ // Applying an equivalent content scale on the content layer and the mask
+ // should still result in the same part of the mask being used.
+ gfx::Size layer_size_large =
+ gfx::ToRoundedSize(gfx::ScaleSize(layer_size, device_scale_factor));
+ content_layer->SetContentBounds(layer_size_large);
+ content_layer->SetContentsScale(device_scale_factor, device_scale_factor);
+ gfx::Size mask_size_large =
+ gfx::ToRoundedSize(gfx::ScaleSize(mask_size, device_scale_factor));
+ mask_layer->SetContentBounds(mask_size_large);
+ mask_layer->SetContentsScale(device_scale_factor, device_scale_factor);
+ host_impl_->active_tree()->set_needs_update_draw_properties();
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ ASSERT_EQ(1u, frame.render_passes.size());
+ ASSERT_EQ(1u, frame.render_passes[0]->quad_list.size());
+ ASSERT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[0]->quad_list[0]->material);
+ const RenderPassDrawQuad* render_pass_quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[0]->quad_list[0]);
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(),
+ render_pass_quad->rect.ToString());
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 1.f, 1.f).ToString(),
+ render_pass_quad->mask_uv_rect.ToString());
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+ }
+
+ // Applying a different contents scale to the mask layer means it will have
+ // a larger texture, but it should use the same tex coords to cover the
+ // layer it masks.
+ mask_layer->SetContentBounds(mask_size);
+ mask_layer->SetContentsScale(1.f, 1.f);
+ host_impl_->active_tree()->set_needs_update_draw_properties();
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ ASSERT_EQ(1u, frame.render_passes.size());
+ ASSERT_EQ(1u, frame.render_passes[0]->quad_list.size());
+ ASSERT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[0]->quad_list[0]->material);
+ const RenderPassDrawQuad* render_pass_quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[0]->quad_list[0]);
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(),
+ render_pass_quad->rect.ToString());
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 1.f, 1.f).ToString(),
+ render_pass_quad->mask_uv_rect.ToString());
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+ }
+}
+
+TEST_F(LayerTreeHostImplTest, ReflectionMaskLayerWithDifferentBounds) {
+ // The replica's mask layer has bounds 100x100 but the replica is of a
+ // layer with bounds 50x50.
+
+ scoped_ptr<LayerImpl> scoped_root =
+ LayerImpl::Create(host_impl_->active_tree(), 1);
+ LayerImpl* root = scoped_root.get();
+ host_impl_->active_tree()->SetRootLayer(scoped_root.Pass());
+
+ scoped_ptr<LayerImpl> scoped_content_layer =
+ LayerImpl::Create(host_impl_->active_tree(), 3);
+ LayerImpl* content_layer = scoped_content_layer.get();
+ root->AddChild(scoped_content_layer.Pass());
+
+ scoped_ptr<LayerImpl> scoped_replica_layer =
+ LayerImpl::Create(host_impl_->active_tree(), 2);
+ LayerImpl* replica_layer = scoped_replica_layer.get();
+ content_layer->SetReplicaLayer(scoped_replica_layer.Pass());
+
+ scoped_ptr<FakeMaskLayerImpl> scoped_mask_layer =
+ FakeMaskLayerImpl::Create(host_impl_->active_tree(), 4);
+ FakeMaskLayerImpl* mask_layer = scoped_mask_layer.get();
+ replica_layer->SetMaskLayer(scoped_mask_layer.PassAs<LayerImpl>());
+
+ gfx::Size root_size(100, 100);
+ root->SetBounds(root_size);
+ root->SetContentBounds(root_size);
+ root->SetPosition(gfx::PointF());
+ root->SetAnchorPoint(gfx::PointF());
+
+ gfx::Size layer_size(50, 50);
+ content_layer->SetBounds(layer_size);
+ content_layer->SetContentBounds(layer_size);
+ content_layer->SetPosition(gfx::PointF());
+ content_layer->SetAnchorPoint(gfx::PointF());
+ content_layer->SetDrawsContent(true);
+
+ gfx::Size mask_size(100, 100);
+ mask_layer->SetBounds(mask_size);
+ mask_layer->SetContentBounds(mask_size);
+ mask_layer->SetPosition(gfx::PointF());
+ mask_layer->SetAnchorPoint(gfx::PointF());
+ mask_layer->SetDrawsContent(true);
+
+ // Check that the mask fills the surface.
+ float device_scale_factor = 1.f;
+ host_impl_->SetViewportSize(root_size);
+ host_impl_->SetDeviceScaleFactor(device_scale_factor);
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ ASSERT_EQ(1u, frame.render_passes.size());
+ ASSERT_EQ(2u, frame.render_passes[0]->quad_list.size());
+ ASSERT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[0]->quad_list[1]->material);
+ const RenderPassDrawQuad* replica_quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[0]->quad_list[1]);
+ EXPECT_TRUE(replica_quad->is_replica);
+ EXPECT_EQ(gfx::Rect(0, 0, 50, 50).ToString(),
+ replica_quad->rect.ToString());
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 1.f, 1.f).ToString(),
+ replica_quad->mask_uv_rect.ToString());
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+ }
+
+ // Applying a DSF should change the render surface size, but won't affect
+ // which part of the mask is used.
+ device_scale_factor = 2.f;
+ gfx::Size device_viewport =
+ gfx::ToFlooredSize(gfx::ScaleSize(root_size, device_scale_factor));
+ host_impl_->SetViewportSize(device_viewport);
+ host_impl_->SetDeviceScaleFactor(device_scale_factor);
+ host_impl_->active_tree()->set_needs_update_draw_properties();
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ ASSERT_EQ(1u, frame.render_passes.size());
+ ASSERT_EQ(2u, frame.render_passes[0]->quad_list.size());
+ ASSERT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[0]->quad_list[1]->material);
+ const RenderPassDrawQuad* replica_quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[0]->quad_list[1]);
+ EXPECT_TRUE(replica_quad->is_replica);
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(),
+ replica_quad->rect.ToString());
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 1.f, 1.f).ToString(),
+ replica_quad->mask_uv_rect.ToString());
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+ }
+
+ // Applying an equivalent content scale on the content layer and the mask
+ // should still result in the same part of the mask being used.
+ gfx::Size layer_size_large =
+ gfx::ToRoundedSize(gfx::ScaleSize(layer_size, device_scale_factor));
+ content_layer->SetContentBounds(layer_size_large);
+ content_layer->SetContentsScale(device_scale_factor, device_scale_factor);
+ gfx::Size mask_size_large =
+ gfx::ToRoundedSize(gfx::ScaleSize(mask_size, device_scale_factor));
+ mask_layer->SetContentBounds(mask_size_large);
+ mask_layer->SetContentsScale(device_scale_factor, device_scale_factor);
+ host_impl_->active_tree()->set_needs_update_draw_properties();
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ ASSERT_EQ(1u, frame.render_passes.size());
+ ASSERT_EQ(2u, frame.render_passes[0]->quad_list.size());
+ ASSERT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[0]->quad_list[1]->material);
+ const RenderPassDrawQuad* replica_quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[0]->quad_list[1]);
+ EXPECT_TRUE(replica_quad->is_replica);
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(),
+ replica_quad->rect.ToString());
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 1.f, 1.f).ToString(),
+ replica_quad->mask_uv_rect.ToString());
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+ }
+
+ // Applying a different contents scale to the mask layer means it will have
+ // a larger texture, but it should use the same tex coords to cover the
+ // layer it masks.
+ mask_layer->SetContentBounds(mask_size);
+ mask_layer->SetContentsScale(1.f, 1.f);
+ host_impl_->active_tree()->set_needs_update_draw_properties();
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ ASSERT_EQ(1u, frame.render_passes.size());
+ ASSERT_EQ(2u, frame.render_passes[0]->quad_list.size());
+ ASSERT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[0]->quad_list[1]->material);
+ const RenderPassDrawQuad* replica_quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[0]->quad_list[1]);
+ EXPECT_TRUE(replica_quad->is_replica);
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(),
+ replica_quad->rect.ToString());
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 1.f, 1.f).ToString(),
+ replica_quad->mask_uv_rect.ToString());
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+ }
+}
+
+TEST_F(LayerTreeHostImplTest, ReflectionMaskLayerForSurfaceWithUnclippedChild) {
+ // The replica is of a layer with bounds 50x50, but it has a child that causes
+ // the surface bounds to be larger.
+
+ scoped_ptr<LayerImpl> scoped_root =
+ LayerImpl::Create(host_impl_->active_tree(), 1);
+ LayerImpl* root = scoped_root.get();
+ host_impl_->active_tree()->SetRootLayer(scoped_root.Pass());
+
+ scoped_ptr<LayerImpl> scoped_content_layer =
+ LayerImpl::Create(host_impl_->active_tree(), 2);
+ LayerImpl* content_layer = scoped_content_layer.get();
+ root->AddChild(scoped_content_layer.Pass());
+
+ scoped_ptr<LayerImpl> scoped_content_child_layer =
+ LayerImpl::Create(host_impl_->active_tree(), 3);
+ LayerImpl* content_child_layer = scoped_content_child_layer.get();
+ content_layer->AddChild(scoped_content_child_layer.Pass());
+
+ scoped_ptr<LayerImpl> scoped_replica_layer =
+ LayerImpl::Create(host_impl_->active_tree(), 4);
+ LayerImpl* replica_layer = scoped_replica_layer.get();
+ content_layer->SetReplicaLayer(scoped_replica_layer.Pass());
+
+ scoped_ptr<FakeMaskLayerImpl> scoped_mask_layer =
+ FakeMaskLayerImpl::Create(host_impl_->active_tree(), 5);
+ FakeMaskLayerImpl* mask_layer = scoped_mask_layer.get();
+ replica_layer->SetMaskLayer(scoped_mask_layer.PassAs<LayerImpl>());
+
+ gfx::Size root_size(100, 100);
+ root->SetBounds(root_size);
+ root->SetContentBounds(root_size);
+ root->SetPosition(gfx::PointF());
+ root->SetAnchorPoint(gfx::PointF());
+
+ gfx::Size layer_size(50, 50);
+ content_layer->SetBounds(layer_size);
+ content_layer->SetContentBounds(layer_size);
+ content_layer->SetPosition(gfx::PointF());
+ content_layer->SetAnchorPoint(gfx::PointF());
+ content_layer->SetDrawsContent(true);
+
+ gfx::Size child_size(50, 50);
+ content_child_layer->SetBounds(child_size);
+ content_child_layer->SetContentBounds(child_size);
+ content_child_layer->SetPosition(gfx::Point(50, 0));
+ content_child_layer->SetAnchorPoint(gfx::PointF());
+ content_child_layer->SetDrawsContent(true);
+
+ gfx::Size mask_size(50, 50);
+ mask_layer->SetBounds(mask_size);
+ mask_layer->SetContentBounds(mask_size);
+ mask_layer->SetPosition(gfx::PointF());
+ mask_layer->SetAnchorPoint(gfx::PointF());
+ mask_layer->SetDrawsContent(true);
+
+ float device_scale_factor = 1.f;
+ host_impl_->SetViewportSize(root_size);
+ host_impl_->SetDeviceScaleFactor(device_scale_factor);
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ ASSERT_EQ(1u, frame.render_passes.size());
+ ASSERT_EQ(2u, frame.render_passes[0]->quad_list.size());
+
+ // The surface is 100x50.
+ ASSERT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[0]->quad_list[0]->material);
+ const RenderPassDrawQuad* render_pass_quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[0]->quad_list[0]);
+ EXPECT_FALSE(render_pass_quad->is_replica);
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 50).ToString(),
+ render_pass_quad->rect.ToString());
+
+ // The mask covers the owning layer only.
+ ASSERT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[0]->quad_list[1]->material);
+ const RenderPassDrawQuad* replica_quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[0]->quad_list[1]);
+ EXPECT_TRUE(replica_quad->is_replica);
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 50).ToString(),
+ replica_quad->rect.ToString());
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 2.f, 1.f).ToString(),
+ replica_quad->mask_uv_rect.ToString());
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+ }
+
+ // Move the child to (-50, 0) instead. Now the mask should be moved to still
+ // cover the layer being replicated.
+ content_child_layer->SetPosition(gfx::Point(-50, 0));
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ ASSERT_EQ(1u, frame.render_passes.size());
+ ASSERT_EQ(2u, frame.render_passes[0]->quad_list.size());
+
+ // The surface is 100x50 with its origin at (-50, 0).
+ ASSERT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[0]->quad_list[0]->material);
+ const RenderPassDrawQuad* render_pass_quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[0]->quad_list[0]);
+ EXPECT_FALSE(render_pass_quad->is_replica);
+ EXPECT_EQ(gfx::Rect(-50, 0, 100, 50).ToString(),
+ render_pass_quad->rect.ToString());
+
+ // The mask covers the owning layer only.
+ ASSERT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[0]->quad_list[1]->material);
+ const RenderPassDrawQuad* replica_quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[0]->quad_list[1]);
+ EXPECT_TRUE(replica_quad->is_replica);
+ EXPECT_EQ(gfx::Rect(-50, 0, 100, 50).ToString(),
+ replica_quad->rect.ToString());
+ EXPECT_EQ(gfx::RectF(-1.f, 0.f, 2.f, 1.f).ToString(),
+ replica_quad->mask_uv_rect.ToString());
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+ }
+}
+
+TEST_F(LayerTreeHostImplTest, MaskLayerForSurfaceWithClippedLayer) {
+ // The masked layer has bounds 50x50, but it has a child that causes
+ // the surface bounds to be larger. It also has a parent that clips the
+ // masked layer and its surface.
+
+ scoped_ptr<LayerImpl> scoped_root =
+ LayerImpl::Create(host_impl_->active_tree(), 1);
+ LayerImpl* root = scoped_root.get();
+ host_impl_->active_tree()->SetRootLayer(scoped_root.Pass());
+
+ scoped_ptr<LayerImpl> scoped_clipping_layer =
+ LayerImpl::Create(host_impl_->active_tree(), 2);
+ LayerImpl* clipping_layer = scoped_clipping_layer.get();
+ root->AddChild(scoped_clipping_layer.Pass());
+
+ scoped_ptr<LayerImpl> scoped_content_layer =
+ LayerImpl::Create(host_impl_->active_tree(), 3);
+ LayerImpl* content_layer = scoped_content_layer.get();
+ clipping_layer->AddChild(scoped_content_layer.Pass());
+
+ scoped_ptr<LayerImpl> scoped_content_child_layer =
+ LayerImpl::Create(host_impl_->active_tree(), 4);
+ LayerImpl* content_child_layer = scoped_content_child_layer.get();
+ content_layer->AddChild(scoped_content_child_layer.Pass());
+
+ scoped_ptr<FakeMaskLayerImpl> scoped_mask_layer =
+ FakeMaskLayerImpl::Create(host_impl_->active_tree(), 6);
+ FakeMaskLayerImpl* mask_layer = scoped_mask_layer.get();
+ content_layer->SetMaskLayer(scoped_mask_layer.PassAs<LayerImpl>());
+
+ gfx::Size root_size(100, 100);
+ root->SetBounds(root_size);
+ root->SetContentBounds(root_size);
+ root->SetPosition(gfx::PointF());
+ root->SetAnchorPoint(gfx::PointF());
+
+ gfx::Rect clipping_rect(20, 10, 10, 20);
+ clipping_layer->SetBounds(clipping_rect.size());
+ clipping_layer->SetContentBounds(clipping_rect.size());
+ clipping_layer->SetPosition(clipping_rect.origin());
+ clipping_layer->SetAnchorPoint(gfx::PointF());
+ clipping_layer->SetMasksToBounds(true);
+
+ gfx::Size layer_size(50, 50);
+ content_layer->SetBounds(layer_size);
+ content_layer->SetContentBounds(layer_size);
+ content_layer->SetPosition(gfx::Point() - clipping_rect.OffsetFromOrigin());
+ content_layer->SetAnchorPoint(gfx::PointF());
+ content_layer->SetDrawsContent(true);
+
+ gfx::Size child_size(50, 50);
+ content_child_layer->SetBounds(child_size);
+ content_child_layer->SetContentBounds(child_size);
+ content_child_layer->SetPosition(gfx::Point(50, 0));
+ content_child_layer->SetAnchorPoint(gfx::PointF());
+ content_child_layer->SetDrawsContent(true);
+
+ gfx::Size mask_size(100, 100);
+ mask_layer->SetBounds(mask_size);
+ mask_layer->SetContentBounds(mask_size);
+ mask_layer->SetPosition(gfx::PointF());
+ mask_layer->SetAnchorPoint(gfx::PointF());
+ mask_layer->SetDrawsContent(true);
+
+ float device_scale_factor = 1.f;
+ host_impl_->SetViewportSize(root_size);
+ host_impl_->SetDeviceScaleFactor(device_scale_factor);
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+
+ ASSERT_EQ(1u, frame.render_passes.size());
+ ASSERT_EQ(1u, frame.render_passes[0]->quad_list.size());
+
+ // The surface is clipped to 10x20.
+ ASSERT_EQ(DrawQuad::RENDER_PASS,
+ frame.render_passes[0]->quad_list[0]->material);
+ const RenderPassDrawQuad* render_pass_quad =
+ RenderPassDrawQuad::MaterialCast(frame.render_passes[0]->quad_list[0]);
+ EXPECT_FALSE(render_pass_quad->is_replica);
+ EXPECT_EQ(gfx::Rect(20, 10, 10, 20).ToString(),
+ render_pass_quad->rect.ToString());
+
+ // The masked layer is 50x50, but the surface size is 10x20. So the texture
+ // coords in the mask are scaled by 10/50 and 20/50.
+ // The surface is clipped to (20,10) so the mask texture coords are offset
+ // by 20/50 and 10/50
+ EXPECT_EQ(gfx::ScaleRect(gfx::RectF(20.f, 10.f, 10.f, 20.f),
+ 1.f / 50.f).ToString(),
+ render_pass_quad->mask_uv_rect.ToString());
+
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+ }
+}
+
+class CompositorFrameMetadataTest : public LayerTreeHostImplTest {
+ public:
+ CompositorFrameMetadataTest()
+ : swap_buffers_complete_(0) {}
+
+ virtual void OnSwapBuffersCompleteOnImplThread() OVERRIDE {
+ swap_buffers_complete_++;
+ }
+
+ int swap_buffers_complete_;
+};
+
+TEST_F(CompositorFrameMetadataTest, CompositorFrameAckCountsAsSwapComplete) {
+ SetupRootLayerImpl(FakeLayerWithQuads::Create(host_impl_->active_tree(), 1));
+ {
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks());
+ host_impl_->DidDrawAllLayers(frame);
+ }
+ CompositorFrameAck ack;
+ host_impl_->OnSwapBuffersComplete(&ack);
+ EXPECT_EQ(swap_buffers_complete_, 1);
+}
+
+class CountingSoftwareDevice : public SoftwareOutputDevice {
+ public:
+ CountingSoftwareDevice() : frames_began_(0), frames_ended_(0) {}
+
+ virtual SkCanvas* BeginPaint(gfx::Rect damage_rect) OVERRIDE {
+ ++frames_began_;
+ return SoftwareOutputDevice::BeginPaint(damage_rect);
+ }
+ virtual void EndPaint(SoftwareFrameData* frame_data) OVERRIDE {
+ ++frames_ended_;
+ SoftwareOutputDevice::EndPaint(frame_data);
+ }
+
+ int frames_began_, frames_ended_;
+};
+
+TEST_F(LayerTreeHostImplTest, ForcedDrawToSoftwareDeviceBasicRender) {
+ // No main thread evictions in resourceless software mode.
+ set_reduce_memory_result(false);
+ SetupScrollAndContentsLayers(gfx::Size(100, 100));
+ host_impl_->SetViewportSize(gfx::Size(50, 50));
+ CountingSoftwareDevice* software_device = new CountingSoftwareDevice();
+ FakeOutputSurface* output_surface = FakeOutputSurface::CreateDeferredGL(
+ scoped_ptr<SoftwareOutputDevice>(software_device)).release();
+ EXPECT_TRUE(host_impl_->InitializeRenderer(
+ scoped_ptr<OutputSurface>(output_surface)));
+
+ output_surface->set_forced_draw_to_software_device(true);
+ EXPECT_TRUE(output_surface->ForcedDrawToSoftwareDevice());
+
+ EXPECT_EQ(0, software_device->frames_began_);
+ EXPECT_EQ(0, software_device->frames_ended_);
+
+ DrawFrame();
+
+ EXPECT_EQ(1, software_device->frames_began_);
+ EXPECT_EQ(1, software_device->frames_ended_);
+
+ // Call other API methods that are likely to hit NULL pointer in this mode.
+ EXPECT_TRUE(host_impl_->AsValue());
+ EXPECT_TRUE(host_impl_->ActivationStateAsValue());
+}
+
+TEST_F(LayerTreeHostImplTest,
+ ForcedDrawToSoftwareDeviceSkipsUnsupportedLayers) {
+ set_reduce_memory_result(false);
+ FakeOutputSurface* output_surface = FakeOutputSurface::CreateDeferredGL(
+ scoped_ptr<SoftwareOutputDevice>(new CountingSoftwareDevice())).release();
+ host_impl_->InitializeRenderer(
+ scoped_ptr<OutputSurface>(output_surface));
+
+ output_surface->set_forced_draw_to_software_device(true);
+ EXPECT_TRUE(output_surface->ForcedDrawToSoftwareDevice());
+
+ // SolidColorLayerImpl will be drawn.
+ scoped_ptr<SolidColorLayerImpl> root_layer =
+ SolidColorLayerImpl::Create(host_impl_->active_tree(), 1);
+
+ // VideoLayerImpl will not be drawn.
+ FakeVideoFrameProvider provider;
+ scoped_ptr<VideoLayerImpl> video_layer =
+ VideoLayerImpl::Create(host_impl_->active_tree(), 2, &provider);
+ video_layer->SetBounds(gfx::Size(10, 10));
+ video_layer->SetContentBounds(gfx::Size(10, 10));
+ video_layer->SetDrawsContent(true);
+ root_layer->AddChild(video_layer.PassAs<LayerImpl>());
+ SetupRootLayerImpl(root_layer.PassAs<LayerImpl>());
+
+ LayerTreeHostImpl::FrameData frame;
+ EXPECT_TRUE(host_impl_->PrepareToDraw(&frame, gfx::Rect()));
+ host_impl_->DrawLayers(&frame, base::TimeTicks::Now());
+ host_impl_->DidDrawAllLayers(frame);
+
+ EXPECT_EQ(1u, frame.will_draw_layers.size());
+ EXPECT_EQ(host_impl_->active_tree()->root_layer(), frame.will_draw_layers[0]);
+}
+
+TEST_F(LayerTreeHostImplTest, DeferredInitializeSmoke) {
+ set_reduce_memory_result(false);
+ scoped_ptr<FakeOutputSurface> output_surface(
+ FakeOutputSurface::CreateDeferredGL(
+ scoped_ptr<SoftwareOutputDevice>(new CountingSoftwareDevice())));
+ FakeOutputSurface* output_surface_ptr = output_surface.get();
+ EXPECT_TRUE(
+ host_impl_->InitializeRenderer(output_surface.PassAs<OutputSurface>()));
+
+ // Add two layers.
+ scoped_ptr<SolidColorLayerImpl> root_layer =
+ SolidColorLayerImpl::Create(host_impl_->active_tree(), 1);
+ FakeVideoFrameProvider provider;
+ scoped_ptr<VideoLayerImpl> video_layer =
+ VideoLayerImpl::Create(host_impl_->active_tree(), 2, &provider);
+ video_layer->SetBounds(gfx::Size(10, 10));
+ video_layer->SetContentBounds(gfx::Size(10, 10));
+ video_layer->SetDrawsContent(true);
+ root_layer->AddChild(video_layer.PassAs<LayerImpl>());
+ SetupRootLayerImpl(root_layer.PassAs<LayerImpl>());
+
+ // Software draw.
+ DrawFrame();
+
+ // DeferredInitialize and hardware draw.
+ EXPECT_FALSE(did_try_initialize_renderer_);
+ EXPECT_TRUE(output_surface_ptr->SetAndInitializeContext3D(
+ scoped_ptr<WebKit::WebGraphicsContext3D>(
+ TestWebGraphicsContext3D::Create())));
+ EXPECT_TRUE(did_try_initialize_renderer_);
+
+ // Defer intialized GL draw.
+ DrawFrame();
+
+ // Revert back to software.
+ did_try_initialize_renderer_ = false;
+ output_surface_ptr->ReleaseGL();
+ EXPECT_TRUE(did_try_initialize_renderer_);
+ DrawFrame();
+}
+
+class ContextThatDoesNotSupportMemoryManagmentExtensions
+ : public TestWebGraphicsContext3D {
+ public:
+ // WebGraphicsContext3D methods.
+ virtual WebKit::WebString getString(WebKit::WGC3Denum name) {
+ return WebKit::WebString();
+ }
+};
+
+// Checks that we have a non-0 default allocation if we pass a context that
+// doesn't support memory management extensions.
+TEST_F(LayerTreeHostImplTest, DefaultMemoryAllocation) {
+ LayerTreeSettings settings;
+ host_impl_ = LayerTreeHostImpl::Create(settings,
+ this,
+ &proxy_,
+ &stats_instrumentation_);
+
+ host_impl_->InitializeRenderer(FakeOutputSurface::Create3d(
+ scoped_ptr<WebKit::WebGraphicsContext3D>(
+ new ContextThatDoesNotSupportMemoryManagmentExtensions))
+ .PassAs<OutputSurface>());
+ EXPECT_LT(0ul, host_impl_->memory_allocation_limit_bytes());
+}
+
+TEST_F(LayerTreeHostImplTest, MemoryPolicy) {
+ ManagedMemoryPolicy policy1(
+ 456, ManagedMemoryPolicy::CUTOFF_ALLOW_EVERYTHING,
+ 123, ManagedMemoryPolicy::CUTOFF_ALLOW_NICE_TO_HAVE, 1000);
+ int visible_cutoff_value = ManagedMemoryPolicy::PriorityCutoffToValue(
+ policy1.priority_cutoff_when_visible);
+ int not_visible_cutoff_value = ManagedMemoryPolicy::PriorityCutoffToValue(
+ policy1.priority_cutoff_when_not_visible);
+
+ host_impl_->SetVisible(true);
+ host_impl_->SetMemoryPolicy(policy1);
+ EXPECT_EQ(policy1.bytes_limit_when_visible, current_limit_bytes_);
+ EXPECT_EQ(visible_cutoff_value, current_priority_cutoff_value_);
+
+ host_impl_->SetVisible(false);
+ EXPECT_EQ(policy1.bytes_limit_when_not_visible, current_limit_bytes_);
+ EXPECT_EQ(not_visible_cutoff_value, current_priority_cutoff_value_);
+
+ host_impl_->SetVisible(true);
+ EXPECT_EQ(policy1.bytes_limit_when_visible, current_limit_bytes_);
+ EXPECT_EQ(visible_cutoff_value, current_priority_cutoff_value_);
+}
+
+TEST_F(LayerTreeHostImplTest, UIResourceManagement) {
+ scoped_ptr<TestWebGraphicsContext3D> context =
+ TestWebGraphicsContext3D::Create();
+ TestWebGraphicsContext3D* context3d = context.get();
+ scoped_ptr<OutputSurface> output_surface = FakeOutputSurface::Create3d(
+ context.PassAs<WebKit::WebGraphicsContext3D>()).PassAs<OutputSurface>();
+ host_impl_->InitializeRenderer(output_surface.Pass());
+
+ EXPECT_EQ(0u, context3d->NumTextures());
+
+ UIResourceId ui_resource_id = 1;
+ scoped_refptr<UIResourceBitmap> bitmap = UIResourceBitmap::Create(
+ new uint8_t[1], UIResourceBitmap::RGBA8, gfx::Size(1, 1));
+ host_impl_->CreateUIResource(ui_resource_id, bitmap);
+ EXPECT_EQ(1u, context3d->NumTextures());
+ ResourceProvider::ResourceId id1 =
+ host_impl_->ResourceIdForUIResource(ui_resource_id);
+ EXPECT_NE(0u, id1);
+
+ // Multiple requests with the same id is allowed. The previous texture is
+ // deleted.
+ host_impl_->CreateUIResource(ui_resource_id, bitmap);
+ EXPECT_EQ(1u, context3d->NumTextures());
+ ResourceProvider::ResourceId id2 =
+ host_impl_->ResourceIdForUIResource(ui_resource_id);
+ EXPECT_NE(0u, id2);
+ EXPECT_NE(id1, id2);
+
+ // Deleting invalid UIResourceId is allowed and does not change state.
+ host_impl_->DeleteUIResource(-1);
+ EXPECT_EQ(1u, context3d->NumTextures());
+
+ // Should return zero for invalid UIResourceId. Number of textures should
+ // not change.
+ EXPECT_EQ(0u, host_impl_->ResourceIdForUIResource(-1));
+ EXPECT_EQ(1u, context3d->NumTextures());
+
+ host_impl_->DeleteUIResource(ui_resource_id);
+ EXPECT_EQ(0u, host_impl_->ResourceIdForUIResource(ui_resource_id));
+ EXPECT_EQ(0u, context3d->NumTextures());
+
+ // Should not change state for multiple deletion on one UIResourceId
+ host_impl_->DeleteUIResource(ui_resource_id);
+ EXPECT_EQ(0u, context3d->NumTextures());
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/trees/layer_tree_host_perftest.cc b/chromium/cc/trees/layer_tree_host_perftest.cc
new file mode 100644
index 00000000000..fc7673cd7ba
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_host_perftest.cc
@@ -0,0 +1,292 @@
+// 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 "cc/trees/layer_tree_host.h"
+
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "base/strings/string_piece.h"
+#include "cc/layers/content_layer.h"
+#include "cc/layers/nine_patch_layer.h"
+#include "cc/layers/solid_color_layer.h"
+#include "cc/test/fake_content_layer_client.h"
+#include "cc/test/layer_tree_json_parser.h"
+#include "cc/test/layer_tree_test.h"
+#include "cc/test/paths.h"
+#include "cc/trees/layer_tree_impl.h"
+
+namespace cc {
+namespace {
+
+static const int kTimeLimitMillis = 2000;
+static const int kWarmupRuns = 5;
+static const int kTimeCheckInterval = 10;
+
+class LayerTreeHostPerfTest : public LayerTreeTest {
+ public:
+ LayerTreeHostPerfTest()
+ : num_draws_(0),
+ num_commits_(0),
+ full_damage_each_frame_(false),
+ animation_driven_drawing_(false),
+ measure_commit_cost_(false) {
+ fake_content_layer_client_.set_paint_all_opaque(true);
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ BuildTree();
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void Animate(base::TimeTicks monotonic_time) OVERRIDE {
+ if (animation_driven_drawing_ && !TestEnded())
+ layer_tree_host()->SetNeedsAnimate();
+ }
+
+ virtual void BeginCommitOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ if (measure_commit_cost_)
+ commit_start_time_ = base::TimeTicks::HighResNow();
+ }
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ if (measure_commit_cost_ && num_draws_ >= kWarmupRuns) {
+ total_commit_time_ += base::TimeTicks::HighResNow() - commit_start_time_;
+ ++num_commits_;
+ }
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ ++num_draws_;
+ if (num_draws_ == kWarmupRuns)
+ start_time_ = base::TimeTicks::HighResNow();
+
+ if (!start_time_.is_null() && (num_draws_ % kTimeCheckInterval) == 0) {
+ base::TimeDelta elapsed = base::TimeTicks::HighResNow() - start_time_;
+ if (elapsed >= base::TimeDelta::FromMilliseconds(kTimeLimitMillis)) {
+ elapsed_ = elapsed;
+ EndTest();
+ return;
+ }
+ }
+ if (!animation_driven_drawing_)
+ impl->SetNeedsRedraw();
+ if (full_damage_each_frame_)
+ impl->SetFullRootLayerDamage();
+ }
+
+ virtual void BuildTree() {}
+
+ virtual void AfterTest() OVERRIDE {
+ num_draws_ -= kWarmupRuns;
+
+ // Format matches chrome/test/perf/perf_test.h:PrintResult
+ printf("*RESULT %s: frames: %d, %.2f ms/frame\n",
+ test_name_.c_str(),
+ num_draws_,
+ elapsed_.InMillisecondsF() / num_draws_);
+ if (measure_commit_cost_) {
+ printf("*RESULT %s: commits: %d, %.2f ms/commit\n",
+ test_name_.c_str(),
+ num_commits_,
+ total_commit_time_.InMillisecondsF() / num_commits_);
+ }
+ }
+
+ protected:
+ base::TimeTicks start_time_;
+ int num_draws_;
+ int num_commits_;
+ std::string test_name_;
+ base::TimeDelta elapsed_;
+ FakeContentLayerClient fake_content_layer_client_;
+ bool full_damage_each_frame_;
+ bool animation_driven_drawing_;
+
+ bool measure_commit_cost_;
+ base::TimeTicks commit_start_time_;
+ base::TimeDelta total_commit_time_;
+};
+
+
+class LayerTreeHostPerfTestJsonReader : public LayerTreeHostPerfTest {
+ public:
+ LayerTreeHostPerfTestJsonReader()
+ : LayerTreeHostPerfTest() {
+ }
+
+ void ReadTestFile(std::string name) {
+ test_name_ = name;
+ base::FilePath test_data_dir;
+ ASSERT_TRUE(PathService::Get(cc::DIR_TEST_DATA, &test_data_dir));
+ base::FilePath json_file = test_data_dir.AppendASCII(name + ".json");
+ ASSERT_TRUE(file_util::ReadFileToString(json_file, &json_));
+ }
+
+ virtual void BuildTree() OVERRIDE {
+ gfx::Size viewport = gfx::Size(720, 1038);
+ layer_tree_host()->SetViewportSize(viewport);
+ scoped_refptr<Layer> root = ParseTreeFromJson(json_,
+ &fake_content_layer_client_);
+ ASSERT_TRUE(root.get());
+ layer_tree_host()->SetRootLayer(root);
+ }
+
+ private:
+ std::string json_;
+};
+
+// Simulates a tab switcher scene with two stacks of 10 tabs each.
+TEST_F(LayerTreeHostPerfTestJsonReader, TenTenSingleThread) {
+ ReadTestFile("10_10_layer_tree");
+ RunTest(false, false, false);
+}
+
+// Simulates a tab switcher scene with two stacks of 10 tabs each.
+TEST_F(LayerTreeHostPerfTestJsonReader,
+ TenTenSingleThread_FullDamageEachFrame) {
+ full_damage_each_frame_ = true;
+ ReadTestFile("10_10_layer_tree");
+ RunTest(false, false, false);
+}
+
+// Invalidates a leaf layer in the tree on the main thread after every commit.
+class LayerTreeHostPerfTestLeafInvalidates
+ : public LayerTreeHostPerfTestJsonReader {
+ public:
+ virtual void BuildTree() OVERRIDE {
+ LayerTreeHostPerfTestJsonReader::BuildTree();
+
+ // Find a leaf layer.
+ for (layer_to_invalidate_ = layer_tree_host()->root_layer();
+ layer_to_invalidate_->children().size();
+ layer_to_invalidate_ = layer_to_invalidate_->children()[0]) {}
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ if (TestEnded())
+ return;
+
+ static bool flip = true;
+ layer_to_invalidate_->SetOpacity(flip ? 1.f : 0.5f);
+ flip = !flip;
+ }
+
+ protected:
+ Layer* layer_to_invalidate_;
+};
+
+// Simulates a tab switcher scene with two stacks of 10 tabs each. Invalidate a
+// property on a leaf layer in the tree every commit.
+TEST_F(LayerTreeHostPerfTestLeafInvalidates, TenTenSingleThread) {
+ ReadTestFile("10_10_layer_tree");
+ RunTest(false, false, false);
+}
+
+// Simulates main-thread scrolling on each frame.
+class ScrollingLayerTreePerfTest : public LayerTreeHostPerfTestJsonReader {
+ public:
+ ScrollingLayerTreePerfTest()
+ : LayerTreeHostPerfTestJsonReader() {
+ }
+
+ virtual void BuildTree() OVERRIDE {
+ LayerTreeHostPerfTestJsonReader::BuildTree();
+ scrollable_ = layer_tree_host()->root_layer()->children()[1];
+ ASSERT_TRUE(scrollable_.get());
+ }
+
+ virtual void Layout() OVERRIDE {
+ static const gfx::Vector2d delta = gfx::Vector2d(0, 10);
+ scrollable_->SetScrollOffset(scrollable_->scroll_offset() + delta);
+ }
+
+ private:
+ scoped_refptr<Layer> scrollable_;
+};
+
+TEST_F(ScrollingLayerTreePerfTest, LongScrollablePage) {
+ ReadTestFile("long_scrollable_page");
+ RunTest(false, false, false);
+}
+
+class ImplSidePaintingPerfTest : public LayerTreeHostPerfTestJsonReader {
+ protected:
+ // Run test with impl-side painting.
+ void RunTestWithImplSidePainting() { RunTest(true, false, true); }
+};
+
+// Simulates a page with several large, transformed and animated layers.
+TEST_F(ImplSidePaintingPerfTest, HeavyPage) {
+ animation_driven_drawing_ = true;
+ measure_commit_cost_ = true;
+ ReadTestFile("heavy_layer_tree");
+ RunTestWithImplSidePainting();
+}
+
+class PageScaleImplSidePaintingPerfTest : public ImplSidePaintingPerfTest {
+ public:
+ PageScaleImplSidePaintingPerfTest()
+ : max_scale_(16.f), min_scale_(1.f / max_scale_) {}
+
+ virtual void SetupTree() OVERRIDE {
+ layer_tree_host()->SetPageScaleFactorAndLimits(1.f, min_scale_, max_scale_);
+ }
+
+ virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta,
+ float scale_delta) OVERRIDE {
+ float page_scale_factor = layer_tree_host()->page_scale_factor();
+ page_scale_factor *= scale_delta;
+ layer_tree_host()->SetPageScaleFactorAndLimits(
+ page_scale_factor, min_scale_, max_scale_);
+ }
+
+ virtual void AnimateLayers(LayerTreeHostImpl* host_impl,
+ base::TimeTicks monotonic_time) OVERRIDE {
+ if (!host_impl->pinch_gesture_active()) {
+ host_impl->PinchGestureBegin();
+ start_time_ = monotonic_time;
+ }
+ gfx::Point anchor(200, 200);
+
+ float seconds = (monotonic_time - start_time_).InSecondsF();
+
+ // Every half second, zoom from min scale to max scale.
+ float interval = 0.5f;
+
+ // Start time in the middle of the interval when zoom = 1.
+ seconds += interval / 2.f;
+
+ // Stack two ranges together to go up from min to max and down from
+ // max to min in the next so as not to have a zoom discrepancy.
+ float time_in_two_intervals = fmod(seconds, 2.f * interval) / interval;
+
+ // Map everything to go from min to max between 0 and 1.
+ float time_in_one_interval =
+ time_in_two_intervals > 1.f ? 2.f - time_in_two_intervals
+ : time_in_two_intervals;
+ // Normalize time to -1..1.
+ float normalized = 2.f * time_in_one_interval - 1.f;
+ float scale_factor = std::abs(normalized) * (max_scale_ - 1.f) + 1.f;
+ float total_scale = normalized < 0.f ? 1.f / scale_factor : scale_factor;
+
+ float desired_delta =
+ total_scale / host_impl->active_tree()->total_page_scale_factor();
+ host_impl->PinchGestureUpdate(desired_delta, anchor);
+ }
+
+ private:
+ float max_scale_;
+ float min_scale_;
+ base::TimeTicks start_time_;
+};
+
+TEST_F(PageScaleImplSidePaintingPerfTest, HeavyPage) {
+ measure_commit_cost_ = true;
+ ReadTestFile("heavy_layer_tree");
+ RunTestWithImplSidePainting();
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/trees/layer_tree_host_pixeltest_filters.cc b/chromium/cc/trees/layer_tree_host_pixeltest_filters.cc
new file mode 100644
index 00000000000..553e66e8dba
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_host_pixeltest_filters.cc
@@ -0,0 +1,154 @@
+// Copyright 2013 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 "build/build_config.h"
+#include "cc/layers/solid_color_layer.h"
+#include "cc/test/layer_tree_pixel_test.h"
+#include "cc/test/pixel_comparator.h"
+
+#if !defined(OS_ANDROID)
+
+namespace cc {
+namespace {
+
+class LayerTreeHostFiltersPixelTest : public LayerTreePixelTest {};
+
+TEST_F(LayerTreeHostFiltersPixelTest, BackgroundFilterBlur) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ // The green box is entirely behind a layer with background blur, so it
+ // should appear blurred on its edges.
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayer(
+ gfx::Rect(50, 50, 100, 100), kCSSGreen);
+ scoped_refptr<SolidColorLayer> blur = CreateSolidColorLayer(
+ gfx::Rect(30, 30, 140, 140), SK_ColorTRANSPARENT);
+ background->AddChild(green);
+ background->AddChild(blur);
+
+ FilterOperations filters;
+ filters.Append(FilterOperation::CreateBlurFilter(2.f));
+ blur->SetBackgroundFilters(filters);
+
+#if defined(OS_WIN)
+ // Windows has 436 pixels off by 1: crbug.com/259915
+ float percentage_pixels_large_error = 1.09f; // 436px / (200*200)
+ float percentage_pixels_small_error = 0.0f;
+ float average_error_allowed_in_bad_pixels = 1.f;
+ int large_error_allowed = 1;
+ int small_error_allowed = 0;
+ pixel_comparator_.reset(new FuzzyPixelComparator(
+ true, // discard_alpha
+ percentage_pixels_large_error,
+ percentage_pixels_small_error,
+ average_error_allowed_in_bad_pixels,
+ large_error_allowed,
+ small_error_allowed));
+#endif
+
+ RunPixelTest(GL_WITH_BITMAP,
+ background,
+ base::FilePath(FILE_PATH_LITERAL("background_filter_blur.png")));
+}
+
+TEST_F(LayerTreeHostFiltersPixelTest, DISABLED_BackgroundFilterBlurOutsets) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ // The green border is outside the layer with background blur, but the
+ // background blur should use pixels from outside its layer borders, up to the
+ // radius of the blur effect. So the border should be blurred underneath the
+ // top layer causing the green to bleed under the transparent layer, but not
+ // in the 1px region between the transparent layer and the green border.
+ scoped_refptr<SolidColorLayer> green_border = CreateSolidColorLayerWithBorder(
+ gfx::Rect(1, 1, 198, 198), SK_ColorWHITE, 10, kCSSGreen);
+ scoped_refptr<SolidColorLayer> blur = CreateSolidColorLayer(
+ gfx::Rect(12, 12, 176, 176), SK_ColorTRANSPARENT);
+ background->AddChild(green_border);
+ background->AddChild(blur);
+
+ FilterOperations filters;
+ filters.Append(FilterOperation::CreateBlurFilter(5.f));
+ blur->SetBackgroundFilters(filters);
+
+#if defined(OS_WIN)
+ // Windows has 2250 pixels off by at most 2: crbug.com/259922
+ float percentage_pixels_large_error = 5.625f; // 2250px / (200*200)
+ float percentage_pixels_small_error = 0.0f;
+ float average_error_allowed_in_bad_pixels = 1.f;
+ int large_error_allowed = 2;
+ int small_error_allowed = 0;
+ pixel_comparator_.reset(new FuzzyPixelComparator(
+ true, // discard_alpha
+ percentage_pixels_large_error,
+ percentage_pixels_small_error,
+ average_error_allowed_in_bad_pixels,
+ large_error_allowed,
+ small_error_allowed));
+#endif
+
+ RunPixelTest(GL_WITH_BITMAP,
+ background,
+ base::FilePath(FILE_PATH_LITERAL(
+ "background_filter_blur_outsets.png")));
+}
+
+TEST_F(LayerTreeHostFiltersPixelTest, BackgroundFilterBlurOffAxis) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ // This verifies that the perspective of the clear layer (with black border)
+ // does not influence the blending of the green box behind it. Also verifies
+ // that the blur is correctly clipped inside the transformed clear layer.
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayer(
+ gfx::Rect(50, 50, 100, 100), kCSSGreen);
+ scoped_refptr<SolidColorLayer> blur = CreateSolidColorLayerWithBorder(
+ gfx::Rect(30, 30, 120, 120), SK_ColorTRANSPARENT, 1, SK_ColorBLACK);
+ background->AddChild(green);
+ background->AddChild(blur);
+
+ background->SetPreserves3d(true);
+ gfx::Transform background_transform;
+ background_transform.ApplyPerspectiveDepth(200.0);
+ background->SetTransform(background_transform);
+
+ blur->SetPreserves3d(true);
+ gfx::Transform blur_transform;
+ blur_transform.Translate(55.0, 65.0);
+ blur_transform.RotateAboutXAxis(85.0);
+ blur_transform.RotateAboutYAxis(180.0);
+ blur_transform.RotateAboutZAxis(20.0);
+ blur_transform.Translate(-60.0, -60.0);
+ blur->SetTransform(blur_transform);
+
+ FilterOperations filters;
+ filters.Append(FilterOperation::CreateBlurFilter(2.f));
+ blur->SetBackgroundFilters(filters);
+
+#if defined(OS_WIN)
+ // Windows has 151 pixels off by at most 2: crbug.com/225027
+ float percentage_pixels_large_error = 0.3775f; // 151px / (200*200)
+ float percentage_pixels_small_error = 0.0f;
+ float average_error_allowed_in_bad_pixels = 1.f;
+ int large_error_allowed = 2;
+ int small_error_allowed = 0;
+ pixel_comparator_.reset(new FuzzyPixelComparator(
+ true, // discard_alpha
+ percentage_pixels_large_error,
+ percentage_pixels_small_error,
+ average_error_allowed_in_bad_pixels,
+ large_error_allowed,
+ small_error_allowed));
+#endif
+
+ RunPixelTest(GL_WITH_BITMAP,
+ background,
+ base::FilePath(FILE_PATH_LITERAL(
+ "background_filter_blur_off_axis.png")));
+}
+
+} // namespace
+} // namespace cc
+
+#endif // OS_ANDROID
diff --git a/chromium/cc/trees/layer_tree_host_pixeltest_masks.cc b/chromium/cc/trees/layer_tree_host_pixeltest_masks.cc
new file mode 100644
index 00000000000..90570ec0b64
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_host_pixeltest_masks.cc
@@ -0,0 +1,272 @@
+// Copyright 2013 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 "build/build_config.h"
+#include "cc/layers/content_layer.h"
+#include "cc/layers/content_layer_client.h"
+#include "cc/layers/image_layer.h"
+#include "cc/layers/solid_color_layer.h"
+#include "cc/test/layer_tree_pixel_test.h"
+#include "cc/test/pixel_comparator.h"
+
+#if !defined(OS_ANDROID)
+
+namespace cc {
+namespace {
+
+class LayerTreeHostMasksPixelTest : public LayerTreePixelTest {};
+
+class MaskContentLayerClient : public cc::ContentLayerClient {
+ public:
+ MaskContentLayerClient() {}
+ virtual ~MaskContentLayerClient() {}
+
+ virtual void DidChangeLayerCanUseLCDText() OVERRIDE {}
+
+ virtual void PaintContents(SkCanvas* canvas,
+ gfx::Rect rect,
+ gfx::RectF* opaque_rect) OVERRIDE {
+ SkPaint paint;
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setStrokeWidth(SkIntToScalar(2));
+ paint.setColor(SK_ColorWHITE);
+
+ canvas->clear(SK_ColorTRANSPARENT);
+ while (!rect.IsEmpty()) {
+ rect.Inset(3, 3, 2, 2);
+ canvas->drawRect(
+ SkRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height()),
+ paint);
+ rect.Inset(3, 3, 2, 2);
+ }
+ }
+};
+
+TEST_F(LayerTreeHostMasksPixelTest, MaskOfLayer) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayerWithBorder(
+ gfx::Rect(50, 50, 100, 100), kCSSGreen, 1, SK_ColorBLACK);
+ background->AddChild(green);
+
+ MaskContentLayerClient client;
+ scoped_refptr<ContentLayer> mask = ContentLayer::Create(&client);
+ mask->SetBounds(gfx::Size(100, 100));
+ mask->SetIsDrawable(true);
+ mask->SetIsMask(true);
+ green->SetMaskLayer(mask.get());
+
+ RunPixelTest(GL_WITH_BITMAP,
+ background,
+ base::FilePath(FILE_PATH_LITERAL("mask_of_layer.png")));
+}
+
+TEST_F(LayerTreeHostMasksPixelTest, ImageMaskOfLayer) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ scoped_refptr<ImageLayer> mask = ImageLayer::Create();
+ mask->SetIsDrawable(true);
+ mask->SetIsMask(true);
+ mask->SetBounds(gfx::Size(100, 100));
+
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, 400, 400);
+ bitmap.allocPixels();
+ SkCanvas canvas(bitmap);
+ canvas.scale(SkIntToScalar(4), SkIntToScalar(4));
+ MaskContentLayerClient client;
+ client.PaintContents(&canvas,
+ gfx::Rect(100, 100),
+ NULL);
+ mask->SetBitmap(bitmap);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayerWithBorder(
+ gfx::Rect(50, 50, 100, 100), kCSSGreen, 1, SK_ColorBLACK);
+ green->SetMaskLayer(mask.get());
+ background->AddChild(green);
+
+ RunPixelTest(GL_WITH_BITMAP,
+ background,
+ base::FilePath(FILE_PATH_LITERAL("image_mask_of_layer.png")));
+}
+
+TEST_F(LayerTreeHostMasksPixelTest, MaskOfClippedLayer) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ // Clip to the top half of the green layer.
+ scoped_refptr<Layer> clip = Layer::Create();
+ clip->SetAnchorPoint(gfx::PointF(0.f, 0.f));
+ clip->SetPosition(gfx::Point(0, 0));
+ clip->SetBounds(gfx::Size(200, 100));
+ clip->SetMasksToBounds(true);
+ background->AddChild(clip);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayerWithBorder(
+ gfx::Rect(50, 50, 100, 100), kCSSGreen, 1, SK_ColorBLACK);
+ clip->AddChild(green);
+
+ MaskContentLayerClient client;
+ scoped_refptr<ContentLayer> mask = ContentLayer::Create(&client);
+ mask->SetBounds(gfx::Size(100, 100));
+ mask->SetIsDrawable(true);
+ mask->SetIsMask(true);
+ green->SetMaskLayer(mask.get());
+
+ RunPixelTest(GL_WITH_BITMAP,
+ background,
+ base::FilePath(FILE_PATH_LITERAL("mask_of_clipped_layer.png")));
+}
+
+TEST_F(LayerTreeHostMasksPixelTest, MaskWithReplica) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ MaskContentLayerClient client;
+ scoped_refptr<ContentLayer> mask = ContentLayer::Create(&client);
+ mask->SetBounds(gfx::Size(100, 100));
+ mask->SetIsDrawable(true);
+ mask->SetIsMask(true);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayerWithBorder(
+ gfx::Rect(0, 0, 100, 100), kCSSGreen, 1, SK_ColorBLACK);
+ background->AddChild(green);
+ green->SetMaskLayer(mask.get());
+
+ gfx::Transform replica_transform;
+ replica_transform.Rotate(-90.0);
+
+ scoped_refptr<Layer> replica = Layer::Create();
+ replica->SetAnchorPoint(gfx::PointF(0.5f, 0.5f));
+ replica->SetPosition(gfx::Point(100, 100));
+ replica->SetTransform(replica_transform);
+ green->SetReplicaLayer(replica.get());
+
+ RunPixelTest(GL_WITH_BITMAP,
+ background,
+ base::FilePath(FILE_PATH_LITERAL("mask_with_replica.png")));
+}
+
+TEST_F(LayerTreeHostMasksPixelTest, MaskWithReplicaOfClippedLayer) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ MaskContentLayerClient client;
+ scoped_refptr<ContentLayer> mask = ContentLayer::Create(&client);
+ mask->SetBounds(gfx::Size(100, 100));
+ mask->SetIsDrawable(true);
+ mask->SetIsMask(true);
+
+ // Clip to the bottom half of the green layer, and the left half of the
+ // replica.
+ scoped_refptr<Layer> clip = Layer::Create();
+ clip->SetAnchorPoint(gfx::PointF(0.f, 0.f));
+ clip->SetPosition(gfx::Point(0, 50));
+ clip->SetBounds(gfx::Size(150, 150));
+ clip->SetMasksToBounds(true);
+ background->AddChild(clip);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayerWithBorder(
+ gfx::Rect(0, -50, 100, 100), kCSSGreen, 1, SK_ColorBLACK);
+ clip->AddChild(green);
+ green->SetMaskLayer(mask.get());
+
+ gfx::Transform replica_transform;
+ replica_transform.Rotate(-90.0);
+
+ scoped_refptr<Layer> replica = Layer::Create();
+ replica->SetAnchorPoint(gfx::PointF(0.5f, 0.5f));
+ replica->SetPosition(gfx::Point(100, 100));
+ replica->SetTransform(replica_transform);
+ green->SetReplicaLayer(replica.get());
+
+ RunPixelTest(GL_WITH_BITMAP,
+ background,
+ base::FilePath(FILE_PATH_LITERAL(
+ "mask_with_replica_of_clipped_layer.png")));
+}
+
+TEST_F(LayerTreeHostMasksPixelTest, MaskOfReplica) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ MaskContentLayerClient client;
+ scoped_refptr<ContentLayer> mask = ContentLayer::Create(&client);
+ mask->SetBounds(gfx::Size(100, 100));
+ mask->SetIsDrawable(true);
+ mask->SetIsMask(true);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayerWithBorder(
+ gfx::Rect(50, 0, 100, 100), kCSSGreen, 1, SK_ColorBLACK);
+ background->AddChild(green);
+
+ scoped_refptr<SolidColorLayer> orange = CreateSolidColorLayer(
+ gfx::Rect(-50, 50, 50, 50), kCSSOrange);
+ green->AddChild(orange);
+
+ gfx::Transform replica_transform;
+ replica_transform.Rotate(180.0);
+ replica_transform.Translate(100.0, 0.0);
+
+ scoped_refptr<Layer> replica = Layer::Create();
+ replica->SetAnchorPoint(gfx::PointF(1.f, 1.f));
+ replica->SetPosition(gfx::Point());
+ replica->SetTransform(replica_transform);
+ replica->SetMaskLayer(mask.get());
+ green->SetReplicaLayer(replica.get());
+
+ RunPixelTest(GL_WITH_BITMAP,
+ background,
+ base::FilePath(FILE_PATH_LITERAL("mask_of_replica.png")));
+}
+
+TEST_F(LayerTreeHostMasksPixelTest, MaskOfReplicaOfClippedLayer) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ MaskContentLayerClient client;
+ scoped_refptr<ContentLayer> mask = ContentLayer::Create(&client);
+ mask->SetBounds(gfx::Size(100, 100));
+ mask->SetIsDrawable(true);
+ mask->SetIsMask(true);
+
+ // Clip to the bottom 3/4 of the green layer, and the top 3/4 of the replica.
+ scoped_refptr<Layer> clip = Layer::Create();
+ clip->SetAnchorPoint(gfx::PointF(0.f, 0.f));
+ clip->SetPosition(gfx::Point(0, 25));
+ clip->SetBounds(gfx::Size(200, 150));
+ clip->SetMasksToBounds(true);
+ background->AddChild(clip);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayerWithBorder(
+ gfx::Rect(50, -25, 100, 100), kCSSGreen, 1, SK_ColorBLACK);
+ clip->AddChild(green);
+
+ scoped_refptr<SolidColorLayer> orange = CreateSolidColorLayer(
+ gfx::Rect(-50, 50, 50, 50), kCSSOrange);
+ green->AddChild(orange);
+
+ gfx::Transform replica_transform;
+ replica_transform.Rotate(180.0);
+ replica_transform.Translate(100.0, 0.0);
+
+ scoped_refptr<Layer> replica = Layer::Create();
+ replica->SetAnchorPoint(gfx::PointF(1.f, 1.f));
+ replica->SetPosition(gfx::Point());
+ replica->SetTransform(replica_transform);
+ replica->SetMaskLayer(mask.get());
+ green->SetReplicaLayer(replica.get());
+
+ RunPixelTest(GL_WITH_BITMAP,
+ background,
+ base::FilePath(FILE_PATH_LITERAL(
+ "mask_of_replica_of_clipped_layer.png")));
+}
+
+} // namespace
+} // namespace cc
+
+#endif // OS_ANDROID
diff --git a/chromium/cc/trees/layer_tree_host_pixeltest_on_demand_raster.cc b/chromium/cc/trees/layer_tree_host_pixeltest_on_demand_raster.cc
new file mode 100644
index 00000000000..a1990b01d5b
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_host_pixeltest_on_demand_raster.cc
@@ -0,0 +1,112 @@
+// Copyright 2013 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 "cc/layers/append_quads_data.h"
+#include "cc/layers/content_layer_client.h"
+#include "cc/layers/picture_layer.h"
+#include "cc/layers/picture_layer_impl.h"
+#include "cc/quads/draw_quad.h"
+#include "cc/test/layer_tree_pixel_test.h"
+#include "cc/test/mock_quad_culler.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/rect_f.h"
+
+#if !defined(OS_ANDROID)
+
+namespace cc {
+namespace {
+
+class LayerTreeHostOnDemandRasterPixelTest : public LayerTreePixelTest {
+ public:
+ virtual void InitializeSettings(LayerTreeSettings* settings) OVERRIDE {
+ settings->impl_side_painting = true;
+ }
+
+ virtual void BeginCommitOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ // Not enough memory available. Enforce on-demand rasterization.
+ impl->SetMemoryPolicy(
+ ManagedMemoryPolicy(1, ManagedMemoryPolicy::CUTOFF_ALLOW_EVERYTHING,
+ 1, ManagedMemoryPolicy::CUTOFF_ALLOW_NOTHING,
+ 1000));
+ impl->SetDiscardBackBufferWhenNotVisible(true);
+ }
+
+ virtual void SwapBuffersOnThread(LayerTreeHostImpl* host_impl,
+ bool result) OVERRIDE {
+ // Find the PictureLayerImpl ask it to append quads to check their material.
+ // The PictureLayerImpl is assumed to be the first child of the root layer
+ // in the active tree.
+ PictureLayerImpl* picture_layer = static_cast<PictureLayerImpl*>(
+ host_impl->active_tree()->root_layer()->child_at(0));
+
+ QuadList quads;
+ SharedQuadStateList shared_states;
+ MockQuadCuller quad_culler(&quads, &shared_states);
+
+ AppendQuadsData data;
+ picture_layer->AppendQuads(&quad_culler, &data);
+
+ for (size_t i = 0; i < quads.size(); ++i)
+ EXPECT_EQ(quads[i]->material, DrawQuad::PICTURE_CONTENT);
+
+ // Triggers pixel readback and ends the test.
+ LayerTreePixelTest::SwapBuffersOnThread(host_impl, result);
+ }
+};
+
+class BlueYellowLayerClient : public ContentLayerClient {
+ public:
+ explicit BlueYellowLayerClient(gfx::Rect layer_rect)
+ : layer_rect_(layer_rect) {}
+
+ virtual void DidChangeLayerCanUseLCDText() OVERRIDE { }
+
+ virtual void PaintContents(SkCanvas* canvas,
+ gfx::Rect clip,
+ gfx::RectF* opaque) OVERRIDE {
+ *opaque = gfx::RectF(layer_rect_.width(), layer_rect_.height());
+
+ SkPaint paint;
+ paint.setColor(SK_ColorBLUE);
+ canvas->drawRect(SkRect::MakeWH(layer_rect_.width(),
+ layer_rect_.height() / 2),
+ paint);
+
+ paint.setColor(SK_ColorYELLOW);
+ canvas->drawRect(
+ SkRect::MakeXYWH(0,
+ layer_rect_.height() / 2,
+ layer_rect_.width(),
+ layer_rect_.height() / 2),
+ paint);
+ }
+
+ private:
+ gfx::Rect layer_rect_;
+};
+
+TEST_F(LayerTreeHostOnDemandRasterPixelTest, RasterPictureLayer) {
+ // Use multiple colors in a single layer to prevent bypassing on-demand
+ // rasterization if a single solid color is detected in picture analysis.
+ gfx::Rect layer_rect(200, 200);
+ BlueYellowLayerClient client(layer_rect);
+ scoped_refptr<PictureLayer> layer = PictureLayer::Create(&client);
+
+ layer->SetIsDrawable(true);
+ layer->SetAnchorPoint(gfx::PointF());
+ layer->SetBounds(layer_rect.size());
+ layer->SetPosition(layer_rect.origin());
+
+ RunPixelTest(GL_WITH_BITMAP,
+ layer,
+ base::FilePath(FILE_PATH_LITERAL("blue_yellow.png")));
+}
+
+} // namespace
+} // namespace cc
+
+#endif // OS_ANDROID
diff --git a/chromium/cc/trees/layer_tree_host_pixeltest_readback.cc b/chromium/cc/trees/layer_tree_host_pixeltest_readback.cc
new file mode 100644
index 00000000000..a9bc6ceff1b
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_host_pixeltest_readback.cc
@@ -0,0 +1,886 @@
+// Copyright 2013 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 "build/build_config.h"
+#include "cc/layers/content_layer.h"
+#include "cc/layers/solid_color_layer.h"
+#include "cc/layers/texture_layer.h"
+#include "cc/output/copy_output_request.h"
+#include "cc/output/copy_output_result.h"
+#include "cc/test/layer_tree_pixel_test.h"
+#include "cc/test/paths.h"
+#include "cc/test/solid_color_content_layer_client.h"
+#include "cc/trees/layer_tree_impl.h"
+
+#if !defined(OS_ANDROID)
+
+namespace cc {
+namespace {
+
+class LayerTreeHostReadbackPixelTest : public LayerTreePixelTest {
+ protected:
+ virtual scoped_ptr<CopyOutputRequest> CreateCopyOutputRequest() OVERRIDE {
+ scoped_ptr<CopyOutputRequest> request;
+
+ switch (test_type_) {
+ case GL_WITH_BITMAP:
+ case SOFTWARE_WITH_BITMAP:
+ request = CopyOutputRequest::CreateBitmapRequest(
+ base::Bind(&LayerTreeHostReadbackPixelTest::ReadbackResultAsBitmap,
+ base::Unretained(this)));
+ break;
+ case SOFTWARE_WITH_DEFAULT:
+ request = CopyOutputRequest::CreateRequest(
+ base::Bind(&LayerTreeHostReadbackPixelTest::ReadbackResultAsBitmap,
+ base::Unretained(this)));
+ break;
+ case GL_WITH_DEFAULT:
+ request = CopyOutputRequest::CreateRequest(
+ base::Bind(&LayerTreeHostReadbackPixelTest::ReadbackResultAsTexture,
+ base::Unretained(this)));
+ break;
+ }
+
+ if (!copy_subrect_.IsEmpty())
+ request->set_area(copy_subrect_);
+ return request.Pass();
+ }
+
+ void ReadbackResultAsBitmap(scoped_ptr<CopyOutputResult> result) {
+ EXPECT_TRUE(proxy()->IsMainThread());
+ EXPECT_TRUE(result->HasBitmap());
+ result_bitmap_ = result->TakeBitmap().Pass();
+ EndTest();
+ }
+
+ void ReadbackResultAsTexture(scoped_ptr<CopyOutputResult> result) {
+ EXPECT_TRUE(proxy()->IsMainThread());
+ EXPECT_TRUE(result->HasTexture());
+
+ scoped_ptr<TextureMailbox> texture_mailbox = result->TakeTexture().Pass();
+ EXPECT_TRUE(texture_mailbox->IsValid());
+ EXPECT_TRUE(texture_mailbox->IsTexture());
+
+ scoped_ptr<SkBitmap> bitmap =
+ CopyTextureMailboxToBitmap(result->size(), *texture_mailbox);
+ texture_mailbox->RunReleaseCallback(0, false);
+
+ ReadbackResultAsBitmap(CopyOutputResult::CreateBitmapResult(bitmap.Pass()));
+ }
+
+ gfx::Rect copy_subrect_;
+};
+
+TEST_F(LayerTreeHostReadbackPixelTest, ReadbackRootLayer_Software) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorGREEN);
+ background->AddChild(green);
+
+ RunPixelTest(SOFTWARE_WITH_DEFAULT,
+ background,
+ base::FilePath(FILE_PATH_LITERAL(
+ "green.png")));
+}
+
+TEST_F(LayerTreeHostReadbackPixelTest, ReadbackRootLayer_Software_Bitmap) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorGREEN);
+ background->AddChild(green);
+
+ RunPixelTest(SOFTWARE_WITH_BITMAP,
+ background,
+ base::FilePath(FILE_PATH_LITERAL(
+ "green.png")));
+}
+
+TEST_F(LayerTreeHostReadbackPixelTest, ReadbackRootLayer_GL_Bitmap) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorGREEN);
+ background->AddChild(green);
+
+ RunPixelTest(GL_WITH_BITMAP,
+ background,
+ base::FilePath(FILE_PATH_LITERAL(
+ "green.png")));
+}
+
+TEST_F(LayerTreeHostReadbackPixelTest, ReadbackRootLayer_GL) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorGREEN);
+ background->AddChild(green);
+
+ RunPixelTest(GL_WITH_DEFAULT,
+ background,
+ base::FilePath(FILE_PATH_LITERAL(
+ "green.png")));
+}
+
+TEST_F(LayerTreeHostReadbackPixelTest,
+ ReadbackRootLayerWithChild_Software) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorGREEN);
+ background->AddChild(green);
+
+ scoped_refptr<SolidColorLayer> blue = CreateSolidColorLayer(
+ gfx::Rect(150, 150, 50, 50), SK_ColorBLUE);
+ green->AddChild(blue);
+
+ RunPixelTest(SOFTWARE_WITH_DEFAULT,
+ background,
+ base::FilePath(FILE_PATH_LITERAL(
+ "green_with_blue_corner.png")));
+}
+
+TEST_F(LayerTreeHostReadbackPixelTest, ReadbackRootLayerWithChild_GL_Bitmap) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorGREEN);
+ background->AddChild(green);
+
+ scoped_refptr<SolidColorLayer> blue = CreateSolidColorLayer(
+ gfx::Rect(150, 150, 50, 50), SK_ColorBLUE);
+ green->AddChild(blue);
+
+ RunPixelTest(GL_WITH_BITMAP,
+ background,
+ base::FilePath(FILE_PATH_LITERAL(
+ "green_with_blue_corner.png")));
+}
+
+TEST_F(LayerTreeHostReadbackPixelTest, ReadbackRootLayerWithChild_GL) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorGREEN);
+ background->AddChild(green);
+
+ scoped_refptr<SolidColorLayer> blue = CreateSolidColorLayer(
+ gfx::Rect(150, 150, 50, 50), SK_ColorBLUE);
+ green->AddChild(blue);
+
+ RunPixelTest(GL_WITH_DEFAULT,
+ background,
+ base::FilePath(FILE_PATH_LITERAL(
+ "green_with_blue_corner.png")));
+}
+
+TEST_F(LayerTreeHostReadbackPixelTest, ReadbackNonRootLayer_Software) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorGREEN);
+ background->AddChild(green);
+
+ RunPixelTestWithReadbackTarget(SOFTWARE_WITH_DEFAULT,
+ background,
+ green.get(),
+ base::FilePath(FILE_PATH_LITERAL(
+ "green.png")));
+}
+
+TEST_F(LayerTreeHostReadbackPixelTest, ReadbackNonRootLayer_GL_Bitmap) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorGREEN);
+ background->AddChild(green);
+
+ RunPixelTestWithReadbackTarget(GL_WITH_BITMAP,
+ background,
+ green.get(),
+ base::FilePath(FILE_PATH_LITERAL(
+ "green.png")));
+}
+
+TEST_F(LayerTreeHostReadbackPixelTest, ReadbackNonRootLayer_GL) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorGREEN);
+ background->AddChild(green);
+
+ RunPixelTestWithReadbackTarget(GL_WITH_DEFAULT,
+ background,
+ green.get(),
+ base::FilePath(FILE_PATH_LITERAL(
+ "green.png")));
+}
+
+TEST_F(LayerTreeHostReadbackPixelTest,
+ ReadbackSmallNonRootLayer_Software) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayer(
+ gfx::Rect(100, 100, 100, 100), SK_ColorGREEN);
+ background->AddChild(green);
+
+ RunPixelTestWithReadbackTarget(SOFTWARE_WITH_DEFAULT,
+ background,
+ green.get(),
+ base::FilePath(FILE_PATH_LITERAL(
+ "green_small.png")));
+}
+
+TEST_F(LayerTreeHostReadbackPixelTest, ReadbackSmallNonRootLayer_GL_Bitmap) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayer(
+ gfx::Rect(100, 100, 100, 100), SK_ColorGREEN);
+ background->AddChild(green);
+
+ RunPixelTestWithReadbackTarget(GL_WITH_BITMAP,
+ background,
+ green.get(),
+ base::FilePath(FILE_PATH_LITERAL(
+ "green_small.png")));
+}
+
+TEST_F(LayerTreeHostReadbackPixelTest, ReadbackSmallNonRootLayer_GL) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayer(
+ gfx::Rect(100, 100, 100, 100), SK_ColorGREEN);
+ background->AddChild(green);
+
+ RunPixelTestWithReadbackTarget(GL_WITH_DEFAULT,
+ background,
+ green.get(),
+ base::FilePath(FILE_PATH_LITERAL(
+ "green_small.png")));
+}
+
+TEST_F(LayerTreeHostReadbackPixelTest,
+ ReadbackSmallNonRootLayerWithChild_Software) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayer(
+ gfx::Rect(100, 100, 100, 100), SK_ColorGREEN);
+ background->AddChild(green);
+
+ scoped_refptr<SolidColorLayer> blue = CreateSolidColorLayer(
+ gfx::Rect(50, 50, 50, 50), SK_ColorBLUE);
+ green->AddChild(blue);
+
+ RunPixelTestWithReadbackTarget(SOFTWARE_WITH_DEFAULT,
+ background,
+ green.get(),
+ base::FilePath(FILE_PATH_LITERAL(
+ "green_small_with_blue_corner.png")));
+}
+
+TEST_F(LayerTreeHostReadbackPixelTest,
+ ReadbackSmallNonRootLayerWithChild_GL_Bitmap) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayer(
+ gfx::Rect(100, 100, 100, 100), SK_ColorGREEN);
+ background->AddChild(green);
+
+ scoped_refptr<SolidColorLayer> blue = CreateSolidColorLayer(
+ gfx::Rect(50, 50, 50, 50), SK_ColorBLUE);
+ green->AddChild(blue);
+
+ RunPixelTestWithReadbackTarget(GL_WITH_BITMAP,
+ background,
+ green.get(),
+ base::FilePath(FILE_PATH_LITERAL(
+ "green_small_with_blue_corner.png")));
+}
+
+TEST_F(LayerTreeHostReadbackPixelTest, ReadbackSmallNonRootLayerWithChild_GL) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayer(
+ gfx::Rect(100, 100, 100, 100), SK_ColorGREEN);
+ background->AddChild(green);
+
+ scoped_refptr<SolidColorLayer> blue = CreateSolidColorLayer(
+ gfx::Rect(50, 50, 50, 50), SK_ColorBLUE);
+ green->AddChild(blue);
+
+ RunPixelTestWithReadbackTarget(GL_WITH_DEFAULT,
+ background,
+ green.get(),
+ base::FilePath(FILE_PATH_LITERAL(
+ "green_small_with_blue_corner.png")));
+}
+
+TEST_F(LayerTreeHostReadbackPixelTest, ReadbackSubrect_Software) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorGREEN);
+ background->AddChild(green);
+
+ scoped_refptr<SolidColorLayer> blue = CreateSolidColorLayer(
+ gfx::Rect(100, 100, 50, 50), SK_ColorBLUE);
+ green->AddChild(blue);
+
+ // Grab the middle of the root layer.
+ copy_subrect_ = gfx::Rect(50, 50, 100, 100);
+
+ RunPixelTest(SOFTWARE_WITH_DEFAULT,
+ background,
+ base::FilePath(FILE_PATH_LITERAL(
+ "green_small_with_blue_corner.png")));
+}
+
+TEST_F(LayerTreeHostReadbackPixelTest, ReadbackSubrect_GL_Bitmap) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorGREEN);
+ background->AddChild(green);
+
+ scoped_refptr<SolidColorLayer> blue = CreateSolidColorLayer(
+ gfx::Rect(100, 100, 50, 50), SK_ColorBLUE);
+ green->AddChild(blue);
+
+ // Grab the middle of the root layer.
+ copy_subrect_ = gfx::Rect(50, 50, 100, 100);
+
+ RunPixelTest(GL_WITH_BITMAP,
+ background,
+ base::FilePath(FILE_PATH_LITERAL(
+ "green_small_with_blue_corner.png")));
+}
+
+TEST_F(LayerTreeHostReadbackPixelTest, ReadbackSubrect_GL) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorGREEN);
+ background->AddChild(green);
+
+ scoped_refptr<SolidColorLayer> blue = CreateSolidColorLayer(
+ gfx::Rect(100, 100, 50, 50), SK_ColorBLUE);
+ green->AddChild(blue);
+
+ // Grab the middle of the root layer.
+ copy_subrect_ = gfx::Rect(50, 50, 100, 100);
+
+ RunPixelTest(GL_WITH_DEFAULT,
+ background,
+ base::FilePath(FILE_PATH_LITERAL(
+ "green_small_with_blue_corner.png")));
+}
+
+TEST_F(LayerTreeHostReadbackPixelTest, ReadbackNonRootLayerSubrect_Software) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayer(
+ gfx::Rect(25, 25, 150, 150), SK_ColorGREEN);
+ background->AddChild(green);
+
+ scoped_refptr<SolidColorLayer> blue = CreateSolidColorLayer(
+ gfx::Rect(75, 75, 50, 50), SK_ColorBLUE);
+ green->AddChild(blue);
+
+ // Grab the middle of the green layer.
+ copy_subrect_ = gfx::Rect(25, 25, 100, 100);
+
+ RunPixelTestWithReadbackTarget(SOFTWARE_WITH_DEFAULT,
+ background,
+ green.get(),
+ base::FilePath(FILE_PATH_LITERAL(
+ "green_small_with_blue_corner.png")));
+}
+
+TEST_F(LayerTreeHostReadbackPixelTest, ReadbackNonRootLayerSubrect_GL_Bitmap) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayer(
+ gfx::Rect(25, 25, 150, 150), SK_ColorGREEN);
+ background->AddChild(green);
+
+ scoped_refptr<SolidColorLayer> blue = CreateSolidColorLayer(
+ gfx::Rect(75, 75, 50, 50), SK_ColorBLUE);
+ green->AddChild(blue);
+
+ // Grab the middle of the green layer.
+ copy_subrect_ = gfx::Rect(25, 25, 100, 100);
+
+ RunPixelTestWithReadbackTarget(GL_WITH_BITMAP,
+ background,
+ green.get(),
+ base::FilePath(FILE_PATH_LITERAL(
+ "green_small_with_blue_corner.png")));
+}
+
+TEST_F(LayerTreeHostReadbackPixelTest, ReadbackNonRootLayerSubrect_GL) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayer(
+ gfx::Rect(25, 25, 150, 150), SK_ColorGREEN);
+ background->AddChild(green);
+
+ scoped_refptr<SolidColorLayer> blue = CreateSolidColorLayer(
+ gfx::Rect(75, 75, 50, 50), SK_ColorBLUE);
+ green->AddChild(blue);
+
+ // Grab the middle of the green layer.
+ copy_subrect_ = gfx::Rect(25, 25, 100, 100);
+
+ RunPixelTestWithReadbackTarget(GL_WITH_DEFAULT,
+ background,
+ green.get(),
+ base::FilePath(FILE_PATH_LITERAL(
+ "green_small_with_blue_corner.png")));
+}
+
+class LayerTreeHostReadbackDeviceScalePixelTest
+ : public LayerTreeHostReadbackPixelTest {
+ protected:
+ LayerTreeHostReadbackDeviceScalePixelTest()
+ : device_scale_factor_(1.f),
+ white_client_(SK_ColorWHITE),
+ green_client_(SK_ColorGREEN),
+ blue_client_(SK_ColorBLUE) {}
+
+ virtual void InitializeSettings(LayerTreeSettings* settings) OVERRIDE {
+ // Cause the device scale factor to be inherited by contents scales.
+ settings->layer_transforms_should_scale_layer_contents = true;
+ }
+
+ virtual void SetupTree() OVERRIDE {
+ layer_tree_host()->SetDeviceScaleFactor(device_scale_factor_);
+ LayerTreePixelTest::SetupTree();
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ LayerImpl* root_impl = host_impl->active_tree()->root_layer();
+
+ LayerImpl* background_impl = root_impl->children()[0];
+ EXPECT_EQ(device_scale_factor_, background_impl->contents_scale_x());
+ EXPECT_EQ(device_scale_factor_, background_impl->contents_scale_y());
+
+ LayerImpl* green_impl = background_impl->children()[0];
+ EXPECT_EQ(device_scale_factor_, green_impl->contents_scale_x());
+ EXPECT_EQ(device_scale_factor_, green_impl->contents_scale_y());
+
+ LayerImpl* blue_impl = green_impl->children()[0];
+ EXPECT_EQ(device_scale_factor_, blue_impl->contents_scale_x());
+ EXPECT_EQ(device_scale_factor_, blue_impl->contents_scale_y());
+ }
+
+ float device_scale_factor_;
+ SolidColorContentLayerClient white_client_;
+ SolidColorContentLayerClient green_client_;
+ SolidColorContentLayerClient blue_client_;
+};
+
+TEST_F(LayerTreeHostReadbackDeviceScalePixelTest,
+ ReadbackSubrect_Software) {
+ scoped_refptr<ContentLayer> background = ContentLayer::Create(&white_client_);
+ background->SetAnchorPoint(gfx::PointF());
+ background->SetBounds(gfx::Size(100, 100));
+ background->SetIsDrawable(true);
+
+ scoped_refptr<ContentLayer> green = ContentLayer::Create(&green_client_);
+ green->SetAnchorPoint(gfx::PointF());
+ green->SetBounds(gfx::Size(100, 100));
+ green->SetIsDrawable(true);
+ background->AddChild(green);
+
+ scoped_refptr<ContentLayer> blue = ContentLayer::Create(&blue_client_);
+ blue->SetAnchorPoint(gfx::PointF());
+ blue->SetPosition(gfx::Point(50, 50));
+ blue->SetBounds(gfx::Size(25, 25));
+ blue->SetIsDrawable(true);
+ green->AddChild(blue);
+
+ // Grab the middle of the root layer.
+ copy_subrect_ = gfx::Rect(25, 25, 50, 50);
+ device_scale_factor_ = 2.f;
+
+ RunPixelTest(SOFTWARE_WITH_DEFAULT,
+ background,
+ base::FilePath(FILE_PATH_LITERAL(
+ "green_small_with_blue_corner.png")));
+}
+
+TEST_F(LayerTreeHostReadbackDeviceScalePixelTest,
+ ReadbackSubrect_GL) {
+ scoped_refptr<ContentLayer> background = ContentLayer::Create(&white_client_);
+ background->SetAnchorPoint(gfx::PointF());
+ background->SetBounds(gfx::Size(100, 100));
+ background->SetIsDrawable(true);
+
+ scoped_refptr<ContentLayer> green = ContentLayer::Create(&green_client_);
+ green->SetAnchorPoint(gfx::PointF());
+ green->SetBounds(gfx::Size(100, 100));
+ green->SetIsDrawable(true);
+ background->AddChild(green);
+
+ scoped_refptr<ContentLayer> blue = ContentLayer::Create(&blue_client_);
+ blue->SetAnchorPoint(gfx::PointF());
+ blue->SetPosition(gfx::Point(50, 50));
+ blue->SetBounds(gfx::Size(25, 25));
+ blue->SetIsDrawable(true);
+ green->AddChild(blue);
+
+ // Grab the middle of the root layer.
+ copy_subrect_ = gfx::Rect(25, 25, 50, 50);
+ device_scale_factor_ = 2.f;
+
+ RunPixelTest(GL_WITH_DEFAULT,
+ background,
+ base::FilePath(FILE_PATH_LITERAL(
+ "green_small_with_blue_corner.png")));
+}
+
+TEST_F(LayerTreeHostReadbackDeviceScalePixelTest,
+ ReadbackNonRootLayerSubrect_Software) {
+ scoped_refptr<ContentLayer> background = ContentLayer::Create(&white_client_);
+ background->SetAnchorPoint(gfx::PointF());
+ background->SetBounds(gfx::Size(100, 100));
+ background->SetIsDrawable(true);
+
+ scoped_refptr<ContentLayer> green = ContentLayer::Create(&green_client_);
+ green->SetAnchorPoint(gfx::PointF());
+ green->SetPosition(gfx::Point(10, 20));
+ green->SetBounds(gfx::Size(90, 80));
+ green->SetIsDrawable(true);
+ background->AddChild(green);
+
+ scoped_refptr<ContentLayer> blue = ContentLayer::Create(&blue_client_);
+ blue->SetAnchorPoint(gfx::PointF());
+ blue->SetPosition(gfx::Point(50, 50));
+ blue->SetBounds(gfx::Size(25, 25));
+ blue->SetIsDrawable(true);
+ green->AddChild(blue);
+
+ // Grab the green layer's content with blue in the bottom right.
+ copy_subrect_ = gfx::Rect(25, 25, 50, 50);
+ device_scale_factor_ = 2.f;
+
+ RunPixelTestWithReadbackTarget(SOFTWARE_WITH_DEFAULT,
+ background,
+ green.get(),
+ base::FilePath(FILE_PATH_LITERAL(
+ "green_small_with_blue_corner.png")));
+}
+
+TEST_F(LayerTreeHostReadbackDeviceScalePixelTest,
+ ReadbackNonRootLayerSubrect_GL) {
+ scoped_refptr<ContentLayer> background = ContentLayer::Create(&white_client_);
+ background->SetAnchorPoint(gfx::PointF());
+ background->SetBounds(gfx::Size(100, 100));
+ background->SetIsDrawable(true);
+
+ scoped_refptr<ContentLayer> green = ContentLayer::Create(&green_client_);
+ green->SetAnchorPoint(gfx::PointF());
+ green->SetPosition(gfx::Point(10, 20));
+ green->SetBounds(gfx::Size(90, 80));
+ green->SetIsDrawable(true);
+ background->AddChild(green);
+
+ scoped_refptr<ContentLayer> blue = ContentLayer::Create(&blue_client_);
+ blue->SetAnchorPoint(gfx::PointF());
+ blue->SetPosition(gfx::Point(50, 50));
+ blue->SetBounds(gfx::Size(25, 25));
+ blue->SetIsDrawable(true);
+ green->AddChild(blue);
+
+ // Grab the green layer's content with blue in the bottom right.
+ copy_subrect_ = gfx::Rect(25, 25, 50, 50);
+ device_scale_factor_ = 2.f;
+
+ RunPixelTestWithReadbackTarget(GL_WITH_DEFAULT,
+ background,
+ green.get(),
+ base::FilePath(FILE_PATH_LITERAL(
+ "green_small_with_blue_corner.png")));
+}
+
+class LayerTreeHostReadbackViaCompositeAndReadbackPixelTest
+ : public LayerTreePixelTest {
+ protected:
+ LayerTreeHostReadbackViaCompositeAndReadbackPixelTest()
+ : device_scale_factor_(1.f),
+ white_client_(SK_ColorWHITE),
+ green_client_(SK_ColorGREEN),
+ blue_client_(SK_ColorBLUE) {}
+
+ virtual void InitializeSettings(LayerTreeSettings* settings) OVERRIDE {
+ // Cause the device scale factor to be inherited by contents scales.
+ settings->layer_transforms_should_scale_layer_contents = true;
+ }
+
+ virtual void SetupTree() OVERRIDE {
+ layer_tree_host()->SetDeviceScaleFactor(device_scale_factor_);
+ LayerTreePixelTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ EXPECT_EQ(device_scale_factor_, layer_tree_host()->device_scale_factor());
+ if (TestEnded())
+ return;
+
+ gfx::Rect device_viewport_copy_rect(
+ layer_tree_host()->device_viewport_size());
+ if (!device_viewport_copy_subrect_.IsEmpty())
+ device_viewport_copy_rect.Intersect(device_viewport_copy_subrect_);
+
+ scoped_ptr<SkBitmap> bitmap(new SkBitmap);
+ bitmap->setConfig(SkBitmap::kARGB_8888_Config,
+ device_viewport_copy_rect.width(),
+ device_viewport_copy_rect.height());
+ bitmap->allocPixels();
+ {
+ scoped_ptr<SkAutoLockPixels> lock(new SkAutoLockPixels(*bitmap));
+ layer_tree_host()->CompositeAndReadback(bitmap->getPixels(),
+ device_viewport_copy_rect);
+ }
+
+ result_bitmap_ = bitmap.Pass();
+ EndTest();
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ LayerImpl* root_impl = host_impl->active_tree()->root_layer();
+
+ LayerImpl* background_impl = root_impl->children()[0];
+ EXPECT_EQ(device_scale_factor_, background_impl->contents_scale_x());
+ EXPECT_EQ(device_scale_factor_, background_impl->contents_scale_y());
+
+ LayerImpl* green_impl = background_impl->children()[0];
+ EXPECT_EQ(device_scale_factor_, green_impl->contents_scale_x());
+ EXPECT_EQ(device_scale_factor_, green_impl->contents_scale_y());
+
+ LayerImpl* blue_impl = green_impl->children()[0];
+ EXPECT_EQ(device_scale_factor_, blue_impl->contents_scale_x());
+ EXPECT_EQ(device_scale_factor_, blue_impl->contents_scale_y());
+ }
+
+ gfx::Rect device_viewport_copy_subrect_;
+ float device_scale_factor_;
+ SolidColorContentLayerClient white_client_;
+ SolidColorContentLayerClient green_client_;
+ SolidColorContentLayerClient blue_client_;
+};
+
+TEST_F(LayerTreeHostReadbackViaCompositeAndReadbackPixelTest,
+ CompositeAndReadback_Software_1) {
+ scoped_refptr<ContentLayer> background = ContentLayer::Create(&white_client_);
+ background->SetAnchorPoint(gfx::PointF());
+ background->SetBounds(gfx::Size(200, 200));
+ background->SetIsDrawable(true);
+
+ scoped_refptr<ContentLayer> green = ContentLayer::Create(&green_client_);
+ green->SetAnchorPoint(gfx::PointF());
+ green->SetBounds(gfx::Size(200, 200));
+ green->SetIsDrawable(true);
+ background->AddChild(green);
+
+ scoped_refptr<ContentLayer> blue = ContentLayer::Create(&blue_client_);
+ blue->SetAnchorPoint(gfx::PointF());
+ blue->SetPosition(gfx::Point(100, 100));
+ blue->SetBounds(gfx::Size(50, 50));
+ blue->SetIsDrawable(true);
+ green->AddChild(blue);
+
+ // Grab the middle of the device viewport.
+ device_viewport_copy_subrect_ = gfx::Rect(50, 50, 100, 100);
+ device_scale_factor_ = 1.f;
+
+ RunPixelTestWithReadbackTarget(SOFTWARE_WITH_DEFAULT,
+ background,
+ green.get(),
+ base::FilePath(FILE_PATH_LITERAL(
+ "green_small_with_blue_corner.png")));
+}
+
+TEST_F(LayerTreeHostReadbackViaCompositeAndReadbackPixelTest,
+ CompositeAndReadback_Software_2) {
+ scoped_refptr<ContentLayer> background = ContentLayer::Create(&white_client_);
+ background->SetAnchorPoint(gfx::PointF());
+ background->SetBounds(gfx::Size(100, 100));
+ background->SetIsDrawable(true);
+
+ scoped_refptr<ContentLayer> green = ContentLayer::Create(&green_client_);
+ green->SetAnchorPoint(gfx::PointF());
+ green->SetBounds(gfx::Size(100, 100));
+ green->SetIsDrawable(true);
+ background->AddChild(green);
+
+ scoped_refptr<ContentLayer> blue = ContentLayer::Create(&blue_client_);
+ blue->SetAnchorPoint(gfx::PointF());
+ blue->SetPosition(gfx::Point(50, 50));
+ blue->SetBounds(gfx::Size(25, 25));
+ blue->SetIsDrawable(true);
+ green->AddChild(blue);
+
+ // Grab the middle of the device viewport.
+ device_viewport_copy_subrect_ = gfx::Rect(50, 50, 100, 100);
+ device_scale_factor_ = 2.f;
+
+ RunPixelTestWithReadbackTarget(SOFTWARE_WITH_DEFAULT,
+ background,
+ green.get(),
+ base::FilePath(FILE_PATH_LITERAL(
+ "green_small_with_blue_corner.png")));
+}
+
+TEST_F(LayerTreeHostReadbackViaCompositeAndReadbackPixelTest,
+ CompositeAndReadback_GL_1) {
+ scoped_refptr<ContentLayer> background = ContentLayer::Create(&white_client_);
+ background->SetAnchorPoint(gfx::PointF());
+ background->SetBounds(gfx::Size(200, 200));
+ background->SetIsDrawable(true);
+
+ scoped_refptr<ContentLayer> green = ContentLayer::Create(&green_client_);
+ green->SetAnchorPoint(gfx::PointF());
+ green->SetBounds(gfx::Size(200, 200));
+ green->SetIsDrawable(true);
+ background->AddChild(green);
+
+ scoped_refptr<ContentLayer> blue = ContentLayer::Create(&blue_client_);
+ blue->SetAnchorPoint(gfx::PointF());
+ blue->SetPosition(gfx::Point(100, 100));
+ blue->SetBounds(gfx::Size(50, 50));
+ blue->SetIsDrawable(true);
+ green->AddChild(blue);
+
+ // Grab the middle of the device viewport.
+ device_viewport_copy_subrect_ = gfx::Rect(50, 50, 100, 100);
+ device_scale_factor_ = 1.f;
+
+ RunPixelTestWithReadbackTarget(GL_WITH_DEFAULT,
+ background,
+ green.get(),
+ base::FilePath(FILE_PATH_LITERAL(
+ "green_small_with_blue_corner.png")));
+}
+
+TEST_F(LayerTreeHostReadbackViaCompositeAndReadbackPixelTest,
+ CompositeAndReadback_GL_2) {
+ scoped_refptr<ContentLayer> background = ContentLayer::Create(&white_client_);
+ background->SetAnchorPoint(gfx::PointF());
+ background->SetBounds(gfx::Size(100, 100));
+ background->SetIsDrawable(true);
+
+ scoped_refptr<ContentLayer> green = ContentLayer::Create(&green_client_);
+ green->SetAnchorPoint(gfx::PointF());
+ green->SetBounds(gfx::Size(100, 100));
+ green->SetIsDrawable(true);
+ background->AddChild(green);
+
+ scoped_refptr<ContentLayer> blue = ContentLayer::Create(&blue_client_);
+ blue->SetAnchorPoint(gfx::PointF());
+ blue->SetPosition(gfx::Point(50, 50));
+ blue->SetBounds(gfx::Size(25, 25));
+ blue->SetIsDrawable(true);
+ green->AddChild(blue);
+
+ // Grab the middle of the device viewport.
+ device_viewport_copy_subrect_ = gfx::Rect(50, 50, 100, 100);
+ device_scale_factor_ = 2.f;
+
+ RunPixelTestWithReadbackTarget(GL_WITH_DEFAULT,
+ background,
+ green.get(),
+ base::FilePath(FILE_PATH_LITERAL(
+ "green_small_with_blue_corner.png")));
+}
+
+TEST_F(LayerTreeHostReadbackPixelTest, ReadbackNonRootLayerOutsideViewport) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ scoped_refptr<SolidColorLayer> green = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorGREEN);
+ // Only the top left quarter of the layer is inside the viewport, so the
+ // blue layer is entirely outside.
+ green->SetPosition(gfx::Point(100, 100));
+ background->AddChild(green);
+
+ scoped_refptr<SolidColorLayer> blue = CreateSolidColorLayer(
+ gfx::Rect(150, 150, 50, 50), SK_ColorBLUE);
+ green->AddChild(blue);
+
+ RunPixelTestWithReadbackTarget(GL_WITH_DEFAULT,
+ background,
+ green.get(),
+ base::FilePath(FILE_PATH_LITERAL(
+ "green_with_blue_corner.png")));
+}
+
+// TextureLayers are clipped differently than SolidColorLayers, verify they
+// also can be copied when outside of the viewport.
+TEST_F(LayerTreeHostReadbackPixelTest,
+ ReadbackNonRootTextureLayerOutsideViewport) {
+ scoped_refptr<SolidColorLayer> background = CreateSolidColorLayer(
+ gfx::Rect(200, 200), SK_ColorWHITE);
+
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, 200, 200);
+ bitmap.allocPixels();
+ bitmap.eraseColor(SK_ColorGREEN);
+ {
+ SkDevice device(bitmap);
+ skia::RefPtr<SkCanvas> canvas = skia::AdoptRef(new SkCanvas(&device));
+ SkPaint paint;
+ paint.setStyle(SkPaint::kFill_Style);
+ paint.setColor(SK_ColorBLUE);
+ canvas->drawRect(SkRect::MakeXYWH(150, 150, 50, 50), paint);
+ }
+
+ scoped_refptr<TextureLayer> texture = CreateTextureLayer(
+ gfx::Rect(200, 200), bitmap);
+
+ // Tests with solid color layers verify correctness when CanClipSelf is false.
+ EXPECT_FALSE(background->CanClipSelf());
+ // This test verifies correctness when CanClipSelf is true.
+ EXPECT_TRUE(texture->CanClipSelf());
+
+ // Only the top left quarter of the layer is inside the viewport, so the
+ // blue corner is entirely outside.
+ texture->SetPosition(gfx::Point(100, 100));
+ background->AddChild(texture);
+
+ RunPixelTestWithReadbackTarget(GL_WITH_DEFAULT,
+ background,
+ texture.get(),
+ base::FilePath(FILE_PATH_LITERAL(
+ "green_with_blue_corner.png")));
+}
+
+} // namespace
+} // namespace cc
+
+#endif // OS_ANDROID
diff --git a/chromium/cc/trees/layer_tree_host_unittest.cc b/chromium/cc/trees/layer_tree_host_unittest.cc
new file mode 100644
index 00000000000..55841e52e70
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_host_unittest.cc
@@ -0,0 +1,4353 @@
+// Copyright 2011 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 "cc/trees/layer_tree_host.h"
+
+#include <algorithm>
+
+#include "base/auto_reset.h"
+#include "base/synchronization/lock.h"
+#include "cc/animation/timing_function.h"
+#include "cc/debug/frame_rate_counter.h"
+#include "cc/layers/content_layer.h"
+#include "cc/layers/content_layer_client.h"
+#include "cc/layers/io_surface_layer.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/layers/picture_layer.h"
+#include "cc/layers/scrollbar_layer.h"
+#include "cc/layers/solid_color_layer.h"
+#include "cc/layers/video_layer.h"
+#include "cc/output/begin_frame_args.h"
+#include "cc/output/copy_output_request.h"
+#include "cc/output/copy_output_result.h"
+#include "cc/output/output_surface.h"
+#include "cc/resources/prioritized_resource.h"
+#include "cc/resources/prioritized_resource_manager.h"
+#include "cc/resources/resource_update_queue.h"
+#include "cc/scheduler/frame_rate_controller.h"
+#include "cc/test/fake_content_layer.h"
+#include "cc/test/fake_content_layer_client.h"
+#include "cc/test/fake_layer_tree_host_client.h"
+#include "cc/test/fake_output_surface.h"
+#include "cc/test/fake_picture_layer.h"
+#include "cc/test/fake_picture_layer_impl.h"
+#include "cc/test/fake_proxy.h"
+#include "cc/test/fake_scoped_ui_resource.h"
+#include "cc/test/fake_scrollbar_layer.h"
+#include "cc/test/fake_video_frame_provider.h"
+#include "cc/test/geometry_test_utils.h"
+#include "cc/test/layer_tree_test.h"
+#include "cc/test/occlusion_tracker_test_common.h"
+#include "cc/trees/layer_tree_host_impl.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "cc/trees/single_thread_proxy.h"
+#include "cc/trees/thread_proxy.h"
+#include "gpu/GLES2/gl2extchromium.h"
+#include "skia/ext/refptr.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "third_party/khronos/GLES2/gl2ext.h"
+#include "third_party/skia/include/core/SkPicture.h"
+#include "ui/gfx/point_conversions.h"
+#include "ui/gfx/size_conversions.h"
+#include "ui/gfx/vector2d_conversions.h"
+
+using testing::_;
+using testing::AnyNumber;
+using testing::AtLeast;
+using testing::Mock;
+
+namespace cc {
+namespace {
+
+class LayerTreeHostTest : public LayerTreeTest {
+};
+
+// Two setNeedsCommits in a row should lead to at least 1 commit and at least 1
+// draw with frame 0.
+class LayerTreeHostTestSetNeedsCommit1 : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestSetNeedsCommit1() : num_commits_(0), num_draws_(0) {}
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ num_draws_++;
+ if (!impl->active_tree()->source_frame_number())
+ EndTest();
+ }
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ num_commits_++;
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ EXPECT_GE(1, num_commits_);
+ EXPECT_GE(1, num_draws_);
+ }
+
+ private:
+ int num_commits_;
+ int num_draws_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestSetNeedsCommit1);
+
+// A SetNeedsCommit should lead to 1 commit. Issuing a second commit after that
+// first committed frame draws should lead to another commit.
+class LayerTreeHostTestSetNeedsCommit2 : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestSetNeedsCommit2() : num_commits_(0), num_draws_(0) {}
+
+ virtual void BeginTest() OVERRIDE { PostSetNeedsCommitToMainThread(); }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ ++num_draws_;
+ }
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ ++num_commits_;
+ switch (num_commits_) {
+ case 1:
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 2:
+ EndTest();
+ break;
+ default:
+ NOTREACHED();
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ EXPECT_EQ(2, num_commits_);
+ EXPECT_LE(1, num_draws_);
+ }
+
+ private:
+ int num_commits_;
+ int num_draws_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostTestSetNeedsCommit2);
+
+// Verify that we pass property values in PushPropertiesTo.
+class LayerTreeHostTestPushPropertiesTo : public LayerTreeHostTest {
+ protected:
+ virtual void SetupTree() OVERRIDE {
+ scoped_refptr<Layer> root = Layer::Create();
+ root->SetBounds(gfx::Size(10, 10));
+ layer_tree_host()->SetRootLayer(root);
+ LayerTreeHostTest::SetupTree();
+ }
+
+ enum Properties {
+ STARTUP,
+ BOUNDS,
+ HIDE_LAYER_AND_SUBTREE,
+ DRAWS_CONTENT,
+ DONE,
+ };
+
+ virtual void BeginTest() OVERRIDE {
+ index_ = STARTUP;
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ VerifyAfterValues(impl->active_tree()->root_layer());
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ SetBeforeValues(layer_tree_host()->root_layer());
+ VerifyBeforeValues(layer_tree_host()->root_layer());
+
+ ++index_;
+ if (index_ == DONE) {
+ EndTest();
+ return;
+ }
+
+ SetAfterValues(layer_tree_host()->root_layer());
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ void VerifyBeforeValues(Layer* layer) {
+ EXPECT_EQ(gfx::Size(10, 10).ToString(), layer->bounds().ToString());
+ EXPECT_FALSE(layer->hide_layer_and_subtree());
+ EXPECT_FALSE(layer->DrawsContent());
+ }
+
+ void SetBeforeValues(Layer* layer) {
+ layer->SetBounds(gfx::Size(10, 10));
+ layer->SetHideLayerAndSubtree(false);
+ layer->SetIsDrawable(false);
+ }
+
+ void VerifyAfterValues(LayerImpl* layer) {
+ switch (static_cast<Properties>(index_)) {
+ case STARTUP:
+ case DONE:
+ break;
+ case BOUNDS:
+ EXPECT_EQ(gfx::Size(20, 20).ToString(), layer->bounds().ToString());
+ break;
+ case HIDE_LAYER_AND_SUBTREE:
+ EXPECT_TRUE(layer->hide_layer_and_subtree());
+ break;
+ case DRAWS_CONTENT:
+ EXPECT_TRUE(layer->DrawsContent());
+ break;
+ }
+ }
+
+ void SetAfterValues(Layer* layer) {
+ switch (static_cast<Properties>(index_)) {
+ case STARTUP:
+ case DONE:
+ break;
+ case BOUNDS:
+ layer->SetBounds(gfx::Size(20, 20));
+ break;
+ case HIDE_LAYER_AND_SUBTREE:
+ layer->SetHideLayerAndSubtree(true);
+ break;
+ case DRAWS_CONTENT:
+ layer->SetIsDrawable(true);
+ break;
+ }
+ }
+
+ int index_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestPushPropertiesTo);
+
+// 1 setNeedsRedraw after the first commit has completed should lead to 1
+// additional draw.
+class LayerTreeHostTestSetNeedsRedraw : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestSetNeedsRedraw() : num_commits_(0), num_draws_(0) {}
+
+ virtual void BeginTest() OVERRIDE { PostSetNeedsCommitToMainThread(); }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ EXPECT_EQ(0, impl->active_tree()->source_frame_number());
+ if (!num_draws_) {
+ // Redraw again to verify that the second redraw doesn't commit.
+ PostSetNeedsRedrawToMainThread();
+ } else {
+ EndTest();
+ }
+ num_draws_++;
+ }
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ EXPECT_EQ(0, num_draws_);
+ num_commits_++;
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ EXPECT_GE(2, num_draws_);
+ EXPECT_EQ(1, num_commits_);
+ }
+
+ private:
+ int num_commits_;
+ int num_draws_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostTestSetNeedsRedraw);
+
+// After setNeedsRedrawRect(invalid_rect) the final damage_rect
+// must contain invalid_rect.
+class LayerTreeHostTestSetNeedsRedrawRect : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestSetNeedsRedrawRect()
+ : num_draws_(0),
+ bounds_(50, 50),
+ invalid_rect_(10, 10, 20, 20),
+ root_layer_(ContentLayer::Create(&client_)) {
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ root_layer_->SetIsDrawable(true);
+ root_layer_->SetBounds(bounds_);
+ layer_tree_host()->SetRootLayer(root_layer_);
+ layer_tree_host()->SetViewportSize(bounds_);
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual bool PrepareToDrawOnThread(LayerTreeHostImpl* host_impl,
+ LayerTreeHostImpl::FrameData* frame_data,
+ bool result) OVERRIDE {
+ EXPECT_TRUE(result);
+
+ gfx::RectF root_damage_rect;
+ if (!frame_data->render_passes.empty())
+ root_damage_rect = frame_data->render_passes.back()->damage_rect;
+
+ if (!num_draws_) {
+ // If this is the first frame, expect full frame damage.
+ EXPECT_RECT_EQ(root_damage_rect, gfx::Rect(bounds_));
+ } else {
+ // Check that invalid_rect_ is indeed repainted.
+ EXPECT_TRUE(root_damage_rect.Contains(invalid_rect_));
+ }
+
+ return result;
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ if (!num_draws_) {
+ PostSetNeedsRedrawRectToMainThread(invalid_rect_);
+ } else {
+ EndTest();
+ }
+ num_draws_++;
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ EXPECT_EQ(2, num_draws_);
+ }
+
+ private:
+ int num_draws_;
+ const gfx::Size bounds_;
+ const gfx::Rect invalid_rect_;
+ FakeContentLayerClient client_;
+ scoped_refptr<ContentLayer> root_layer_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestSetNeedsRedrawRect);
+
+class LayerTreeHostTestNoExtraCommitFromInvalidate : public LayerTreeHostTest {
+ public:
+ virtual void InitializeSettings(LayerTreeSettings* settings) OVERRIDE {
+ settings->layer_transforms_should_scale_layer_contents = true;
+ }
+
+ virtual void SetupTree() OVERRIDE {
+ root_layer_ = Layer::Create();
+ root_layer_->SetBounds(gfx::Size(10, 20));
+
+ scaled_layer_ = FakeContentLayer::Create(&client_);
+ scaled_layer_->SetBounds(gfx::Size(1, 1));
+ root_layer_->AddChild(scaled_layer_);
+
+ layer_tree_host()->SetRootLayer(root_layer_);
+ LayerTreeHostTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ if (host_impl->active_tree()->source_frame_number() == 1)
+ EndTest();
+ }
+
+ virtual void DidCommit() OVERRIDE {
+ switch (layer_tree_host()->source_frame_number()) {
+ case 1:
+ // Changing the device scale factor causes a commit. It also changes
+ // the content bounds of |scaled_layer_|, which should not generate
+ // a second commit as a result.
+ layer_tree_host()->SetDeviceScaleFactor(4.f);
+ break;
+ default:
+ // No extra commits.
+ EXPECT_EQ(2, layer_tree_host()->source_frame_number());
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ EXPECT_EQ(gfx::Size(4, 4).ToString(),
+ scaled_layer_->content_bounds().ToString());
+ }
+
+ private:
+ FakeContentLayerClient client_;
+ scoped_refptr<Layer> root_layer_;
+ scoped_refptr<FakeContentLayer> scaled_layer_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestNoExtraCommitFromInvalidate);
+
+class LayerTreeHostTestNoExtraCommitFromScrollbarInvalidate
+ : public LayerTreeHostTest {
+ public:
+ virtual void InitializeSettings(LayerTreeSettings* settings) OVERRIDE {
+ settings->layer_transforms_should_scale_layer_contents = true;
+ }
+
+ virtual void SetupTree() OVERRIDE {
+ root_layer_ = Layer::Create();
+ root_layer_->SetBounds(gfx::Size(10, 20));
+
+ bool paint_scrollbar = true;
+ bool has_thumb = false;
+ scrollbar_ = FakeScrollbarLayer::Create(paint_scrollbar,
+ has_thumb,
+ root_layer_->id());
+ scrollbar_->SetPosition(gfx::Point(0, 10));
+ scrollbar_->SetBounds(gfx::Size(10, 10));
+
+ root_layer_->AddChild(scrollbar_);
+
+ layer_tree_host()->SetRootLayer(root_layer_);
+ LayerTreeHostTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ if (host_impl->active_tree()->source_frame_number() == 1)
+ EndTest();
+ }
+
+ virtual void DidCommit() OVERRIDE {
+ switch (layer_tree_host()->source_frame_number()) {
+ case 1:
+ // Changing the device scale factor causes a commit. It also changes
+ // the content bounds of |scrollbar_|, which should not generate
+ // a second commit as a result.
+ layer_tree_host()->SetDeviceScaleFactor(4.f);
+ break;
+ default:
+ // No extra commits.
+ EXPECT_EQ(2, layer_tree_host()->source_frame_number());
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ EXPECT_EQ(gfx::Size(40, 40).ToString(),
+ scrollbar_->content_bounds().ToString());
+ }
+
+ private:
+ FakeContentLayerClient client_;
+ scoped_refptr<Layer> root_layer_;
+ scoped_refptr<FakeScrollbarLayer> scrollbar_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostTestNoExtraCommitFromScrollbarInvalidate);
+
+class LayerTreeHostTestCompositeAndReadback : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestCompositeAndReadback() : num_commits_(0) {}
+
+ virtual void BeginTest() OVERRIDE { PostSetNeedsCommitToMainThread(); }
+
+ virtual void DidCommit() OVERRIDE {
+ num_commits_++;
+ if (num_commits_ == 1) {
+ char pixels[4];
+ layer_tree_host()->CompositeAndReadback(&pixels, gfx::Rect(0, 0, 1, 1));
+ } else if (num_commits_ == 2) {
+ // This is inside the readback. We should get another commit after it.
+ } else if (num_commits_ == 3) {
+ EndTest();
+ } else {
+ NOTREACHED();
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ int num_commits_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostTestCompositeAndReadback);
+
+class LayerTreeHostTestCompositeAndReadbackBeforePreviousCommitDraws
+ : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestCompositeAndReadbackBeforePreviousCommitDraws()
+ : num_commits_(0) {}
+
+ virtual void BeginTest() OVERRIDE { PostSetNeedsCommitToMainThread(); }
+
+ virtual void DidCommit() OVERRIDE {
+ num_commits_++;
+ if (num_commits_ == 1) {
+ layer_tree_host()->SetNeedsCommit();
+ } else if (num_commits_ == 2) {
+ char pixels[4];
+ layer_tree_host()->CompositeAndReadback(&pixels, gfx::Rect(0, 0, 1, 1));
+ } else if (num_commits_ == 3) {
+ // This is inside the readback. We should get another commit after it.
+ } else if (num_commits_ == 4) {
+ EndTest();
+ } else {
+ NOTREACHED();
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ int num_commits_;
+};
+
+MULTI_THREAD_TEST_F(
+ LayerTreeHostTestCompositeAndReadbackBeforePreviousCommitDraws);
+
+// If the layerTreeHost says it can't draw, Then we should not try to draw.
+class LayerTreeHostTestCanDrawBlocksDrawing : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestCanDrawBlocksDrawing() : num_commits_(0), done_(false) {}
+
+ virtual void BeginTest() OVERRIDE { PostSetNeedsCommitToMainThread(); }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ if (done_)
+ return;
+ // Only the initial draw should bring us here.
+ EXPECT_TRUE(impl->CanDraw());
+ EXPECT_EQ(0, impl->active_tree()->source_frame_number());
+ }
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ if (done_)
+ return;
+ if (num_commits_ >= 1) {
+ // After the first commit, we should not be able to draw.
+ EXPECT_FALSE(impl->CanDraw());
+ }
+ }
+
+ virtual void DidCommit() OVERRIDE {
+ num_commits_++;
+ if (num_commits_ == 1) {
+ // Make the viewport empty so the host says it can't draw.
+ layer_tree_host()->SetViewportSize(gfx::Size(0, 0));
+ } else if (num_commits_ == 2) {
+ char pixels[4];
+ layer_tree_host()->CompositeAndReadback(&pixels, gfx::Rect(0, 0, 1, 1));
+ } else if (num_commits_ == 3) {
+ // Let it draw so we go idle and end the test.
+ layer_tree_host()->SetViewportSize(gfx::Size(1, 1));
+ done_ = true;
+ EndTest();
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ int num_commits_;
+ bool done_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestCanDrawBlocksDrawing);
+
+// beginLayerWrite should prevent draws from executing until a commit occurs
+class LayerTreeHostTestWriteLayersRedraw : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestWriteLayersRedraw() : num_commits_(0), num_draws_(0) {}
+
+ virtual void BeginTest() OVERRIDE {
+ PostAcquireLayerTextures();
+ PostSetNeedsRedrawToMainThread(); // should be inhibited without blocking
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ num_draws_++;
+ EXPECT_EQ(num_draws_, num_commits_);
+ }
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ num_commits_++;
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE { EXPECT_EQ(1, num_commits_); }
+
+ private:
+ int num_commits_;
+ int num_draws_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostTestWriteLayersRedraw);
+
+// Verify that when resuming visibility, Requesting layer write permission
+// will not deadlock the main thread even though there are not yet any
+// scheduled redraws. This behavior is critical for reliably surviving tab
+// switching. There are no failure conditions to this test, it just passes
+// by not timing out.
+class LayerTreeHostTestWriteLayersAfterVisible : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestWriteLayersAfterVisible() : num_commits_(0) {}
+
+ virtual void BeginTest() OVERRIDE { PostSetNeedsCommitToMainThread(); }
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ num_commits_++;
+ if (num_commits_ == 2)
+ EndTest();
+ else if (num_commits_ < 2) {
+ PostSetVisibleToMainThread(false);
+ PostSetVisibleToMainThread(true);
+ PostAcquireLayerTextures();
+ PostSetNeedsCommitToMainThread();
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ int num_commits_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostTestWriteLayersAfterVisible);
+
+// A compositeAndReadback while invisible should force a normal commit without
+// assertion.
+class LayerTreeHostTestCompositeAndReadbackWhileInvisible
+ : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestCompositeAndReadbackWhileInvisible() : num_commits_(0) {}
+
+ virtual void BeginTest() OVERRIDE { PostSetNeedsCommitToMainThread(); }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ num_commits_++;
+ if (num_commits_ == 1) {
+ layer_tree_host()->SetVisible(false);
+ layer_tree_host()->SetNeedsCommit();
+ layer_tree_host()->SetNeedsCommit();
+ char pixels[4];
+ layer_tree_host()->CompositeAndReadback(&pixels, gfx::Rect(0, 0, 1, 1));
+ } else {
+ EndTest();
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ int num_commits_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostTestCompositeAndReadbackWhileInvisible);
+
+class LayerTreeHostTestAbortFrameWhenInvisible : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestAbortFrameWhenInvisible() {}
+
+ virtual void BeginTest() OVERRIDE {
+ // Request a commit (from the main thread), Which will trigger the commit
+ // flow from the impl side.
+ layer_tree_host()->SetNeedsCommit();
+ // Then mark ourselves as not visible before processing any more messages
+ // on the main thread.
+ layer_tree_host()->SetVisible(false);
+ // If we make it without kicking a frame, we pass!
+ EndTestAfterDelay(1);
+ }
+
+ virtual void Layout() OVERRIDE {
+ ASSERT_FALSE(true);
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostTestAbortFrameWhenInvisible);
+
+// This test verifies that properties on the layer tree host are commited
+// to the impl side.
+class LayerTreeHostTestCommit : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestCommit() {}
+
+ virtual void BeginTest() OVERRIDE {
+ layer_tree_host()->SetViewportSize(gfx::Size(20, 20));
+ layer_tree_host()->set_background_color(SK_ColorGRAY);
+
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ EXPECT_EQ(gfx::Size(20, 20), impl->device_viewport_size());
+ EXPECT_EQ(SK_ColorGRAY, impl->active_tree()->background_color());
+
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostTestCommit);
+
+// This test verifies that LayerTreeHostImpl's current frame time gets
+// updated in consecutive frames when it doesn't draw due to tree
+// activation failure.
+class LayerTreeHostTestFrameTimeUpdatesAfterActivationFails
+ : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestFrameTimeUpdatesAfterActivationFails() : frame_(0) {}
+
+ virtual void BeginTest() OVERRIDE {
+ layer_tree_host()->SetViewportSize(gfx::Size(20, 20));
+ layer_tree_host()->set_background_color(SK_ColorGRAY);
+
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ if (frame_ >= 1) {
+ EXPECT_NE(first_frame_time_, impl->CurrentFrameTimeTicks());
+ EndTest();
+ return;
+ }
+
+ EXPECT_FALSE(impl->settings().impl_side_painting);
+ EndTest();
+ }
+
+ virtual bool CanActivatePendingTree(LayerTreeHostImpl* impl) OVERRIDE {
+ if (frame_ >= 1)
+ return true;
+
+ return false;
+ }
+
+ virtual bool CanActivatePendingTreeIfNeeded(LayerTreeHostImpl* impl)
+ OVERRIDE {
+ frame_++;
+ if (frame_ == 1) {
+ first_frame_time_ = impl->CurrentFrameTimeTicks();
+
+ // Since base::TimeTicks::Now() uses a low-resolution clock on
+ // Windows, we need to make sure that the clock has incremented past
+ // first_frame_time_.
+ while (first_frame_time_ == base::TimeTicks::Now()) {}
+
+ return false;
+ }
+
+ return true;
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ int frame_;
+ base::TimeTicks first_frame_time_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostTestFrameTimeUpdatesAfterActivationFails);
+
+// This test verifies that LayerTreeHostImpl's current frame time gets
+// updated in consecutive frames when it draws in each frame.
+class LayerTreeHostTestFrameTimeUpdatesAfterDraw : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestFrameTimeUpdatesAfterDraw() : frame_(0) {}
+
+ virtual void BeginTest() OVERRIDE {
+ layer_tree_host()->SetViewportSize(gfx::Size(20, 20));
+ layer_tree_host()->set_background_color(SK_ColorGRAY);
+
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ frame_++;
+ if (frame_ == 1) {
+ first_frame_time_ = impl->CurrentFrameTimeTicks();
+ impl->SetNeedsRedraw();
+
+ // Since base::TimeTicks::Now() uses a low-resolution clock on
+ // Windows, we need to make sure that the clock has incremented past
+ // first_frame_time_.
+ while (first_frame_time_ == base::TimeTicks::Now()) {}
+
+ return;
+ }
+
+ EXPECT_NE(first_frame_time_, impl->CurrentFrameTimeTicks());
+ EndTest();
+ }
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ // Ensure there isn't a commit between the two draws, to ensure that a
+ // commit isn't required for updating the current frame time. We can
+ // only check for this in the multi-threaded case, since in the single-
+ // threaded case there will always be a commit between consecutive draws.
+ if (HasImplThread())
+ EXPECT_EQ(0, frame_);
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ int frame_;
+ base::TimeTicks first_frame_time_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestFrameTimeUpdatesAfterDraw);
+
+// Verifies that StartPageScaleAnimation events propagate correctly
+// from LayerTreeHost to LayerTreeHostImpl in the MT compositor.
+class LayerTreeHostTestStartPageScaleAnimation : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestStartPageScaleAnimation() {}
+
+ virtual void SetupTree() OVERRIDE {
+ LayerTreeHostTest::SetupTree();
+
+ scroll_layer_ = FakeContentLayer::Create(&client_);
+ scroll_layer_->SetScrollable(true);
+ scroll_layer_->SetScrollOffset(gfx::Vector2d());
+ layer_tree_host()->root_layer()->AddChild(scroll_layer_);
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta, float scale)
+ OVERRIDE {
+ gfx::Vector2d offset = scroll_layer_->scroll_offset();
+ scroll_layer_->SetScrollOffset(offset + scroll_delta);
+ layer_tree_host()->SetPageScaleFactorAndLimits(scale, 0.5f, 2.f);
+ }
+
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ impl->ProcessScrollDeltas();
+ // We get one commit before the first draw, and the animation doesn't happen
+ // until the second draw.
+ switch (impl->active_tree()->source_frame_number()) {
+ case 0:
+ EXPECT_EQ(1.f, impl->active_tree()->page_scale_factor());
+ // We'll start an animation when we get back to the main thread.
+ break;
+ case 1:
+ EXPECT_EQ(1.f, impl->active_tree()->page_scale_factor());
+ PostSetNeedsRedrawToMainThread();
+ break;
+ case 2:
+ EXPECT_EQ(1.25f, impl->active_tree()->page_scale_factor());
+ EndTest();
+ break;
+ default:
+ NOTREACHED();
+ }
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ switch (layer_tree_host()->source_frame_number()) {
+ case 1:
+ layer_tree_host()->SetPageScaleFactorAndLimits(1.f, 0.5f, 2.f);
+ layer_tree_host()->StartPageScaleAnimation(
+ gfx::Vector2d(), false, 1.25f, base::TimeDelta());
+ break;
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ FakeContentLayerClient client_;
+ scoped_refptr<FakeContentLayer> scroll_layer_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostTestStartPageScaleAnimation);
+
+class LayerTreeHostTestSetVisible : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestSetVisible() : num_draws_(0) {}
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ PostSetVisibleToMainThread(false);
+ // This is suppressed while we're invisible.
+ PostSetNeedsRedrawToMainThread();
+ // Triggers the redraw.
+ PostSetVisibleToMainThread(true);
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ EXPECT_TRUE(impl->visible());
+ ++num_draws_;
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE { EXPECT_EQ(1, num_draws_); }
+
+ private:
+ int num_draws_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostTestSetVisible);
+
+class TestOpacityChangeLayerDelegate : public ContentLayerClient {
+ public:
+ TestOpacityChangeLayerDelegate() : test_layer_(0) {}
+
+ void SetTestLayer(Layer* test_layer) { test_layer_ = test_layer; }
+
+ virtual void PaintContents(SkCanvas*, gfx::Rect, gfx::RectF*) OVERRIDE {
+ // Set layer opacity to 0.
+ if (test_layer_)
+ test_layer_->SetOpacity(0.f);
+ }
+ virtual void DidChangeLayerCanUseLCDText() OVERRIDE {}
+
+ private:
+ Layer* test_layer_;
+};
+
+class ContentLayerWithUpdateTracking : public ContentLayer {
+ public:
+ static scoped_refptr<ContentLayerWithUpdateTracking> Create(
+ ContentLayerClient* client) {
+ return make_scoped_refptr(new ContentLayerWithUpdateTracking(client));
+ }
+
+ int PaintContentsCount() { return paint_contents_count_; }
+ void ResetPaintContentsCount() { paint_contents_count_ = 0; }
+
+ virtual bool Update(ResourceUpdateQueue* queue,
+ const OcclusionTracker* occlusion) OVERRIDE {
+ bool updated = ContentLayer::Update(queue, occlusion);
+ paint_contents_count_++;
+ return updated;
+ }
+
+ private:
+ explicit ContentLayerWithUpdateTracking(ContentLayerClient* client)
+ : ContentLayer(client), paint_contents_count_(0) {
+ SetAnchorPoint(gfx::PointF(0.f, 0.f));
+ SetBounds(gfx::Size(10, 10));
+ SetIsDrawable(true);
+ }
+ virtual ~ContentLayerWithUpdateTracking() {}
+
+ int paint_contents_count_;
+};
+
+// Layer opacity change during paint should not prevent compositor resources
+// from being updated during commit.
+class LayerTreeHostTestOpacityChange : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestOpacityChange()
+ : test_opacity_change_delegate_(),
+ update_check_layer_(ContentLayerWithUpdateTracking::Create(
+ &test_opacity_change_delegate_)) {
+ test_opacity_change_delegate_.SetTestLayer(update_check_layer_.get());
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ layer_tree_host()->SetViewportSize(gfx::Size(10, 10));
+ layer_tree_host()->root_layer()->AddChild(update_check_layer_);
+
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ // Update() should have been called once.
+ EXPECT_EQ(1, update_check_layer_->PaintContentsCount());
+ }
+
+ private:
+ TestOpacityChangeLayerDelegate test_opacity_change_delegate_;
+ scoped_refptr<ContentLayerWithUpdateTracking> update_check_layer_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostTestOpacityChange);
+
+class NoScaleContentLayer : public ContentLayer {
+ public:
+ static scoped_refptr<NoScaleContentLayer> Create(ContentLayerClient* client) {
+ return make_scoped_refptr(new NoScaleContentLayer(client));
+ }
+
+ virtual void CalculateContentsScale(float ideal_contents_scale,
+ float device_scale_factor,
+ float page_scale_factor,
+ bool animating_transform_to_screen,
+ float* contents_scale_x,
+ float* contents_scale_y,
+ gfx::Size* contentBounds) OVERRIDE {
+ // Skip over the ContentLayer's method to the base Layer class.
+ Layer::CalculateContentsScale(ideal_contents_scale,
+ device_scale_factor,
+ page_scale_factor,
+ animating_transform_to_screen,
+ contents_scale_x,
+ contents_scale_y,
+ contentBounds);
+ }
+
+ private:
+ explicit NoScaleContentLayer(ContentLayerClient* client)
+ : ContentLayer(client) {}
+ virtual ~NoScaleContentLayer() {}
+};
+
+class LayerTreeHostTestDeviceScaleFactorScalesViewportAndLayers
+ : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestDeviceScaleFactorScalesViewportAndLayers()
+ : root_layer_(NoScaleContentLayer::Create(&client_)),
+ child_layer_(ContentLayer::Create(&client_)) {}
+
+ virtual void BeginTest() OVERRIDE {
+ layer_tree_host()->SetViewportSize(gfx::Size(60, 60));
+ layer_tree_host()->SetDeviceScaleFactor(1.5);
+ EXPECT_EQ(gfx::Size(60, 60), layer_tree_host()->device_viewport_size());
+
+ root_layer_->AddChild(child_layer_);
+
+ root_layer_->SetIsDrawable(true);
+ root_layer_->SetBounds(gfx::Size(30, 30));
+ root_layer_->SetAnchorPoint(gfx::PointF(0.f, 0.f));
+
+ child_layer_->SetIsDrawable(true);
+ child_layer_->SetPosition(gfx::Point(2, 2));
+ child_layer_->SetBounds(gfx::Size(10, 10));
+ child_layer_->SetAnchorPoint(gfx::PointF(0.f, 0.f));
+
+ layer_tree_host()->SetRootLayer(root_layer_);
+
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ // Should only do one commit.
+ EXPECT_EQ(0, impl->active_tree()->source_frame_number());
+ // Device scale factor should come over to impl.
+ EXPECT_NEAR(impl->device_scale_factor(), 1.5f, 0.00001f);
+
+ // Both layers are on impl.
+ ASSERT_EQ(1u, impl->active_tree()->root_layer()->children().size());
+
+ // Device viewport is scaled.
+ EXPECT_EQ(gfx::Size(60, 60), impl->device_viewport_size());
+
+ LayerImpl* root = impl->active_tree()->root_layer();
+ LayerImpl* child = impl->active_tree()->root_layer()->children()[0];
+
+ // Positions remain in layout pixels.
+ EXPECT_EQ(gfx::Point(0, 0), root->position());
+ EXPECT_EQ(gfx::Point(2, 2), child->position());
+
+ // Compute all the layer transforms for the frame.
+ LayerTreeHostImpl::FrameData frame_data;
+ impl->PrepareToDraw(&frame_data, gfx::Rect());
+ impl->DidDrawAllLayers(frame_data);
+
+ const LayerImplList& render_surface_layer_list =
+ *frame_data.render_surface_layer_list;
+
+ // Both layers should be drawing into the root render surface.
+ ASSERT_EQ(1u, render_surface_layer_list.size());
+ ASSERT_EQ(root->render_surface(),
+ render_surface_layer_list[0]->render_surface());
+ ASSERT_EQ(2u, root->render_surface()->layer_list().size());
+
+ // The root render surface is the size of the viewport.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 60, 60),
+ root->render_surface()->content_rect());
+
+ // The content bounds of the child should be scaled.
+ gfx::Size child_bounds_scaled =
+ gfx::ToCeiledSize(gfx::ScaleSize(child->bounds(), 1.5));
+ EXPECT_EQ(child_bounds_scaled, child->content_bounds());
+
+ gfx::Transform scale_transform;
+ scale_transform.Scale(impl->device_scale_factor(),
+ impl->device_scale_factor());
+
+ // The root layer is scaled by 2x.
+ gfx::Transform root_screen_space_transform = scale_transform;
+ gfx::Transform root_draw_transform = scale_transform;
+
+ EXPECT_EQ(root_draw_transform, root->draw_transform());
+ EXPECT_EQ(root_screen_space_transform, root->screen_space_transform());
+
+ // The child is at position 2,2, which is transformed to 3,3 after the scale
+ gfx::Transform child_screen_space_transform;
+ child_screen_space_transform.Translate(3.f, 3.f);
+ gfx::Transform child_draw_transform = child_screen_space_transform;
+
+ EXPECT_TRANSFORMATION_MATRIX_EQ(child_draw_transform,
+ child->draw_transform());
+ EXPECT_TRANSFORMATION_MATRIX_EQ(child_screen_space_transform,
+ child->screen_space_transform());
+
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ FakeContentLayerClient client_;
+ scoped_refptr<NoScaleContentLayer> root_layer_;
+ scoped_refptr<ContentLayer> child_layer_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostTestDeviceScaleFactorScalesViewportAndLayers);
+
+// Verify atomicity of commits and reuse of textures.
+class LayerTreeHostTestDirectRendererAtomicCommit : public LayerTreeHostTest {
+ public:
+ virtual void InitializeSettings(LayerTreeSettings* settings) OVERRIDE {
+ // Make sure partial texture updates are turned off.
+ settings->max_partial_texture_updates = 0;
+ // Linear fade animator prevents scrollbars from drawing immediately.
+ settings->use_linear_fade_scrollbar_animator = false;
+ }
+
+ virtual void SetupTree() OVERRIDE {
+ layer_ = FakeContentLayer::Create(&client_);
+ layer_->SetBounds(gfx::Size(10, 20));
+
+ bool paint_scrollbar = true;
+ bool has_thumb = false;
+ scrollbar_ =
+ FakeScrollbarLayer::Create(paint_scrollbar, has_thumb, layer_->id());
+ scrollbar_->SetPosition(gfx::Point(0, 10));
+ scrollbar_->SetBounds(gfx::Size(10, 10));
+
+ layer_->AddChild(scrollbar_);
+
+ layer_tree_host()->SetRootLayer(layer_);
+ LayerTreeHostTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ drew_frame_ = -1;
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ ASSERT_EQ(0u, layer_tree_host()->settings().max_partial_texture_updates);
+
+ TestWebGraphicsContext3D* context = static_cast<TestWebGraphicsContext3D*>(
+ impl->output_surface()->context3d());
+
+ switch (impl->active_tree()->source_frame_number()) {
+ case 0:
+ // Number of textures should be one for each layer
+ ASSERT_EQ(2u, context->NumTextures());
+ // Number of textures used for commit should be one for each layer.
+ EXPECT_EQ(2u, context->NumUsedTextures());
+ // Verify that used texture is correct.
+ EXPECT_TRUE(context->UsedTexture(context->TextureAt(0)));
+ EXPECT_TRUE(context->UsedTexture(context->TextureAt(1)));
+
+ context->ResetUsedTextures();
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 1:
+ // Number of textures should be one for scrollbar layer since it was
+ // requested and deleted on the impl-thread, and double for the content
+ // layer since its first texture is used by impl thread and cannot by
+ // used for update.
+ ASSERT_EQ(3u, context->NumTextures());
+ // Number of textures used for commit should be one for each layer.
+ EXPECT_EQ(2u, context->NumUsedTextures());
+ // First textures should not have been used.
+ EXPECT_FALSE(context->UsedTexture(context->TextureAt(0)));
+ EXPECT_TRUE(context->UsedTexture(context->TextureAt(1)));
+ // New textures should have been used.
+ EXPECT_TRUE(context->UsedTexture(context->TextureAt(2)));
+ context->ResetUsedTextures();
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 2:
+ EndTest();
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ TestWebGraphicsContext3D* context = static_cast<TestWebGraphicsContext3D*>(
+ impl->output_surface()->context3d());
+
+ if (drew_frame_ == impl->active_tree()->source_frame_number()) {
+ EXPECT_EQ(0u, context->NumUsedTextures()) << "For frame " << drew_frame_;
+ return;
+ }
+ drew_frame_ = impl->active_tree()->source_frame_number();
+
+ // We draw/ship one texture each frame for each layer.
+ EXPECT_EQ(2u, context->NumUsedTextures());
+ context->ResetUsedTextures();
+ }
+
+ virtual void Layout() OVERRIDE {
+ layer_->SetNeedsDisplay();
+ scrollbar_->SetNeedsDisplay();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ protected:
+ FakeContentLayerClient client_;
+ scoped_refptr<FakeContentLayer> layer_;
+ scoped_refptr<FakeScrollbarLayer> scrollbar_;
+ int drew_frame_;
+};
+
+MULTI_THREAD_DIRECT_RENDERER_TEST_F(
+ LayerTreeHostTestDirectRendererAtomicCommit);
+
+class LayerTreeHostTestDelegatingRendererAtomicCommit
+ : public LayerTreeHostTestDirectRendererAtomicCommit {
+ public:
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ ASSERT_EQ(0u, layer_tree_host()->settings().max_partial_texture_updates);
+
+ TestWebGraphicsContext3D* context = static_cast<TestWebGraphicsContext3D*>(
+ impl->output_surface()->context3d());
+
+ switch (impl->active_tree()->source_frame_number()) {
+ case 0:
+ // Number of textures should be one for each layer
+ ASSERT_EQ(2u, context->NumTextures());
+ // Number of textures used for commit should be one for each layer.
+ EXPECT_EQ(2u, context->NumUsedTextures());
+ // Verify that used texture is correct.
+ EXPECT_TRUE(context->UsedTexture(context->TextureAt(0)));
+ EXPECT_TRUE(context->UsedTexture(context->TextureAt(1)));
+ context->ResetUsedTextures();
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 1:
+ // Number of textures should be doubled as the first context layer
+ // texture is being used by the impl-thread and cannot be used for
+ // update. The scrollbar behavior is different direct renderer because
+ // UI resource deletion with delegating renderer occurs after tree
+ // activation.
+ ASSERT_EQ(4u, context->NumTextures());
+ // Number of textures used for commit should still be
+ // one for each layer.
+ EXPECT_EQ(2u, context->NumUsedTextures());
+ // First textures should not have been used.
+ EXPECT_FALSE(context->UsedTexture(context->TextureAt(0)));
+ EXPECT_FALSE(context->UsedTexture(context->TextureAt(1)));
+ // New textures should have been used.
+ EXPECT_TRUE(context->UsedTexture(context->TextureAt(2)));
+ EXPECT_TRUE(context->UsedTexture(context->TextureAt(3)));
+ context->ResetUsedTextures();
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 2:
+ EndTest();
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+};
+
+MULTI_THREAD_DELEGATING_RENDERER_TEST_F(
+ LayerTreeHostTestDelegatingRendererAtomicCommit);
+
+static void SetLayerPropertiesForTesting(Layer* layer,
+ Layer* parent,
+ const gfx::Transform& transform,
+ gfx::PointF anchor,
+ gfx::PointF position,
+ gfx::Size bounds,
+ bool opaque) {
+ layer->RemoveAllChildren();
+ if (parent)
+ parent->AddChild(layer);
+ layer->SetTransform(transform);
+ layer->SetAnchorPoint(anchor);
+ layer->SetPosition(position);
+ layer->SetBounds(bounds);
+ layer->SetContentsOpaque(opaque);
+}
+
+class LayerTreeHostTestAtomicCommitWithPartialUpdate
+ : public LayerTreeHostTest {
+ public:
+ virtual void InitializeSettings(LayerTreeSettings* settings) OVERRIDE {
+ // Allow one partial texture update.
+ settings->max_partial_texture_updates = 1;
+ // No partial updates when impl side painting is enabled.
+ settings->impl_side_painting = false;
+ }
+
+ virtual void SetupTree() OVERRIDE {
+ parent_ = FakeContentLayer::Create(&client_);
+ parent_->SetBounds(gfx::Size(10, 20));
+
+ child_ = FakeContentLayer::Create(&client_);
+ child_->SetPosition(gfx::Point(0, 10));
+ child_->SetBounds(gfx::Size(3, 10));
+
+ parent_->AddChild(child_);
+
+ layer_tree_host()->SetRootLayer(parent_);
+ LayerTreeHostTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ switch (layer_tree_host()->source_frame_number()) {
+ case 1:
+ parent_->SetNeedsDisplay();
+ child_->SetNeedsDisplay();
+ break;
+ case 2:
+ // Damage part of layers.
+ parent_->SetNeedsDisplayRect(gfx::RectF(0.f, 0.f, 5.f, 5.f));
+ child_->SetNeedsDisplayRect(gfx::RectF(0.f, 0.f, 5.f, 5.f));
+ break;
+ case 3:
+ child_->SetNeedsDisplay();
+ layer_tree_host()->SetViewportSize(gfx::Size(10, 10));
+ break;
+ case 4:
+ layer_tree_host()->SetViewportSize(gfx::Size(10, 20));
+ break;
+ case 5:
+ EndTest();
+ break;
+ default:
+ NOTREACHED() << layer_tree_host()->source_frame_number();
+ break;
+ }
+ }
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ ASSERT_EQ(1u, layer_tree_host()->settings().max_partial_texture_updates);
+
+ TestWebGraphicsContext3D* context = static_cast<TestWebGraphicsContext3D*>(
+ impl->output_surface()->context3d());
+
+ switch (impl->active_tree()->source_frame_number()) {
+ case 0:
+ // Number of textures should be one for each layer.
+ ASSERT_EQ(2u, context->NumTextures());
+ // Number of textures used for commit should be one for each layer.
+ EXPECT_EQ(2u, context->NumUsedTextures());
+ // Verify that used textures are correct.
+ EXPECT_TRUE(context->UsedTexture(context->TextureAt(0)));
+ EXPECT_TRUE(context->UsedTexture(context->TextureAt(1)));
+ context->ResetUsedTextures();
+ break;
+ case 1:
+ // Number of textures should be two for each content layer.
+ ASSERT_EQ(4u, context->NumTextures());
+ // Number of textures used for commit should be one for each content
+ // layer.
+ EXPECT_EQ(2u, context->NumUsedTextures());
+
+ // First content textures should not have been used.
+ EXPECT_FALSE(context->UsedTexture(context->TextureAt(0)));
+ EXPECT_FALSE(context->UsedTexture(context->TextureAt(1)));
+ // New textures should have been used.
+ EXPECT_TRUE(context->UsedTexture(context->TextureAt(2)));
+ EXPECT_TRUE(context->UsedTexture(context->TextureAt(3)));
+
+ context->ResetUsedTextures();
+ break;
+ case 2:
+ // Number of textures should be two for each content layer.
+ ASSERT_EQ(4u, context->NumTextures());
+ // Number of textures used for commit should be one for each content
+ // layer.
+ EXPECT_EQ(2u, context->NumUsedTextures());
+
+ // One content layer does a partial update also.
+ EXPECT_TRUE(context->UsedTexture(context->TextureAt(2)));
+ EXPECT_FALSE(context->UsedTexture(context->TextureAt(3)));
+
+ context->ResetUsedTextures();
+ break;
+ case 3:
+ // No textures should be used for commit.
+ EXPECT_EQ(0u, context->NumUsedTextures());
+
+ context->ResetUsedTextures();
+ break;
+ case 4:
+ // Number of textures used for commit should be one, for the
+ // content layer.
+ EXPECT_EQ(1u, context->NumUsedTextures());
+
+ context->ResetUsedTextures();
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ EXPECT_LT(impl->active_tree()->source_frame_number(), 5);
+
+ TestWebGraphicsContext3D* context = static_cast<TestWebGraphicsContext3D*>(
+ impl->output_surface()->context3d());
+
+ // Number of textures used for drawing should one per layer except for
+ // frame 3 where the viewport only contains one layer.
+ if (impl->active_tree()->source_frame_number() == 3) {
+ EXPECT_EQ(1u, context->NumUsedTextures());
+ } else {
+ EXPECT_EQ(2u, context->NumUsedTextures()) <<
+ "For frame " << impl->active_tree()->source_frame_number();
+ }
+
+ context->ResetUsedTextures();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ FakeContentLayerClient client_;
+ scoped_refptr<FakeContentLayer> parent_;
+ scoped_refptr<FakeContentLayer> child_;
+};
+
+// Partial updates are not possible with a delegating renderer.
+SINGLE_AND_MULTI_THREAD_DIRECT_RENDERER_TEST_F(
+ LayerTreeHostTestAtomicCommitWithPartialUpdate);
+
+class LayerTreeHostTestFinishAllRendering : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestFinishAllRendering() : once_(false), draw_count_(0) {}
+
+ virtual void BeginTest() OVERRIDE {
+ layer_tree_host()->SetNeedsRedraw();
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ if (once_)
+ return;
+ once_ = true;
+ layer_tree_host()->SetNeedsRedraw();
+ layer_tree_host()->AcquireLayerTextures();
+ {
+ base::AutoLock lock(lock_);
+ draw_count_ = 0;
+ }
+ layer_tree_host()->FinishAllRendering();
+ {
+ base::AutoLock lock(lock_);
+ EXPECT_EQ(0, draw_count_);
+ }
+ EndTest();
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ base::AutoLock lock(lock_);
+ ++draw_count_;
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ bool once_;
+ base::Lock lock_;
+ int draw_count_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestFinishAllRendering);
+
+class LayerTreeHostTestCompositeAndReadbackCleanup : public LayerTreeHostTest {
+ public:
+ virtual void BeginTest() OVERRIDE {
+ Layer* root_layer = layer_tree_host()->root_layer();
+
+ char pixels[4];
+ layer_tree_host()->CompositeAndReadback(static_cast<void*>(&pixels),
+ gfx::Rect(0, 0, 1, 1));
+ EXPECT_FALSE(root_layer->render_surface());
+
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestCompositeAndReadbackCleanup);
+
+class LayerTreeHostTestSurfaceNotAllocatedForLayersOutsideMemoryLimit
+ : public LayerTreeHostTest {
+ protected:
+ virtual void InitializeSettings(LayerTreeSettings* settings) OVERRIDE {
+ settings->cache_render_pass_contents = true;
+ }
+
+ virtual void SetupTree() OVERRIDE {
+ root_layer_ = FakeContentLayer::Create(&client_);
+ root_layer_->SetBounds(gfx::Size(100, 100));
+
+ surface_layer1_ = FakeContentLayer::Create(&client_);
+ surface_layer1_->SetBounds(gfx::Size(100, 100));
+ surface_layer1_->SetForceRenderSurface(true);
+ surface_layer1_->SetOpacity(0.5f);
+ root_layer_->AddChild(surface_layer1_);
+
+ surface_layer2_ = FakeContentLayer::Create(&client_);
+ surface_layer2_->SetBounds(gfx::Size(100, 100));
+ surface_layer2_->SetForceRenderSurface(true);
+ surface_layer2_->SetOpacity(0.5f);
+ surface_layer1_->AddChild(surface_layer2_);
+
+ replica_layer1_ = FakeContentLayer::Create(&client_);
+ surface_layer1_->SetReplicaLayer(replica_layer1_.get());
+
+ replica_layer2_ = FakeContentLayer::Create(&client_);
+ surface_layer2_->SetReplicaLayer(replica_layer2_.get());
+
+ layer_tree_host()->SetRootLayer(root_layer_);
+ LayerTreeHostTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ Renderer* renderer = host_impl->renderer();
+ RenderPass::Id surface1_render_pass_id = host_impl->active_tree()
+ ->root_layer()->children()[0]->render_surface()->RenderPassId();
+ RenderPass::Id surface2_render_pass_id =
+ host_impl->active_tree()->root_layer()->children()[0]->children()[0]
+ ->render_surface()->RenderPassId();
+
+ switch (host_impl->active_tree()->source_frame_number()) {
+ case 0:
+ EXPECT_TRUE(renderer->HaveCachedResourcesForRenderPassId(
+ surface1_render_pass_id));
+ EXPECT_TRUE(renderer->HaveCachedResourcesForRenderPassId(
+ surface2_render_pass_id));
+
+ // Reduce the memory limit to only fit the root layer and one render
+ // surface. This prevents any contents drawing into surfaces
+ // from being allocated.
+ host_impl->SetMemoryPolicy(ManagedMemoryPolicy(100 * 100 * 4 * 2));
+ host_impl->SetDiscardBackBufferWhenNotVisible(true);
+ break;
+ case 1:
+ EXPECT_FALSE(renderer->HaveCachedResourcesForRenderPassId(
+ surface1_render_pass_id));
+ EXPECT_FALSE(renderer->HaveCachedResourcesForRenderPassId(
+ surface2_render_pass_id));
+
+ EndTest();
+ break;
+ }
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ if (layer_tree_host()->source_frame_number() < 2)
+ root_layer_->SetNeedsDisplay();
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ EXPECT_LE(2u, root_layer_->update_count());
+ EXPECT_LE(2u, surface_layer1_->update_count());
+ EXPECT_LE(2u, surface_layer2_->update_count());
+ }
+
+ FakeContentLayerClient client_;
+ scoped_refptr<FakeContentLayer> root_layer_;
+ scoped_refptr<FakeContentLayer> surface_layer1_;
+ scoped_refptr<FakeContentLayer> replica_layer1_;
+ scoped_refptr<FakeContentLayer> surface_layer2_;
+ scoped_refptr<FakeContentLayer> replica_layer2_;
+};
+
+// Surfaces don't exist with a delegated renderer.
+SINGLE_AND_MULTI_THREAD_DIRECT_RENDERER_TEST_F(
+ LayerTreeHostTestSurfaceNotAllocatedForLayersOutsideMemoryLimit);
+
+class EvictionTestLayer : public Layer {
+ public:
+ static scoped_refptr<EvictionTestLayer> Create() {
+ return make_scoped_refptr(new EvictionTestLayer());
+ }
+
+ virtual bool Update(ResourceUpdateQueue*,
+ const OcclusionTracker*) OVERRIDE;
+ virtual bool DrawsContent() const OVERRIDE { return true; }
+
+ virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl)
+ OVERRIDE;
+ virtual void PushPropertiesTo(LayerImpl* impl) OVERRIDE;
+ virtual void SetTexturePriorities(const PriorityCalculator&) OVERRIDE;
+
+ bool HaveBackingTexture() const {
+ return texture_.get() ? texture_->have_backing_texture() : false;
+ }
+
+ private:
+ EvictionTestLayer() : Layer() {}
+ virtual ~EvictionTestLayer() {}
+
+ void CreateTextureIfNeeded() {
+ if (texture_)
+ return;
+ texture_ = PrioritizedResource::Create(
+ layer_tree_host()->contents_texture_manager());
+ texture_->SetDimensions(gfx::Size(10, 10), GL_RGBA);
+ bitmap_.setConfig(SkBitmap::kARGB_8888_Config, 10, 10);
+ }
+
+ scoped_ptr<PrioritizedResource> texture_;
+ SkBitmap bitmap_;
+};
+
+class EvictionTestLayerImpl : public LayerImpl {
+ public:
+ static scoped_ptr<EvictionTestLayerImpl> Create(LayerTreeImpl* tree_impl,
+ int id) {
+ return make_scoped_ptr(new EvictionTestLayerImpl(tree_impl, id));
+ }
+ virtual ~EvictionTestLayerImpl() {}
+
+ virtual void AppendQuads(QuadSink* quad_sink,
+ AppendQuadsData* append_quads_data) OVERRIDE {
+ ASSERT_TRUE(has_texture_);
+ ASSERT_NE(0u, layer_tree_impl()->resource_provider()->num_resources());
+ }
+
+ void SetHasTexture(bool has_texture) { has_texture_ = has_texture; }
+
+ private:
+ EvictionTestLayerImpl(LayerTreeImpl* tree_impl, int id)
+ : LayerImpl(tree_impl, id), has_texture_(false) {}
+
+ bool has_texture_;
+};
+
+void EvictionTestLayer::SetTexturePriorities(const PriorityCalculator&) {
+ CreateTextureIfNeeded();
+ if (!texture_)
+ return;
+ texture_->set_request_priority(PriorityCalculator::UIPriority(true));
+}
+
+bool EvictionTestLayer::Update(ResourceUpdateQueue* queue,
+ const OcclusionTracker*) {
+ CreateTextureIfNeeded();
+ if (!texture_)
+ return false;
+
+ gfx::Rect full_rect(0, 0, 10, 10);
+ ResourceUpdate upload = ResourceUpdate::Create(
+ texture_.get(), &bitmap_, full_rect, full_rect, gfx::Vector2d());
+ queue->AppendFullUpload(upload);
+ return true;
+}
+
+scoped_ptr<LayerImpl> EvictionTestLayer::CreateLayerImpl(
+ LayerTreeImpl* tree_impl) {
+ return EvictionTestLayerImpl::Create(tree_impl, layer_id_)
+ .PassAs<LayerImpl>();
+}
+
+void EvictionTestLayer::PushPropertiesTo(LayerImpl* layer_impl) {
+ Layer::PushPropertiesTo(layer_impl);
+
+ EvictionTestLayerImpl* test_layer_impl =
+ static_cast<EvictionTestLayerImpl*>(layer_impl);
+ test_layer_impl->SetHasTexture(texture_->have_backing_texture());
+}
+
+class LayerTreeHostTestEvictTextures : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestEvictTextures()
+ : layer_(EvictionTestLayer::Create()),
+ impl_for_evict_textures_(0),
+ num_commits_(0) {}
+
+ virtual void BeginTest() OVERRIDE {
+ layer_tree_host()->SetRootLayer(layer_);
+ layer_tree_host()->SetViewportSize(gfx::Size(10, 20));
+
+ gfx::Transform identity_matrix;
+ SetLayerPropertiesForTesting(layer_.get(),
+ 0,
+ identity_matrix,
+ gfx::PointF(0.f, 0.f),
+ gfx::PointF(0.f, 0.f),
+ gfx::Size(10, 20),
+ true);
+
+ PostSetNeedsCommitToMainThread();
+ }
+
+ void PostEvictTextures() {
+ DCHECK(HasImplThread());
+ ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&LayerTreeHostTestEvictTextures::EvictTexturesOnImplThread,
+ base::Unretained(this)));
+ }
+
+ void EvictTexturesOnImplThread() {
+ DCHECK(impl_for_evict_textures_);
+ impl_for_evict_textures_->EvictTexturesForTesting();
+ }
+
+ // Commit 1: Just commit and draw normally, then post an eviction at the end
+ // that will trigger a commit.
+ // Commit 2: Triggered by the eviction, let it go through and then set
+ // needsCommit.
+ // Commit 3: Triggered by the setNeedsCommit. In Layout(), post an eviction
+ // task, which will be handled before the commit. Don't set needsCommit, it
+ // should have been posted. A frame should not be drawn (note,
+ // didCommitAndDrawFrame may be called anyway).
+ // Commit 4: Triggered by the eviction, let it go through and then set
+ // needsCommit.
+ // Commit 5: Triggered by the setNeedsCommit, post an eviction task in
+ // Layout(), a frame should not be drawn but a commit will be posted.
+ // Commit 6: Triggered by the eviction, post an eviction task in
+ // Layout(), which will be a noop, letting the commit (which recreates the
+ // textures) go through and draw a frame, then end the test.
+ //
+ // Commits 1+2 test the eviction recovery path where eviction happens outside
+ // of the beginFrame/commit pair.
+ // Commits 3+4 test the eviction recovery path where eviction happens inside
+ // the beginFrame/commit pair.
+ // Commits 5+6 test the path where an eviction happens during the eviction
+ // recovery path.
+ virtual void DidCommit() OVERRIDE {
+ switch (num_commits_) {
+ case 1:
+ EXPECT_TRUE(layer_->HaveBackingTexture());
+ PostEvictTextures();
+ break;
+ case 2:
+ EXPECT_TRUE(layer_->HaveBackingTexture());
+ layer_tree_host()->SetNeedsCommit();
+ break;
+ case 3:
+ break;
+ case 4:
+ EXPECT_TRUE(layer_->HaveBackingTexture());
+ layer_tree_host()->SetNeedsCommit();
+ break;
+ case 5:
+ break;
+ case 6:
+ EXPECT_TRUE(layer_->HaveBackingTexture());
+ EndTest();
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ impl_for_evict_textures_ = impl;
+ }
+
+ virtual void Layout() OVERRIDE {
+ ++num_commits_;
+ switch (num_commits_) {
+ case 1:
+ case 2:
+ break;
+ case 3:
+ PostEvictTextures();
+ break;
+ case 4:
+ // We couldn't check in didCommitAndDrawFrame on commit 3,
+ // so check here.
+ EXPECT_FALSE(layer_->HaveBackingTexture());
+ break;
+ case 5:
+ PostEvictTextures();
+ break;
+ case 6:
+ // We couldn't check in didCommitAndDrawFrame on commit 5,
+ // so check here.
+ EXPECT_FALSE(layer_->HaveBackingTexture());
+ PostEvictTextures();
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ FakeContentLayerClient client_;
+ scoped_refptr<EvictionTestLayer> layer_;
+ LayerTreeHostImpl* impl_for_evict_textures_;
+ int num_commits_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostTestEvictTextures);
+
+class LayerTreeHostTestContinuousCommit : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestContinuousCommit()
+ : num_commit_complete_(0), num_draw_layers_(0) {}
+
+ virtual void BeginTest() OVERRIDE {
+ layer_tree_host()->SetViewportSize(gfx::Size(10, 10));
+ layer_tree_host()->root_layer()->SetBounds(gfx::Size(10, 10));
+
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommit() OVERRIDE {
+ if (num_draw_layers_ == 2)
+ return;
+ layer_tree_host()->SetNeedsCommit();
+ }
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ if (num_draw_layers_ == 1)
+ num_commit_complete_++;
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ num_draw_layers_++;
+ if (num_draw_layers_ == 2)
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ // Check that we didn't commit twice between first and second draw.
+ EXPECT_EQ(1, num_commit_complete_);
+ }
+
+ private:
+ int num_commit_complete_;
+ int num_draw_layers_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostTestContinuousCommit);
+
+class LayerTreeHostTestContinuousInvalidate : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestContinuousInvalidate()
+ : num_commit_complete_(0), num_draw_layers_(0) {}
+
+ virtual void BeginTest() OVERRIDE {
+ layer_tree_host()->SetViewportSize(gfx::Size(10, 10));
+ layer_tree_host()->root_layer()->SetBounds(gfx::Size(10, 10));
+
+ content_layer_ = ContentLayer::Create(&client_);
+ content_layer_->SetBounds(gfx::Size(10, 10));
+ content_layer_->SetPosition(gfx::PointF(0.f, 0.f));
+ content_layer_->SetAnchorPoint(gfx::PointF(0.f, 0.f));
+ content_layer_->SetIsDrawable(true);
+ layer_tree_host()->root_layer()->AddChild(content_layer_);
+
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ if (num_draw_layers_ == 2)
+ return;
+ content_layer_->SetNeedsDisplay();
+ }
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ if (num_draw_layers_ == 1)
+ num_commit_complete_++;
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ num_draw_layers_++;
+ if (num_draw_layers_ == 2)
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ // Check that we didn't commit twice between first and second draw.
+ EXPECT_EQ(1, num_commit_complete_);
+ }
+
+ private:
+ FakeContentLayerClient client_;
+ scoped_refptr<Layer> content_layer_;
+ int num_commit_complete_;
+ int num_draw_layers_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostTestContinuousInvalidate);
+
+class LayerTreeHostTestDeferCommits : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestDeferCommits()
+ : num_commits_deferred_(0), num_complete_commits_(0) {}
+
+ virtual void BeginTest() OVERRIDE { PostSetNeedsCommitToMainThread(); }
+
+ virtual void DidDeferCommit() OVERRIDE {
+ num_commits_deferred_++;
+ layer_tree_host()->SetDeferCommits(false);
+ }
+
+ virtual void DidCommit() OVERRIDE {
+ num_complete_commits_++;
+ switch (num_complete_commits_) {
+ case 1:
+ EXPECT_EQ(0, num_commits_deferred_);
+ layer_tree_host()->SetDeferCommits(true);
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 2:
+ EndTest();
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ EXPECT_EQ(1, num_commits_deferred_);
+ EXPECT_EQ(2, num_complete_commits_);
+ }
+
+ private:
+ int num_commits_deferred_;
+ int num_complete_commits_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostTestDeferCommits);
+
+class LayerTreeHostWithProxy : public LayerTreeHost {
+ public:
+ LayerTreeHostWithProxy(FakeLayerTreeHostClient* client,
+ const LayerTreeSettings& settings,
+ scoped_ptr<FakeProxy> proxy)
+ : LayerTreeHost(client, settings) {
+ proxy->SetLayerTreeHost(this);
+ EXPECT_TRUE(InitializeForTesting(proxy.PassAs<Proxy>()));
+ }
+};
+
+TEST(LayerTreeHostTest, LimitPartialUpdates) {
+ // When partial updates are not allowed, max updates should be 0.
+ {
+ FakeLayerTreeHostClient client(FakeLayerTreeHostClient::DIRECT_3D);
+
+ scoped_ptr<FakeProxy> proxy(new FakeProxy);
+ proxy->GetRendererCapabilities().allow_partial_texture_updates = false;
+ proxy->SetMaxPartialTextureUpdates(5);
+
+ LayerTreeSettings settings;
+ settings.max_partial_texture_updates = 10;
+
+ LayerTreeHostWithProxy host(&client, settings, proxy.Pass());
+ EXPECT_TRUE(host.InitializeOutputSurfaceIfNeeded());
+
+ EXPECT_EQ(0u, host.settings().max_partial_texture_updates);
+ }
+
+ // When partial updates are allowed,
+ // max updates should be limited by the proxy.
+ {
+ FakeLayerTreeHostClient client(FakeLayerTreeHostClient::DIRECT_3D);
+
+ scoped_ptr<FakeProxy> proxy(new FakeProxy);
+ proxy->GetRendererCapabilities().allow_partial_texture_updates = true;
+ proxy->SetMaxPartialTextureUpdates(5);
+
+ LayerTreeSettings settings;
+ settings.max_partial_texture_updates = 10;
+
+ LayerTreeHostWithProxy host(&client, settings, proxy.Pass());
+ EXPECT_TRUE(host.InitializeOutputSurfaceIfNeeded());
+
+ EXPECT_EQ(5u, host.settings().max_partial_texture_updates);
+ }
+
+ // When partial updates are allowed,
+ // max updates should also be limited by the settings.
+ {
+ FakeLayerTreeHostClient client(FakeLayerTreeHostClient::DIRECT_3D);
+
+ scoped_ptr<FakeProxy> proxy(new FakeProxy);
+ proxy->GetRendererCapabilities().allow_partial_texture_updates = true;
+ proxy->SetMaxPartialTextureUpdates(20);
+
+ LayerTreeSettings settings;
+ settings.max_partial_texture_updates = 10;
+
+ LayerTreeHostWithProxy host(&client, settings, proxy.Pass());
+ EXPECT_TRUE(host.InitializeOutputSurfaceIfNeeded());
+
+ EXPECT_EQ(10u, host.settings().max_partial_texture_updates);
+ }
+}
+
+TEST(LayerTreeHostTest, PartialUpdatesWithGLRenderer) {
+ FakeLayerTreeHostClient client(FakeLayerTreeHostClient::DIRECT_3D);
+
+ LayerTreeSettings settings;
+ settings.max_partial_texture_updates = 4;
+
+ scoped_ptr<LayerTreeHost> host =
+ LayerTreeHost::Create(&client, settings, NULL);
+ EXPECT_TRUE(host->InitializeOutputSurfaceIfNeeded());
+ EXPECT_EQ(4u, host->settings().max_partial_texture_updates);
+}
+
+TEST(LayerTreeHostTest, PartialUpdatesWithSoftwareRenderer) {
+ FakeLayerTreeHostClient client(FakeLayerTreeHostClient::DIRECT_SOFTWARE);
+
+ LayerTreeSettings settings;
+ settings.max_partial_texture_updates = 4;
+
+ scoped_ptr<LayerTreeHost> host =
+ LayerTreeHost::Create(&client, settings, NULL);
+ EXPECT_TRUE(host->InitializeOutputSurfaceIfNeeded());
+ EXPECT_EQ(4u, host->settings().max_partial_texture_updates);
+}
+
+TEST(LayerTreeHostTest, PartialUpdatesWithDelegatingRendererAndGLContent) {
+ FakeLayerTreeHostClient client(FakeLayerTreeHostClient::DELEGATED_3D);
+
+ LayerTreeSettings settings;
+ settings.max_partial_texture_updates = 4;
+
+ scoped_ptr<LayerTreeHost> host =
+ LayerTreeHost::Create(&client, settings, NULL);
+ EXPECT_TRUE(host->InitializeOutputSurfaceIfNeeded());
+ EXPECT_EQ(0u, host->settings().max_partial_texture_updates);
+}
+
+TEST(LayerTreeHostTest,
+ PartialUpdatesWithDelegatingRendererAndSoftwareContent) {
+ FakeLayerTreeHostClient client(FakeLayerTreeHostClient::DELEGATED_SOFTWARE);
+
+ LayerTreeSettings settings;
+ settings.max_partial_texture_updates = 4;
+
+ scoped_ptr<LayerTreeHost> host =
+ LayerTreeHost::Create(&client, settings, NULL);
+ EXPECT_TRUE(host->InitializeOutputSurfaceIfNeeded());
+ EXPECT_EQ(0u, host->settings().max_partial_texture_updates);
+}
+
+class LayerTreeHostTestShutdownWithOnlySomeResourcesEvicted
+ : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestShutdownWithOnlySomeResourcesEvicted()
+ : root_layer_(FakeContentLayer::Create(&client_)),
+ child_layer1_(FakeContentLayer::Create(&client_)),
+ child_layer2_(FakeContentLayer::Create(&client_)),
+ num_commits_(0) {}
+
+ virtual void BeginTest() OVERRIDE {
+ layer_tree_host()->SetViewportSize(gfx::Size(100, 100));
+ root_layer_->SetBounds(gfx::Size(100, 100));
+ child_layer1_->SetBounds(gfx::Size(100, 100));
+ child_layer2_->SetBounds(gfx::Size(100, 100));
+ root_layer_->AddChild(child_layer1_);
+ root_layer_->AddChild(child_layer2_);
+ layer_tree_host()->SetRootLayer(root_layer_);
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidSetVisibleOnImplTree(LayerTreeHostImpl* host_impl,
+ bool visible) OVERRIDE {
+ // One backing should remain unevicted.
+ EXPECT_EQ(100u * 100u * 4u * 1u,
+ layer_tree_host()->contents_texture_manager()->MemoryUseBytes());
+ // Make sure that contents textures are marked as having been
+ // purged.
+ EXPECT_TRUE(host_impl->active_tree()->ContentsTexturesPurged());
+ // End the test in this state.
+ EndTest();
+ }
+
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ ++num_commits_;
+ switch (num_commits_) {
+ case 1:
+ // All three backings should have memory.
+ EXPECT_EQ(
+ 100u * 100u * 4u * 3u,
+ layer_tree_host()->contents_texture_manager()->MemoryUseBytes());
+ // Set a new policy that will kick out 1 of the 3 resources.
+ // Because a resource was evicted, a commit will be kicked off.
+ host_impl->SetMemoryPolicy(
+ ManagedMemoryPolicy(100 * 100 * 4 * 2,
+ ManagedMemoryPolicy::CUTOFF_ALLOW_EVERYTHING,
+ 100 * 100 * 4 * 1,
+ ManagedMemoryPolicy::CUTOFF_ALLOW_EVERYTHING,
+ 1000));
+ host_impl->SetDiscardBackBufferWhenNotVisible(true);
+ break;
+ case 2:
+ // Only two backings should have memory.
+ EXPECT_EQ(
+ 100u * 100u * 4u * 2u,
+ layer_tree_host()->contents_texture_manager()->MemoryUseBytes());
+ // Become backgrounded, which will cause 1 more resource to be
+ // evicted.
+ PostSetVisibleToMainThread(false);
+ break;
+ default:
+ // No further commits should happen because this is not visible
+ // anymore.
+ NOTREACHED();
+ break;
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ FakeContentLayerClient client_;
+ scoped_refptr<FakeContentLayer> root_layer_;
+ scoped_refptr<FakeContentLayer> child_layer1_;
+ scoped_refptr<FakeContentLayer> child_layer2_;
+ int num_commits_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostTestShutdownWithOnlySomeResourcesEvicted);
+
+class LayerTreeHostTestLCDNotification : public LayerTreeHostTest {
+ public:
+ class NotificationClient : public ContentLayerClient {
+ public:
+ NotificationClient()
+ : layer_(0), paint_count_(0), lcd_notification_count_(0) {}
+
+ void set_layer(Layer* layer) { layer_ = layer; }
+ int paint_count() const { return paint_count_; }
+ int lcd_notification_count() const { return lcd_notification_count_; }
+
+ virtual void PaintContents(SkCanvas* canvas,
+ gfx::Rect clip,
+ gfx::RectF* opaque) OVERRIDE {
+ ++paint_count_;
+ }
+ virtual void DidChangeLayerCanUseLCDText() OVERRIDE {
+ ++lcd_notification_count_;
+ layer_->SetNeedsDisplay();
+ }
+
+ private:
+ Layer* layer_;
+ int paint_count_;
+ int lcd_notification_count_;
+ };
+
+ virtual void SetupTree() OVERRIDE {
+ scoped_refptr<ContentLayer> root_layer = ContentLayer::Create(&client_);
+ root_layer->SetIsDrawable(true);
+ root_layer->SetBounds(gfx::Size(1, 1));
+
+ layer_tree_host()->SetRootLayer(root_layer);
+ client_.set_layer(root_layer.get());
+
+ // The expecations are based on the assumption that the default
+ // LCD settings are:
+ EXPECT_TRUE(layer_tree_host()->settings().can_use_lcd_text);
+ EXPECT_FALSE(root_layer->can_use_lcd_text());
+
+ LayerTreeHostTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE { PostSetNeedsCommitToMainThread(); }
+ virtual void AfterTest() OVERRIDE {}
+
+ virtual void DidCommit() OVERRIDE {
+ switch (layer_tree_host()->source_frame_number()) {
+ case 1:
+ // The first update consists one LCD notification and one paint.
+ EXPECT_EQ(1, client_.lcd_notification_count());
+ EXPECT_EQ(1, client_.paint_count());
+ // LCD text must have been enabled on the layer.
+ EXPECT_TRUE(layer_tree_host()->root_layer()->can_use_lcd_text());
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 2:
+ // Since nothing changed on layer, there should be no notification
+ // or paint on the second update.
+ EXPECT_EQ(1, client_.lcd_notification_count());
+ EXPECT_EQ(1, client_.paint_count());
+ // LCD text must not have changed.
+ EXPECT_TRUE(layer_tree_host()->root_layer()->can_use_lcd_text());
+ // Change layer opacity that should trigger lcd notification.
+ layer_tree_host()->root_layer()->SetOpacity(.5f);
+ // No need to request a commit - setting opacity will do it.
+ break;
+ default:
+ // Verify that there is not extra commit due to layer invalidation.
+ EXPECT_EQ(3, layer_tree_host()->source_frame_number());
+ // LCD notification count should have incremented due to
+ // change in layer opacity.
+ EXPECT_EQ(2, client_.lcd_notification_count());
+ // Paint count should be incremented due to invalidation.
+ EXPECT_EQ(2, client_.paint_count());
+ // LCD text must have been disabled on the layer due to opacity.
+ EXPECT_FALSE(layer_tree_host()->root_layer()->can_use_lcd_text());
+ EndTest();
+ break;
+ }
+ }
+
+ private:
+ NotificationClient client_;
+};
+
+SINGLE_THREAD_TEST_F(LayerTreeHostTestLCDNotification);
+
+// Verify that the BeginFrame notification is used to initiate rendering.
+class LayerTreeHostTestBeginFrameNotification : public LayerTreeHostTest {
+ public:
+ virtual void InitializeSettings(LayerTreeSettings* settings) OVERRIDE {
+ settings->begin_frame_scheduling_enabled = true;
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ // This will trigger a SetNeedsBeginFrame which will trigger a BeginFrame.
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual bool PrepareToDrawOnThread(
+ LayerTreeHostImpl* host_impl,
+ LayerTreeHostImpl::FrameData* frame,
+ bool result) OVERRIDE {
+ EndTest();
+ return true;
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ base::TimeTicks frame_time_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostTestBeginFrameNotification);
+
+class LayerTreeHostTestBeginFrameNotificationShutdownWhileEnabled
+ : public LayerTreeHostTest {
+ public:
+ virtual void InitializeSettings(LayerTreeSettings* settings) OVERRIDE {
+ settings->begin_frame_scheduling_enabled = true;
+ settings->using_synchronous_renderer_compositor = true;
+ }
+
+ virtual void BeginTest() OVERRIDE { PostSetNeedsCommitToMainThread(); }
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ // The BeginFrame notification is turned off now but will get enabled
+ // once we return. End test while it's enabled.
+ ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&LayerTreeHostTestBeginFrameNotification::EndTest,
+ base::Unretained(this)));
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+};
+
+MULTI_THREAD_TEST_F(
+ LayerTreeHostTestBeginFrameNotificationShutdownWhileEnabled);
+
+class LayerTreeHostTestUninvertibleTransformDoesNotBlockActivation
+ : public LayerTreeHostTest {
+ protected:
+ virtual void InitializeSettings(LayerTreeSettings* settings) OVERRIDE {
+ settings->impl_side_painting = true;
+ }
+
+ virtual void SetupTree() OVERRIDE {
+ LayerTreeHostTest::SetupTree();
+
+ scoped_refptr<Layer> layer = PictureLayer::Create(&client_);
+ layer->SetTransform(gfx::Transform(0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
+ layer->SetBounds(gfx::Size(10, 10));
+ layer_tree_host()->root_layer()->AddChild(layer);
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ }
+
+ FakeContentLayerClient client_;
+};
+
+MULTI_THREAD_TEST_F(
+ LayerTreeHostTestUninvertibleTransformDoesNotBlockActivation);
+
+class LayerTreeHostTestChangeLayerPropertiesInPaintContents
+ : public LayerTreeHostTest {
+ public:
+ class SetBoundsClient : public ContentLayerClient {
+ public:
+ SetBoundsClient() : layer_(0) {}
+
+ void set_layer(Layer* layer) { layer_ = layer; }
+
+ virtual void PaintContents(SkCanvas* canvas,
+ gfx::Rect clip,
+ gfx::RectF* opaque) OVERRIDE {
+ layer_->SetBounds(gfx::Size(2, 2));
+ }
+
+ virtual void DidChangeLayerCanUseLCDText() OVERRIDE {}
+
+ private:
+ Layer* layer_;
+ };
+
+ LayerTreeHostTestChangeLayerPropertiesInPaintContents() : num_commits_(0) {}
+
+ virtual void SetupTree() OVERRIDE {
+ scoped_refptr<ContentLayer> root_layer = ContentLayer::Create(&client_);
+ root_layer->SetIsDrawable(true);
+ root_layer->SetBounds(gfx::Size(1, 1));
+
+ layer_tree_host()->SetRootLayer(root_layer);
+ client_.set_layer(root_layer.get());
+
+ LayerTreeHostTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE { PostSetNeedsCommitToMainThread(); }
+ virtual void AfterTest() OVERRIDE {}
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ num_commits_++;
+ if (num_commits_ == 1) {
+ LayerImpl* root_layer = host_impl->active_tree()->root_layer();
+ EXPECT_SIZE_EQ(gfx::Size(1, 1), root_layer->bounds());
+ } else {
+ LayerImpl* root_layer = host_impl->active_tree()->root_layer();
+ EXPECT_SIZE_EQ(gfx::Size(2, 2), root_layer->bounds());
+ EndTest();
+ }
+ }
+
+ private:
+ SetBoundsClient client_;
+ int num_commits_;
+};
+
+SINGLE_THREAD_TEST_F(LayerTreeHostTestChangeLayerPropertiesInPaintContents);
+
+class MockIOSurfaceWebGraphicsContext3D : public FakeWebGraphicsContext3D {
+ public:
+ MockIOSurfaceWebGraphicsContext3D()
+ : FakeWebGraphicsContext3D() {}
+
+ virtual WebKit::WebGLId createTexture() OVERRIDE {
+ return 1;
+ }
+
+ virtual WebKit::WebString getString(WebKit::WGC3Denum name) OVERRIDE {
+ if (name == GL_EXTENSIONS) {
+ return WebKit::WebString(
+ "GL_CHROMIUM_iosurface GL_ARB_texture_rectangle");
+ }
+ return WebKit::WebString();
+ }
+
+ MOCK_METHOD1(activeTexture, void(WebKit::WGC3Denum texture));
+ MOCK_METHOD2(bindTexture, void(WebKit::WGC3Denum target,
+ WebKit::WebGLId texture_id));
+ MOCK_METHOD3(texParameteri, void(WebKit::WGC3Denum target,
+ WebKit::WGC3Denum pname,
+ WebKit::WGC3Dint param));
+ MOCK_METHOD5(texImageIOSurface2DCHROMIUM, void(WebKit::WGC3Denum target,
+ WebKit::WGC3Dint width,
+ WebKit::WGC3Dint height,
+ WebKit::WGC3Duint ioSurfaceId,
+ WebKit::WGC3Duint plane));
+ MOCK_METHOD4(drawElements, void(WebKit::WGC3Denum mode,
+ WebKit::WGC3Dsizei count,
+ WebKit::WGC3Denum type,
+ WebKit::WGC3Dintptr offset));
+};
+
+
+class LayerTreeHostTestIOSurfaceDrawing : public LayerTreeHostTest {
+ protected:
+ virtual scoped_ptr<OutputSurface> CreateOutputSurface(bool fallback)
+ OVERRIDE {
+ scoped_ptr<MockIOSurfaceWebGraphicsContext3D> context(
+ new MockIOSurfaceWebGraphicsContext3D);
+ mock_context_ = context.get();
+ scoped_ptr<OutputSurface> output_surface = FakeOutputSurface::Create3d(
+ context.PassAs<WebKit::WebGraphicsContext3D>()).PassAs<OutputSurface>();
+ return output_surface.Pass();
+ }
+
+ virtual void SetupTree() OVERRIDE {
+ LayerTreeHostTest::SetupTree();
+
+ layer_tree_host()->root_layer()->SetIsDrawable(false);
+
+ io_surface_id_ = 9;
+ io_surface_size_ = gfx::Size(6, 7);
+
+ scoped_refptr<IOSurfaceLayer> io_surface_layer = IOSurfaceLayer::Create();
+ io_surface_layer->SetBounds(gfx::Size(10, 10));
+ io_surface_layer->SetAnchorPoint(gfx::PointF());
+ io_surface_layer->SetIsDrawable(true);
+ io_surface_layer->SetIOSurfaceProperties(io_surface_id_, io_surface_size_);
+ layer_tree_host()->root_layer()->AddChild(io_surface_layer);
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ // In WillDraw, the IOSurfaceLayer sets up the io surface texture.
+
+ EXPECT_CALL(*mock_context_, activeTexture(_))
+ .Times(0);
+ EXPECT_CALL(*mock_context_, bindTexture(GL_TEXTURE_RECTANGLE_ARB, 1))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*mock_context_, texParameteri(GL_TEXTURE_RECTANGLE_ARB,
+ GL_TEXTURE_MIN_FILTER,
+ GL_LINEAR))
+ .Times(1);
+ EXPECT_CALL(*mock_context_, texParameteri(GL_TEXTURE_RECTANGLE_ARB,
+ GL_TEXTURE_MAG_FILTER,
+ GL_LINEAR))
+ .Times(1);
+ EXPECT_CALL(*mock_context_, texParameteri(GL_TEXTURE_RECTANGLE_ARB,
+ GL_TEXTURE_WRAP_S,
+ GL_CLAMP_TO_EDGE))
+ .Times(1);
+ EXPECT_CALL(*mock_context_, texParameteri(GL_TEXTURE_RECTANGLE_ARB,
+ GL_TEXTURE_WRAP_T,
+ GL_CLAMP_TO_EDGE))
+ .Times(1);
+
+ EXPECT_CALL(*mock_context_, texImageIOSurface2DCHROMIUM(
+ GL_TEXTURE_RECTANGLE_ARB,
+ io_surface_size_.width(),
+ io_surface_size_.height(),
+ io_surface_id_,
+ 0))
+ .Times(1);
+
+ EXPECT_CALL(*mock_context_, bindTexture(_, 0))
+ .Times(AnyNumber());
+ }
+
+ virtual bool PrepareToDrawOnThread(
+ LayerTreeHostImpl* host_impl,
+ LayerTreeHostImpl::FrameData* frame,
+ bool result) OVERRIDE {
+ Mock::VerifyAndClearExpectations(&mock_context_);
+
+ // The io surface layer's texture is drawn.
+ EXPECT_CALL(*mock_context_, activeTexture(GL_TEXTURE0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*mock_context_, bindTexture(GL_TEXTURE_RECTANGLE_ARB, 1))
+ .Times(1);
+ EXPECT_CALL(*mock_context_, drawElements(GL_TRIANGLES, 6, _, _))
+ .Times(AtLeast(1));
+
+ return result;
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ Mock::VerifyAndClearExpectations(&mock_context_);
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ int io_surface_id_;
+ MockIOSurfaceWebGraphicsContext3D* mock_context_;
+ gfx::Size io_surface_size_;
+};
+
+// TODO(danakj): IOSurface layer can not be transported. crbug.com/239335
+SINGLE_AND_MULTI_THREAD_DIRECT_RENDERER_TEST_F(
+ LayerTreeHostTestIOSurfaceDrawing);
+
+class LayerTreeHostTestAsyncReadback : public LayerTreeHostTest {
+ protected:
+ virtual void SetupTree() OVERRIDE {
+ root = FakeContentLayer::Create(&client_);
+ root->SetBounds(gfx::Size(20, 20));
+
+ child = FakeContentLayer::Create(&client_);
+ child->SetBounds(gfx::Size(10, 10));
+ root->AddChild(child);
+
+ layer_tree_host()->SetRootLayer(root);
+ LayerTreeHostTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ WaitForCallback();
+ }
+
+ void WaitForCallback() {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &LayerTreeHostTestAsyncReadback::NextStep,
+ base::Unretained(this)));
+ }
+
+ void NextStep() {
+ int frame = layer_tree_host()->source_frame_number();
+ switch (frame) {
+ case 1:
+ child->RequestCopyOfOutput(CopyOutputRequest::CreateBitmapRequest(
+ base::Bind(&LayerTreeHostTestAsyncReadback::CopyOutputCallback,
+ base::Unretained(this))));
+ EXPECT_EQ(0u, callbacks_.size());
+ break;
+ case 2:
+ if (callbacks_.size() < 1u) {
+ WaitForCallback();
+ return;
+ }
+ EXPECT_EQ(1u, callbacks_.size());
+ EXPECT_EQ(gfx::Size(10, 10).ToString(), callbacks_[0].ToString());
+
+ child->RequestCopyOfOutput(CopyOutputRequest::CreateBitmapRequest(
+ base::Bind(&LayerTreeHostTestAsyncReadback::CopyOutputCallback,
+ base::Unretained(this))));
+ root->RequestCopyOfOutput(CopyOutputRequest::CreateBitmapRequest(
+ base::Bind(&LayerTreeHostTestAsyncReadback::CopyOutputCallback,
+ base::Unretained(this))));
+ child->RequestCopyOfOutput(CopyOutputRequest::CreateBitmapRequest(
+ base::Bind(&LayerTreeHostTestAsyncReadback::CopyOutputCallback,
+ base::Unretained(this))));
+ EXPECT_EQ(1u, callbacks_.size());
+ break;
+ case 3:
+ if (callbacks_.size() < 4u) {
+ WaitForCallback();
+ return;
+ }
+ EXPECT_EQ(4u, callbacks_.size());
+ // The child was copied to a bitmap and passed back twice.
+ EXPECT_EQ(gfx::Size(10, 10).ToString(), callbacks_[1].ToString());
+ EXPECT_EQ(gfx::Size(10, 10).ToString(), callbacks_[2].ToString());
+ // The root was copied to a bitmap and passed back also.
+ EXPECT_EQ(gfx::Size(20, 20).ToString(), callbacks_[3].ToString());
+ EndTest();
+ break;
+ }
+ }
+
+ void CopyOutputCallback(scoped_ptr<CopyOutputResult> result) {
+ EXPECT_TRUE(layer_tree_host()->proxy()->IsMainThread());
+ EXPECT_TRUE(result->HasBitmap());
+ scoped_ptr<SkBitmap> bitmap = result->TakeBitmap().Pass();
+ EXPECT_EQ(result->size().ToString(),
+ gfx::Size(bitmap->width(), bitmap->height()).ToString());
+ callbacks_.push_back(result->size());
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ EXPECT_EQ(4u, callbacks_.size());
+ }
+
+ virtual scoped_ptr<OutputSurface> CreateOutputSurface(bool fallback)
+ OVERRIDE {
+ if (use_gl_renderer_)
+ return FakeOutputSurface::Create3d().PassAs<OutputSurface>();
+ return FakeOutputSurface::CreateSoftware(
+ make_scoped_ptr(new SoftwareOutputDevice)).PassAs<OutputSurface>();
+ }
+
+ bool use_gl_renderer_;
+ std::vector<gfx::Size> callbacks_;
+ FakeContentLayerClient client_;
+ scoped_refptr<FakeContentLayer> root;
+ scoped_refptr<FakeContentLayer> child;
+};
+
+// Readback can't be done with a delegating renderer.
+TEST_F(LayerTreeHostTestAsyncReadback, GLRenderer_RunSingleThread) {
+ use_gl_renderer_ = true;
+ RunTest(false, false, false);
+}
+
+TEST_F(LayerTreeHostTestAsyncReadback,
+ GLRenderer_RunMultiThread_MainThreadPainting) {
+ use_gl_renderer_ = true;
+ RunTest(true, false, false);
+}
+
+TEST_F(LayerTreeHostTestAsyncReadback,
+ GLRenderer_RunMultiThread_ImplSidePainting) {
+ use_gl_renderer_ = true;
+ RunTest(true, false, true);
+}
+
+TEST_F(LayerTreeHostTestAsyncReadback, SoftwareRenderer_RunSingleThread) {
+ use_gl_renderer_ = false;
+ RunTest(false, false, false);
+}
+
+TEST_F(LayerTreeHostTestAsyncReadback,
+ SoftwareRenderer_RunMultiThread_MainThreadPainting) {
+ use_gl_renderer_ = false;
+ RunTest(true, false, false);
+}
+
+TEST_F(LayerTreeHostTestAsyncReadback,
+ SoftwareRenderer_RunMultiThread_ImplSidePainting) {
+ use_gl_renderer_ = false;
+ RunTest(true, false, true);
+}
+
+class LayerTreeHostTestAsyncReadbackLayerDestroyed : public LayerTreeHostTest {
+ protected:
+ virtual void SetupTree() OVERRIDE {
+ root_ = FakeContentLayer::Create(&client_);
+ root_->SetBounds(gfx::Size(20, 20));
+
+ main_destroyed_ = FakeContentLayer::Create(&client_);
+ main_destroyed_->SetBounds(gfx::Size(15, 15));
+ root_->AddChild(main_destroyed_);
+
+ impl_destroyed_ = FakeContentLayer::Create(&client_);
+ impl_destroyed_->SetBounds(gfx::Size(10, 10));
+ root_->AddChild(impl_destroyed_);
+
+ layer_tree_host()->SetRootLayer(root_);
+ LayerTreeHostTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ callback_count_ = 0;
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommit() OVERRIDE {
+ int frame = layer_tree_host()->source_frame_number();
+ switch (frame) {
+ case 1:
+ main_destroyed_->RequestCopyOfOutput(
+ CopyOutputRequest::CreateBitmapRequest(base::Bind(
+ &LayerTreeHostTestAsyncReadbackLayerDestroyed::
+ CopyOutputCallback,
+ base::Unretained(this))));
+ impl_destroyed_->RequestCopyOfOutput(
+ CopyOutputRequest::CreateBitmapRequest(base::Bind(
+ &LayerTreeHostTestAsyncReadbackLayerDestroyed::
+ CopyOutputCallback,
+ base::Unretained(this))));
+ EXPECT_EQ(0, callback_count_);
+
+ // Destroy the main thread layer right away.
+ main_destroyed_->RemoveFromParent();
+ main_destroyed_ = NULL;
+
+ // Should callback with a NULL bitmap.
+ EXPECT_EQ(1, callback_count_);
+
+ // Prevent drawing so we can't make a copy of the impl_destroyed layer.
+ layer_tree_host()->SetViewportSize(gfx::Size());
+ break;
+ case 2:
+ // Flush the message loops and make sure the callbacks run.
+ layer_tree_host()->SetNeedsCommit();
+ break;
+ case 3:
+ // No drawing means no readback yet.
+ EXPECT_EQ(1, callback_count_);
+
+ // Destroy the impl thread layer.
+ impl_destroyed_->RemoveFromParent();
+ impl_destroyed_ = NULL;
+
+ // No callback yet because it's on the impl side.
+ EXPECT_EQ(1, callback_count_);
+ break;
+ case 4:
+ // Flush the message loops and make sure the callbacks run.
+ layer_tree_host()->SetNeedsCommit();
+ break;
+ case 5:
+ // We should get another callback with a NULL bitmap.
+ EXPECT_EQ(2, callback_count_);
+ EndTest();
+ break;
+ }
+ }
+
+ void CopyOutputCallback(scoped_ptr<CopyOutputResult> result) {
+ EXPECT_TRUE(layer_tree_host()->proxy()->IsMainThread());
+ EXPECT_TRUE(result->IsEmpty());
+ ++callback_count_;
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ int callback_count_;
+ FakeContentLayerClient client_;
+ scoped_refptr<FakeContentLayer> root_;
+ scoped_refptr<FakeContentLayer> main_destroyed_;
+ scoped_refptr<FakeContentLayer> impl_destroyed_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestAsyncReadbackLayerDestroyed);
+
+class LayerTreeHostTestAsyncReadbackInHiddenSubtree : public LayerTreeHostTest {
+ protected:
+ virtual void SetupTree() OVERRIDE {
+ root_ = FakeContentLayer::Create(&client_);
+ root_->SetBounds(gfx::Size(20, 20));
+
+ grand_parent_layer_ = FakeContentLayer::Create(&client_);
+ grand_parent_layer_->SetBounds(gfx::Size(15, 15));
+ root_->AddChild(grand_parent_layer_);
+
+ // parent_layer_ owns a render surface.
+ parent_layer_ = FakeContentLayer::Create(&client_);
+ parent_layer_->SetBounds(gfx::Size(15, 15));
+ parent_layer_->SetForceRenderSurface(true);
+ grand_parent_layer_->AddChild(parent_layer_);
+
+ copy_layer_ = FakeContentLayer::Create(&client_);
+ copy_layer_->SetBounds(gfx::Size(10, 10));
+ parent_layer_->AddChild(copy_layer_);
+
+ layer_tree_host()->SetRootLayer(root_);
+ LayerTreeHostTest::SetupTree();
+ }
+
+ void AddCopyRequest(Layer* layer) {
+ layer->RequestCopyOfOutput(
+ CopyOutputRequest::CreateBitmapRequest(base::Bind(
+ &LayerTreeHostTestAsyncReadbackInHiddenSubtree::CopyOutputCallback,
+ base::Unretained(this))));
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ callback_count_ = 0;
+ PostSetNeedsCommitToMainThread();
+
+ AddCopyRequest(copy_layer_.get());
+ }
+
+ void CopyOutputCallback(scoped_ptr<CopyOutputResult> result) {
+ EXPECT_TRUE(layer_tree_host()->proxy()->IsMainThread());
+ EXPECT_EQ(copy_layer_->bounds().ToString(), result->size().ToString());
+ ++callback_count_;
+
+ switch (callback_count_) {
+ case 1:
+ // Hide the copy request layer.
+ grand_parent_layer_->SetHideLayerAndSubtree(false);
+ parent_layer_->SetHideLayerAndSubtree(false);
+ copy_layer_->SetHideLayerAndSubtree(true);
+ AddCopyRequest(copy_layer_.get());
+ break;
+ case 2:
+ // Hide the copy request layer's parent only.
+ grand_parent_layer_->SetHideLayerAndSubtree(false);
+ parent_layer_->SetHideLayerAndSubtree(true);
+ copy_layer_->SetHideLayerAndSubtree(false);
+ AddCopyRequest(copy_layer_.get());
+ break;
+ case 3:
+ // Hide the copy request layer's grand parent only.
+ grand_parent_layer_->SetHideLayerAndSubtree(true);
+ parent_layer_->SetHideLayerAndSubtree(false);
+ copy_layer_->SetHideLayerAndSubtree(false);
+ AddCopyRequest(copy_layer_.get());
+ break;
+ case 4:
+ // Hide the copy request layer's parent and grandparent.
+ grand_parent_layer_->SetHideLayerAndSubtree(true);
+ parent_layer_->SetHideLayerAndSubtree(true);
+ copy_layer_->SetHideLayerAndSubtree(false);
+ AddCopyRequest(copy_layer_.get());
+ break;
+ case 5:
+ // Hide the copy request layer as well as its parent and grandparent.
+ grand_parent_layer_->SetHideLayerAndSubtree(true);
+ parent_layer_->SetHideLayerAndSubtree(true);
+ copy_layer_->SetHideLayerAndSubtree(true);
+ AddCopyRequest(copy_layer_.get());
+ break;
+ case 6:
+ EndTest();
+ break;
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ int callback_count_;
+ FakeContentLayerClient client_;
+ scoped_refptr<FakeContentLayer> root_;
+ scoped_refptr<FakeContentLayer> grand_parent_layer_;
+ scoped_refptr<FakeContentLayer> parent_layer_;
+ scoped_refptr<FakeContentLayer> copy_layer_;
+};
+
+// No output to copy for delegated renderers.
+SINGLE_AND_MULTI_THREAD_DIRECT_RENDERER_TEST_F(
+ LayerTreeHostTestAsyncReadbackInHiddenSubtree);
+
+class LayerTreeHostTestHiddenSurfaceNotAllocatedForSubtreeCopyRequest
+ : public LayerTreeHostTest {
+ protected:
+ virtual void InitializeSettings(LayerTreeSettings* settings) OVERRIDE {
+ settings->cache_render_pass_contents = true;
+ }
+
+ virtual void SetupTree() OVERRIDE {
+ root_ = FakeContentLayer::Create(&client_);
+ root_->SetBounds(gfx::Size(20, 20));
+
+ grand_parent_layer_ = FakeContentLayer::Create(&client_);
+ grand_parent_layer_->SetBounds(gfx::Size(15, 15));
+ grand_parent_layer_->SetHideLayerAndSubtree(true);
+ root_->AddChild(grand_parent_layer_);
+
+ // parent_layer_ owns a render surface.
+ parent_layer_ = FakeContentLayer::Create(&client_);
+ parent_layer_->SetBounds(gfx::Size(15, 15));
+ parent_layer_->SetForceRenderSurface(true);
+ grand_parent_layer_->AddChild(parent_layer_);
+
+ copy_layer_ = FakeContentLayer::Create(&client_);
+ copy_layer_->SetBounds(gfx::Size(10, 10));
+ parent_layer_->AddChild(copy_layer_);
+
+ layer_tree_host()->SetRootLayer(root_);
+ LayerTreeHostTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ did_draw_ = false;
+ PostSetNeedsCommitToMainThread();
+
+ copy_layer_->RequestCopyOfOutput(
+ CopyOutputRequest::CreateBitmapRequest(base::Bind(
+ &LayerTreeHostTestHiddenSurfaceNotAllocatedForSubtreeCopyRequest::
+ CopyOutputCallback,
+ base::Unretained(this))));
+ }
+
+ void CopyOutputCallback(scoped_ptr<CopyOutputResult> result) {
+ EXPECT_TRUE(layer_tree_host()->proxy()->IsMainThread());
+ EXPECT_EQ(copy_layer_->bounds().ToString(), result->size().ToString());
+ EndTest();
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ Renderer* renderer = host_impl->renderer();
+
+ LayerImpl* root = host_impl->active_tree()->root_layer();
+ LayerImpl* grand_parent = root->children()[0];
+ LayerImpl* parent = grand_parent->children()[0];
+ LayerImpl* copy_layer = parent->children()[0];
+
+ // |parent| owns a surface, but it was hidden and not part of the copy
+ // request so it should not allocate any resource.
+ EXPECT_FALSE(renderer->HaveCachedResourcesForRenderPassId(
+ parent->render_surface()->RenderPassId()));
+
+ // |copy_layer| should have been rendered to a texture since it was needed
+ // for a copy request.
+ EXPECT_TRUE(renderer->HaveCachedResourcesForRenderPassId(
+ copy_layer->render_surface()->RenderPassId()));
+
+ did_draw_ = true;
+ }
+
+ virtual void AfterTest() OVERRIDE { EXPECT_TRUE(did_draw_); }
+
+ FakeContentLayerClient client_;
+ bool did_draw_;
+ scoped_refptr<FakeContentLayer> root_;
+ scoped_refptr<FakeContentLayer> grand_parent_layer_;
+ scoped_refptr<FakeContentLayer> parent_layer_;
+ scoped_refptr<FakeContentLayer> copy_layer_;
+};
+
+// No output to copy for delegated renderers.
+SINGLE_AND_MULTI_THREAD_DIRECT_RENDERER_TEST_F(
+ LayerTreeHostTestHiddenSurfaceNotAllocatedForSubtreeCopyRequest);
+
+class LayerTreeHostTestAsyncReadbackClippedOut : public LayerTreeHostTest {
+ protected:
+ virtual void SetupTree() OVERRIDE {
+ root_ = FakeContentLayer::Create(&client_);
+ root_->SetBounds(gfx::Size(20, 20));
+
+ parent_layer_ = FakeContentLayer::Create(&client_);
+ parent_layer_->SetBounds(gfx::Size(15, 15));
+ parent_layer_->SetMasksToBounds(true);
+ root_->AddChild(parent_layer_);
+
+ copy_layer_ = FakeContentLayer::Create(&client_);
+ copy_layer_->SetPosition(gfx::Point(15, 15));
+ copy_layer_->SetBounds(gfx::Size(10, 10));
+ parent_layer_->AddChild(copy_layer_);
+
+ layer_tree_host()->SetRootLayer(root_);
+ LayerTreeHostTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+
+ copy_layer_->RequestCopyOfOutput(
+ CopyOutputRequest::CreateBitmapRequest(base::Bind(
+ &LayerTreeHostTestAsyncReadbackClippedOut::CopyOutputCallback,
+ base::Unretained(this))));
+ }
+
+ void CopyOutputCallback(scoped_ptr<CopyOutputResult> result) {
+ // We should still get a callback with no output if the copy requested layer
+ // was completely clipped away.
+ EXPECT_TRUE(layer_tree_host()->proxy()->IsMainThread());
+ EXPECT_EQ(gfx::Size().ToString(), result->size().ToString());
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ FakeContentLayerClient client_;
+ scoped_refptr<FakeContentLayer> root_;
+ scoped_refptr<FakeContentLayer> parent_layer_;
+ scoped_refptr<FakeContentLayer> copy_layer_;
+};
+
+// No output to copy for delegated renderers.
+SINGLE_AND_MULTI_THREAD_DIRECT_RENDERER_TEST_F(
+ LayerTreeHostTestAsyncReadbackClippedOut);
+
+class LayerTreeHostTestAsyncTwoReadbacksWithoutDraw : public LayerTreeHostTest {
+ protected:
+ virtual void SetupTree() OVERRIDE {
+ root_ = FakeContentLayer::Create(&client_);
+ root_->SetBounds(gfx::Size(20, 20));
+
+ copy_layer_ = FakeContentLayer::Create(&client_);
+ copy_layer_->SetBounds(gfx::Size(10, 10));
+ root_->AddChild(copy_layer_);
+
+ layer_tree_host()->SetRootLayer(root_);
+ LayerTreeHostTest::SetupTree();
+ }
+
+ void AddCopyRequest(Layer* layer) {
+ layer->RequestCopyOfOutput(
+ CopyOutputRequest::CreateBitmapRequest(base::Bind(
+ &LayerTreeHostTestAsyncTwoReadbacksWithoutDraw::CopyOutputCallback,
+ base::Unretained(this))));
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ saw_copy_request_ = false;
+ callback_count_ = 0;
+ PostSetNeedsCommitToMainThread();
+
+ // Prevent drawing.
+ layer_tree_host()->SetViewportSize(gfx::Size(0, 0));
+
+ AddCopyRequest(copy_layer_.get());
+ }
+
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ if (impl->active_tree()->source_frame_number() == 0) {
+ LayerImpl* root = impl->active_tree()->root_layer();
+ EXPECT_TRUE(root->children()[0]->HasCopyRequest());
+ saw_copy_request_ = true;
+ }
+ }
+
+ virtual void DidCommit() OVERRIDE {
+ if (layer_tree_host()->source_frame_number() == 1) {
+ // Allow drawing.
+ layer_tree_host()->SetViewportSize(gfx::Size(root_->bounds()));
+
+ AddCopyRequest(copy_layer_.get());
+ }
+ }
+
+ void CopyOutputCallback(scoped_ptr<CopyOutputResult> result) {
+ EXPECT_TRUE(layer_tree_host()->proxy()->IsMainThread());
+ EXPECT_EQ(copy_layer_->bounds().ToString(), result->size().ToString());
+ ++callback_count_;
+
+ if (callback_count_ == 2)
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE { EXPECT_TRUE(saw_copy_request_); }
+
+ bool saw_copy_request_;
+ int callback_count_;
+ FakeContentLayerClient client_;
+ scoped_refptr<FakeContentLayer> root_;
+ scoped_refptr<FakeContentLayer> copy_layer_;
+};
+
+// No output to copy for delegated renderers.
+SINGLE_AND_MULTI_THREAD_DIRECT_RENDERER_TEST_F(
+ LayerTreeHostTestAsyncTwoReadbacksWithoutDraw);
+
+class LayerTreeHostTestNumFramesPending : public LayerTreeHostTest {
+ public:
+ virtual void BeginTest() OVERRIDE {
+ frame_ = 0;
+ PostSetNeedsCommitToMainThread();
+ }
+
+ // Round 1: commit + draw
+ // Round 2: commit only (no draw/swap)
+ // Round 3: draw only (no commit)
+ // Round 4: composite & readback (2 commits, no draw/swap)
+ // Round 5: commit + draw
+
+ virtual void DidCommit() OVERRIDE {
+ int commit = layer_tree_host()->source_frame_number();
+ switch (commit) {
+ case 2:
+ // Round 2 done.
+ EXPECT_EQ(1, frame_);
+ layer_tree_host()->SetNeedsRedraw();
+ break;
+ case 3:
+ // CompositeAndReadback in Round 4, first commit.
+ EXPECT_EQ(2, frame_);
+ break;
+ case 4:
+ // Round 4 done.
+ EXPECT_EQ(2, frame_);
+ layer_tree_host()->SetNeedsCommit();
+ layer_tree_host()->SetNeedsRedraw();
+ break;
+ }
+ }
+
+ virtual void DidCompleteSwapBuffers() OVERRIDE {
+ int commit = layer_tree_host()->source_frame_number();
+ ++frame_;
+ char pixels[4] = {0};
+ switch (frame_) {
+ case 1:
+ // Round 1 done.
+ EXPECT_EQ(1, commit);
+ layer_tree_host()->SetNeedsCommit();
+ break;
+ case 2:
+ // Round 3 done.
+ EXPECT_EQ(2, commit);
+ layer_tree_host()->CompositeAndReadback(pixels, gfx::Rect(0, 0, 1, 1));
+ break;
+ case 3:
+ // Round 5 done.
+ EXPECT_EQ(5, commit);
+ EndTest();
+ break;
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ protected:
+ int frame_;
+};
+
+TEST_F(LayerTreeHostTestNumFramesPending, DelegatingRenderer) {
+ RunTest(true, true, true);
+}
+
+TEST_F(LayerTreeHostTestNumFramesPending, GLRenderer) {
+ RunTest(true, false, true);
+}
+
+class LayerTreeHostTestDeferredInitialize : public LayerTreeHostTest {
+ public:
+ virtual void InitializeSettings(LayerTreeSettings* settings) OVERRIDE {
+ // PictureLayer can only be used with impl side painting enabled.
+ settings->impl_side_painting = true;
+ settings->solid_color_scrollbars = true;
+ }
+
+ virtual void SetupTree() OVERRIDE {
+ layer_ = FakePictureLayer::Create(&client_);
+ // Force commits to not be aborted so new frames get drawn, otherwise
+ // the renderer gets deferred initialized but nothing new needs drawing.
+ layer_->set_always_update_resources(true);
+ layer_tree_host()->SetRootLayer(layer_);
+ LayerTreeHostTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ did_initialize_gl_ = false;
+ did_release_gl_ = false;
+ last_source_frame_number_drawn_ = -1; // Never drawn.
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual scoped_ptr<OutputSurface> CreateOutputSurface(bool fallback)
+ OVERRIDE {
+ scoped_ptr<TestWebGraphicsContext3D> context3d(
+ TestWebGraphicsContext3D::Create());
+ context3d->set_support_swapbuffers_complete_callback(false);
+
+ return FakeOutputSurface::CreateDeferredGL(
+ scoped_ptr<SoftwareOutputDevice>(new SoftwareOutputDevice))
+ .PassAs<OutputSurface>();
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ ASSERT_TRUE(host_impl->RootLayer());
+ FakePictureLayerImpl* layer_impl =
+ static_cast<FakePictureLayerImpl*>(host_impl->RootLayer());
+
+ // The same frame can be draw multiple times if new visible tiles are
+ // rasterized. But we want to make sure we only post DeferredInitialize
+ // and ReleaseGL once, so early out if the same frame is drawn again.
+ if (last_source_frame_number_drawn_ ==
+ host_impl->active_tree()->source_frame_number())
+ return;
+
+ last_source_frame_number_drawn_ =
+ host_impl->active_tree()->source_frame_number();
+
+ if (!did_initialize_gl_) {
+ EXPECT_LE(1u, layer_impl->append_quads_count());
+ ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &LayerTreeHostTestDeferredInitialize::DeferredInitializeAndRedraw,
+ base::Unretained(this),
+ base::Unretained(host_impl)));
+ } else if (did_initialize_gl_ && !did_release_gl_) {
+ EXPECT_LE(2u, layer_impl->append_quads_count());
+ ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &LayerTreeHostTestDeferredInitialize::ReleaseGLAndRedraw,
+ base::Unretained(this),
+ base::Unretained(host_impl)));
+ } else if (did_initialize_gl_ && did_release_gl_) {
+ EXPECT_LE(3u, layer_impl->append_quads_count());
+ EndTest();
+ }
+ }
+
+ void DeferredInitializeAndRedraw(LayerTreeHostImpl* host_impl) {
+ EXPECT_FALSE(did_initialize_gl_);
+ // SetAndInitializeContext3D calls SetNeedsCommit.
+ EXPECT_TRUE(static_cast<FakeOutputSurface*>(host_impl->output_surface())
+ ->SetAndInitializeContext3D(
+ scoped_ptr<WebKit::WebGraphicsContext3D>(
+ TestWebGraphicsContext3D::Create())));
+ did_initialize_gl_ = true;
+ }
+
+ void ReleaseGLAndRedraw(LayerTreeHostImpl* host_impl) {
+ EXPECT_TRUE(did_initialize_gl_);
+ EXPECT_FALSE(did_release_gl_);
+ // ReleaseGL calls SetNeedsCommit.
+ static_cast<FakeOutputSurface*>(host_impl->output_surface())->ReleaseGL();
+ did_release_gl_ = true;
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ EXPECT_TRUE(did_initialize_gl_);
+ EXPECT_TRUE(did_release_gl_);
+ }
+
+ private:
+ FakeContentLayerClient client_;
+ scoped_refptr<FakePictureLayer> layer_;
+ bool did_initialize_gl_;
+ bool did_release_gl_;
+ int last_source_frame_number_drawn_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostTestDeferredInitialize);
+
+// Test for UI Resource management.
+class LayerTreeHostTestUIResource : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestUIResource() : num_ui_resources_(0), num_commits_(0) {}
+
+ virtual void BeginTest() OVERRIDE { PostSetNeedsCommitToMainThread(); }
+
+ virtual void DidCommit() OVERRIDE {
+ int frame = num_commits_;
+ switch (frame) {
+ case 1:
+ CreateResource();
+ CreateResource();
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 2:
+ // Usually ScopedUIResource are deleted from the manager in their
+ // destructor. Here we just want to test that a direct call to
+ // DeleteUIResource works.
+ layer_tree_host()->DeleteUIResource(ui_resources_[0]->id());
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 3:
+ // DeleteUIResource can be called with an invalid id.
+ layer_tree_host()->DeleteUIResource(ui_resources_[0]->id());
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 4:
+ CreateResource();
+ CreateResource();
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 5:
+ ClearResources();
+ EndTest();
+ break;
+ }
+ }
+
+ void PerformTest(LayerTreeHostImpl* impl) {
+ TestWebGraphicsContext3D* context = static_cast<TestWebGraphicsContext3D*>(
+ impl->output_surface()->context3d());
+
+ int frame = num_commits_;
+ switch (frame) {
+ case 1:
+ ASSERT_EQ(0u, context->NumTextures());
+ break;
+ case 2:
+ // Created two textures.
+ ASSERT_EQ(2u, context->NumTextures());
+ break;
+ case 3:
+ // One texture left after one deletion.
+ ASSERT_EQ(1u, context->NumTextures());
+ break;
+ case 4:
+ // Resource manager state should not change when delete is called on an
+ // invalid id.
+ ASSERT_EQ(1u, context->NumTextures());
+ break;
+ case 5:
+ // Creation after deletion: two more creates should total up to
+ // three textures.
+ ASSERT_EQ(3u, context->NumTextures());
+ break;
+ }
+ }
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ ++num_commits_;
+ if (!layer_tree_host()->settings().impl_side_painting)
+ PerformTest(impl);
+ }
+
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ if (layer_tree_host()->settings().impl_side_painting)
+ PerformTest(impl);
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ // Must clear all resources before exiting.
+ void ClearResources() {
+ for (int i = 0; i < num_ui_resources_; i++)
+ ui_resources_[i].reset();
+ }
+
+ void CreateResource() {
+ ui_resources_[num_ui_resources_++] =
+ FakeScopedUIResource::Create(layer_tree_host());
+ }
+
+ scoped_ptr<FakeScopedUIResource> ui_resources_[5];
+ int num_ui_resources_;
+ int num_commits_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostTestUIResource);
+
+class PushPropertiesCountingLayer : public Layer {
+ public:
+ static scoped_refptr<PushPropertiesCountingLayer> Create() {
+ return new PushPropertiesCountingLayer();
+ }
+
+ virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE {
+ Layer::PushPropertiesTo(layer);
+ push_properties_count_++;
+ if (persist_needs_push_properties_)
+ needs_push_properties_ = true;
+ }
+
+ size_t push_properties_count() const { return push_properties_count_; }
+ void reset_push_properties_count() { push_properties_count_ = 0; }
+
+ void set_persist_needs_push_properties(bool persist) {
+ persist_needs_push_properties_ = persist;
+ }
+
+ private:
+ PushPropertiesCountingLayer()
+ : push_properties_count_(0),
+ persist_needs_push_properties_(false) {
+ SetAnchorPoint(gfx::PointF());
+ SetBounds(gfx::Size(1, 1));
+ SetIsDrawable(true);
+ }
+ virtual ~PushPropertiesCountingLayer() {}
+
+ size_t push_properties_count_;
+ bool persist_needs_push_properties_;
+};
+
+class LayerTreeHostTestLayersPushProperties : public LayerTreeHostTest {
+ protected:
+ virtual void BeginTest() OVERRIDE {
+ num_commits_ = 0;
+ expected_push_properties_root_ = 0;
+ expected_push_properties_child_ = 0;
+ expected_push_properties_grandchild_ = 0;
+ expected_push_properties_child2_ = 0;
+ expected_push_properties_other_root_ = 0;
+ expected_push_properties_leaf_layer_ = 0;
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void SetupTree() OVERRIDE {
+ root_ = PushPropertiesCountingLayer::Create();
+ child_ = PushPropertiesCountingLayer::Create();
+ child2_ = PushPropertiesCountingLayer::Create();
+ grandchild_ = PushPropertiesCountingLayer::Create();
+
+ if (layer_tree_host()->settings().impl_side_painting)
+ leaf_picture_layer_ = FakePictureLayer::Create(&client_);
+ else
+ leaf_content_layer_ = FakeContentLayer::Create(&client_);
+
+ root_->AddChild(child_);
+ root_->AddChild(child2_);
+ child_->AddChild(grandchild_);
+ if (leaf_picture_layer_)
+ child2_->AddChild(leaf_picture_layer_);
+ if (leaf_content_layer_)
+ child2_->AddChild(leaf_content_layer_);
+
+ other_root_ = PushPropertiesCountingLayer::Create();
+
+ // Don't set the root layer here.
+ LayerTreeHostTest::SetupTree();
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ ++num_commits_;
+
+ EXPECT_EQ(expected_push_properties_root_, root_->push_properties_count());
+ EXPECT_EQ(expected_push_properties_child_, child_->push_properties_count());
+ EXPECT_EQ(expected_push_properties_grandchild_,
+ grandchild_->push_properties_count());
+ EXPECT_EQ(expected_push_properties_child2_,
+ child2_->push_properties_count());
+ EXPECT_EQ(expected_push_properties_other_root_,
+ other_root_->push_properties_count());
+ if (leaf_content_layer_) {
+ EXPECT_EQ(expected_push_properties_leaf_layer_,
+ leaf_content_layer_->push_properties_count());
+ }
+ if (leaf_picture_layer_) {
+ EXPECT_EQ(expected_push_properties_leaf_layer_,
+ leaf_picture_layer_->push_properties_count());
+ }
+
+ // The content/picture layer always needs to be pushed.
+ if (root_->layer_tree_host()) {
+ EXPECT_TRUE(root_->descendant_needs_push_properties());
+ EXPECT_FALSE(root_->needs_push_properties());
+ }
+ if (child2_->layer_tree_host()) {
+ EXPECT_TRUE(child2_->descendant_needs_push_properties());
+ EXPECT_FALSE(child2_->needs_push_properties());
+ }
+ if (leaf_content_layer_.get() && leaf_content_layer_->layer_tree_host()) {
+ EXPECT_FALSE(leaf_content_layer_->descendant_needs_push_properties());
+ EXPECT_TRUE(leaf_content_layer_->needs_push_properties());
+ }
+ if (leaf_picture_layer_.get() && leaf_picture_layer_->layer_tree_host()) {
+ EXPECT_FALSE(leaf_picture_layer_->descendant_needs_push_properties());
+ EXPECT_TRUE(leaf_picture_layer_->needs_push_properties());
+ }
+
+ // child_ and grandchild_ don't persist their need to push properties.
+ if (child_->layer_tree_host()) {
+ EXPECT_FALSE(child_->descendant_needs_push_properties());
+ EXPECT_FALSE(child_->needs_push_properties());
+ }
+ if (grandchild_->layer_tree_host()) {
+ EXPECT_FALSE(grandchild_->descendant_needs_push_properties());
+ EXPECT_FALSE(grandchild_->needs_push_properties());
+ }
+
+ if (other_root_->layer_tree_host()) {
+ EXPECT_FALSE(other_root_->descendant_needs_push_properties());
+ EXPECT_FALSE(other_root_->needs_push_properties());
+ }
+
+ switch (num_commits_) {
+ case 1:
+ layer_tree_host()->SetRootLayer(root_);
+ // Layers added to the tree get committed.
+ ++expected_push_properties_root_;
+ ++expected_push_properties_child_;
+ ++expected_push_properties_grandchild_;
+ ++expected_push_properties_child2_;
+ break;
+ case 2:
+ layer_tree_host()->SetNeedsCommit();
+ // No layers need commit.
+ break;
+ case 3:
+ layer_tree_host()->SetRootLayer(other_root_);
+ // Layers added to the tree get committed.
+ ++expected_push_properties_other_root_;
+ break;
+ case 4:
+ layer_tree_host()->SetRootLayer(root_);
+ // Layers added to the tree get committed.
+ ++expected_push_properties_root_;
+ ++expected_push_properties_child_;
+ ++expected_push_properties_grandchild_;
+ ++expected_push_properties_child2_;
+ break;
+ case 5:
+ layer_tree_host()->SetNeedsCommit();
+ // No layers need commit.
+ break;
+ case 6:
+ child_->RemoveFromParent();
+ // No layers need commit.
+ break;
+ case 7:
+ root_->AddChild(child_);
+ // Layers added to the tree get committed.
+ ++expected_push_properties_child_;
+ ++expected_push_properties_grandchild_;
+ break;
+ case 8:
+ grandchild_->RemoveFromParent();
+ // No layers need commit.
+ break;
+ case 9:
+ child_->AddChild(grandchild_);
+ // Layers added to the tree get committed.
+ ++expected_push_properties_grandchild_;
+ break;
+ case 10:
+ layer_tree_host()->SetViewportSize(gfx::Size(20, 20));
+ // No layers need commit.
+ break;
+ case 11:
+ layer_tree_host()->SetPageScaleFactorAndLimits(1.f, 0.8f, 1.1f);
+ // No layers need commit.
+ break;
+ case 12:
+ child_->SetPosition(gfx::Point(1, 1));
+ // The modified layer needs commit
+ ++expected_push_properties_child_;
+ break;
+ case 13:
+ child2_->SetPosition(gfx::Point(1, 1));
+ // The modified layer needs commit
+ ++expected_push_properties_child2_;
+ break;
+ case 14:
+ child_->RemoveFromParent();
+ root_->AddChild(child_);
+ // Layers added to the tree get committed.
+ ++expected_push_properties_child_;
+ ++expected_push_properties_grandchild_;
+ break;
+ case 15:
+ grandchild_->SetPosition(gfx::Point(1, 1));
+ // The modified layer needs commit
+ ++expected_push_properties_grandchild_;
+ break;
+ case 16:
+ // SetNeedsDisplay does not always set needs commit (so call it
+ // explicitly), but is a property change.
+ child_->SetNeedsDisplay();
+ ++expected_push_properties_child_;
+ layer_tree_host()->SetNeedsCommit();
+ break;
+ case 17:
+ EndTest();
+ break;
+ }
+
+ // Content/Picture layers require PushProperties every commit that they are
+ // in the tree.
+ if ((leaf_content_layer_.get() && leaf_content_layer_->layer_tree_host()) ||
+ (leaf_picture_layer_.get() && leaf_picture_layer_->layer_tree_host()))
+ ++expected_push_properties_leaf_layer_;
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ int num_commits_;
+ FakeContentLayerClient client_;
+ scoped_refptr<PushPropertiesCountingLayer> root_;
+ scoped_refptr<PushPropertiesCountingLayer> child_;
+ scoped_refptr<PushPropertiesCountingLayer> child2_;
+ scoped_refptr<PushPropertiesCountingLayer> grandchild_;
+ scoped_refptr<PushPropertiesCountingLayer> other_root_;
+ scoped_refptr<FakeContentLayer> leaf_content_layer_;
+ scoped_refptr<FakePictureLayer> leaf_picture_layer_;
+ size_t expected_push_properties_root_;
+ size_t expected_push_properties_child_;
+ size_t expected_push_properties_child2_;
+ size_t expected_push_properties_grandchild_;
+ size_t expected_push_properties_other_root_;
+ size_t expected_push_properties_leaf_layer_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostTestLayersPushProperties);
+
+class LayerTreeHostTestPropertyChangesDuringUpdateArePushed
+ : public LayerTreeHostTest {
+ protected:
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void SetupTree() OVERRIDE {
+ root_ = Layer::Create();
+ root_->SetBounds(gfx::Size(1, 1));
+
+ bool paint_scrollbar = true;
+ bool has_thumb = false;
+ scrollbar_layer_ =
+ FakeScrollbarLayer::Create(paint_scrollbar, has_thumb, root_->id());
+
+ root_->AddChild(scrollbar_layer_);
+
+ layer_tree_host()->SetRootLayer(root_);
+ LayerTreeHostTest::SetupTree();
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ switch (layer_tree_host()->source_frame_number()) {
+ case 0:
+ break;
+ case 1: {
+ // During update, the ignore_set_needs_commit_ bit is set to true to
+ // avoid causing a second commit to be scheduled. If a property change
+ // is made during this, however, it needs to be pushed in the upcoming
+ // commit.
+ scoped_ptr<base::AutoReset<bool> > ignore =
+ scrollbar_layer_->IgnoreSetNeedsCommit();
+
+ scrollbar_layer_->SetBounds(gfx::Size(30, 30));
+
+ EXPECT_TRUE(scrollbar_layer_->needs_push_properties());
+ EXPECT_TRUE(root_->descendant_needs_push_properties());
+ layer_tree_host()->SetNeedsCommit();
+
+ scrollbar_layer_->reset_push_properties_count();
+ EXPECT_EQ(0u, scrollbar_layer_->push_properties_count());
+ break;
+ }
+ case 2:
+ EXPECT_EQ(1u, scrollbar_layer_->push_properties_count());
+ EndTest();
+ break;
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ scoped_refptr<Layer> root_;
+ scoped_refptr<FakeScrollbarLayer> scrollbar_layer_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostTestPropertyChangesDuringUpdateArePushed);
+
+class LayerTreeHostTestCasePushPropertiesThreeGrandChildren
+ : public LayerTreeHostTest {
+ protected:
+ virtual void BeginTest() OVERRIDE {
+ expected_push_properties_root_ = 0;
+ expected_push_properties_child_ = 0;
+ expected_push_properties_grandchild1_ = 0;
+ expected_push_properties_grandchild2_ = 0;
+ expected_push_properties_grandchild3_ = 0;
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void SetupTree() OVERRIDE {
+ root_ = PushPropertiesCountingLayer::Create();
+ child_ = PushPropertiesCountingLayer::Create();
+ grandchild1_ = PushPropertiesCountingLayer::Create();
+ grandchild2_ = PushPropertiesCountingLayer::Create();
+ grandchild3_ = PushPropertiesCountingLayer::Create();
+
+ root_->AddChild(child_);
+ child_->AddChild(grandchild1_);
+ child_->AddChild(grandchild2_);
+ child_->AddChild(grandchild3_);
+
+ // Don't set the root layer here.
+ LayerTreeHostTest::SetupTree();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ FakeContentLayerClient client_;
+ scoped_refptr<PushPropertiesCountingLayer> root_;
+ scoped_refptr<PushPropertiesCountingLayer> child_;
+ scoped_refptr<PushPropertiesCountingLayer> grandchild1_;
+ scoped_refptr<PushPropertiesCountingLayer> grandchild2_;
+ scoped_refptr<PushPropertiesCountingLayer> grandchild3_;
+ size_t expected_push_properties_root_;
+ size_t expected_push_properties_child_;
+ size_t expected_push_properties_grandchild1_;
+ size_t expected_push_properties_grandchild2_;
+ size_t expected_push_properties_grandchild3_;
+};
+
+class LayerTreeHostTestPushPropertiesAddingToTreeRequiresPush
+ : public LayerTreeHostTestCasePushPropertiesThreeGrandChildren {
+ protected:
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ int last_source_frame_number = layer_tree_host()->source_frame_number() - 1;
+ switch (last_source_frame_number) {
+ case 0:
+ EXPECT_FALSE(root_->needs_push_properties());
+ EXPECT_FALSE(root_->descendant_needs_push_properties());
+ EXPECT_FALSE(child_->needs_push_properties());
+ EXPECT_FALSE(child_->descendant_needs_push_properties());
+ EXPECT_FALSE(grandchild1_->needs_push_properties());
+ EXPECT_FALSE(grandchild1_->descendant_needs_push_properties());
+ EXPECT_FALSE(grandchild2_->needs_push_properties());
+ EXPECT_FALSE(grandchild2_->descendant_needs_push_properties());
+ EXPECT_FALSE(grandchild3_->needs_push_properties());
+ EXPECT_FALSE(grandchild3_->descendant_needs_push_properties());
+
+ layer_tree_host()->SetRootLayer(root_);
+
+ EXPECT_TRUE(root_->needs_push_properties());
+ EXPECT_TRUE(root_->descendant_needs_push_properties());
+ EXPECT_TRUE(child_->needs_push_properties());
+ EXPECT_TRUE(child_->descendant_needs_push_properties());
+ EXPECT_TRUE(grandchild1_->needs_push_properties());
+ EXPECT_FALSE(grandchild1_->descendant_needs_push_properties());
+ EXPECT_TRUE(grandchild2_->needs_push_properties());
+ EXPECT_FALSE(grandchild2_->descendant_needs_push_properties());
+ EXPECT_TRUE(grandchild3_->needs_push_properties());
+ EXPECT_FALSE(grandchild3_->descendant_needs_push_properties());
+ break;
+ case 1:
+ EndTest();
+ break;
+ }
+ }
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostTestPushPropertiesAddingToTreeRequiresPush);
+
+class LayerTreeHostTestPushPropertiesRemovingChildStopsRecursion
+ : public LayerTreeHostTestCasePushPropertiesThreeGrandChildren {
+ protected:
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ int last_source_frame_number = layer_tree_host()->source_frame_number() - 1;
+ switch (last_source_frame_number) {
+ case 0:
+ layer_tree_host()->SetRootLayer(root_);
+ break;
+ case 1:
+ EXPECT_FALSE(root_->needs_push_properties());
+ EXPECT_FALSE(root_->descendant_needs_push_properties());
+ EXPECT_FALSE(child_->needs_push_properties());
+ EXPECT_FALSE(child_->descendant_needs_push_properties());
+ EXPECT_FALSE(grandchild1_->needs_push_properties());
+ EXPECT_FALSE(grandchild1_->descendant_needs_push_properties());
+ EXPECT_FALSE(grandchild2_->needs_push_properties());
+ EXPECT_FALSE(grandchild2_->descendant_needs_push_properties());
+ EXPECT_FALSE(grandchild3_->needs_push_properties());
+ EXPECT_FALSE(grandchild3_->descendant_needs_push_properties());
+
+ grandchild1_->RemoveFromParent();
+ grandchild1_->SetPosition(gfx::Point(1, 1));
+
+ EXPECT_FALSE(root_->needs_push_properties());
+ EXPECT_FALSE(root_->descendant_needs_push_properties());
+ EXPECT_FALSE(child_->needs_push_properties());
+ EXPECT_FALSE(child_->descendant_needs_push_properties());
+ EXPECT_FALSE(grandchild2_->needs_push_properties());
+ EXPECT_FALSE(grandchild2_->descendant_needs_push_properties());
+ EXPECT_FALSE(grandchild3_->needs_push_properties());
+ EXPECT_FALSE(grandchild3_->descendant_needs_push_properties());
+
+ child_->AddChild(grandchild1_);
+
+ EXPECT_FALSE(root_->needs_push_properties());
+ EXPECT_TRUE(root_->descendant_needs_push_properties());
+ EXPECT_FALSE(child_->needs_push_properties());
+ EXPECT_TRUE(child_->descendant_needs_push_properties());
+ EXPECT_TRUE(grandchild1_->needs_push_properties());
+ EXPECT_FALSE(grandchild1_->descendant_needs_push_properties());
+ EXPECT_FALSE(grandchild2_->needs_push_properties());
+ EXPECT_FALSE(grandchild2_->descendant_needs_push_properties());
+ EXPECT_FALSE(grandchild3_->needs_push_properties());
+ EXPECT_FALSE(grandchild3_->descendant_needs_push_properties());
+
+ grandchild2_->SetPosition(gfx::Point(1, 1));
+
+ EXPECT_FALSE(root_->needs_push_properties());
+ EXPECT_TRUE(root_->descendant_needs_push_properties());
+ EXPECT_FALSE(child_->needs_push_properties());
+ EXPECT_TRUE(child_->descendant_needs_push_properties());
+ EXPECT_TRUE(grandchild1_->needs_push_properties());
+ EXPECT_FALSE(grandchild1_->descendant_needs_push_properties());
+ EXPECT_TRUE(grandchild2_->needs_push_properties());
+ EXPECT_FALSE(grandchild2_->descendant_needs_push_properties());
+ EXPECT_FALSE(grandchild3_->needs_push_properties());
+ EXPECT_FALSE(grandchild3_->descendant_needs_push_properties());
+
+ // grandchild2_ will still need a push properties.
+ grandchild1_->RemoveFromParent();
+
+ EXPECT_FALSE(root_->needs_push_properties());
+ EXPECT_TRUE(root_->descendant_needs_push_properties());
+ EXPECT_FALSE(child_->needs_push_properties());
+ EXPECT_TRUE(child_->descendant_needs_push_properties());
+
+ // grandchild3_ does not need a push properties, so recursing should
+ // no longer be needed.
+ grandchild2_->RemoveFromParent();
+
+ EXPECT_FALSE(root_->needs_push_properties());
+ EXPECT_FALSE(root_->descendant_needs_push_properties());
+ EXPECT_FALSE(child_->needs_push_properties());
+ EXPECT_FALSE(child_->descendant_needs_push_properties());
+ EndTest();
+ break;
+ }
+ }
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostTestPushPropertiesRemovingChildStopsRecursion);
+
+class LayerTreeHostTestPushPropertiesRemovingChildStopsRecursionWithPersistence
+ : public LayerTreeHostTestCasePushPropertiesThreeGrandChildren {
+ protected:
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ int last_source_frame_number = layer_tree_host()->source_frame_number() - 1;
+ switch (last_source_frame_number) {
+ case 0:
+ layer_tree_host()->SetRootLayer(root_);
+ grandchild1_->set_persist_needs_push_properties(true);
+ grandchild2_->set_persist_needs_push_properties(true);
+ break;
+ case 1:
+ EXPECT_FALSE(root_->needs_push_properties());
+ EXPECT_TRUE(root_->descendant_needs_push_properties());
+ EXPECT_FALSE(child_->needs_push_properties());
+ EXPECT_TRUE(child_->descendant_needs_push_properties());
+ EXPECT_TRUE(grandchild1_->needs_push_properties());
+ EXPECT_FALSE(grandchild1_->descendant_needs_push_properties());
+ EXPECT_TRUE(grandchild2_->needs_push_properties());
+ EXPECT_FALSE(grandchild2_->descendant_needs_push_properties());
+ EXPECT_FALSE(grandchild3_->needs_push_properties());
+ EXPECT_FALSE(grandchild3_->descendant_needs_push_properties());
+
+ // grandchild2_ will still need a push properties.
+ grandchild1_->RemoveFromParent();
+
+ EXPECT_FALSE(root_->needs_push_properties());
+ EXPECT_TRUE(root_->descendant_needs_push_properties());
+ EXPECT_FALSE(child_->needs_push_properties());
+ EXPECT_TRUE(child_->descendant_needs_push_properties());
+
+ // grandchild3_ does not need a push properties, so recursing should
+ // no longer be needed.
+ grandchild2_->RemoveFromParent();
+
+ EXPECT_FALSE(root_->needs_push_properties());
+ EXPECT_FALSE(root_->descendant_needs_push_properties());
+ EXPECT_FALSE(child_->needs_push_properties());
+ EXPECT_FALSE(child_->descendant_needs_push_properties());
+ EndTest();
+ break;
+ }
+ }
+};
+
+MULTI_THREAD_TEST_F(
+ LayerTreeHostTestPushPropertiesRemovingChildStopsRecursionWithPersistence);
+
+class LayerTreeHostTestPushPropertiesSetPropertiesWhileOutsideTree
+ : public LayerTreeHostTestCasePushPropertiesThreeGrandChildren {
+ protected:
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ int last_source_frame_number = layer_tree_host()->source_frame_number() - 1;
+ switch (last_source_frame_number) {
+ case 0:
+ layer_tree_host()->SetRootLayer(root_);
+ break;
+ case 1:
+ EXPECT_FALSE(root_->needs_push_properties());
+ EXPECT_FALSE(root_->descendant_needs_push_properties());
+ EXPECT_FALSE(child_->needs_push_properties());
+ EXPECT_FALSE(child_->descendant_needs_push_properties());
+ EXPECT_FALSE(grandchild1_->needs_push_properties());
+ EXPECT_FALSE(grandchild1_->descendant_needs_push_properties());
+ EXPECT_FALSE(grandchild2_->needs_push_properties());
+ EXPECT_FALSE(grandchild2_->descendant_needs_push_properties());
+ EXPECT_FALSE(grandchild3_->needs_push_properties());
+ EXPECT_FALSE(grandchild3_->descendant_needs_push_properties());
+
+ // Change grandchildren while their parent is not in the tree.
+ child_->RemoveFromParent();
+ grandchild1_->SetPosition(gfx::Point(1, 1));
+ grandchild2_->SetPosition(gfx::Point(1, 1));
+ root_->AddChild(child_);
+
+ EXPECT_FALSE(root_->needs_push_properties());
+ EXPECT_TRUE(root_->descendant_needs_push_properties());
+ EXPECT_TRUE(child_->needs_push_properties());
+ EXPECT_TRUE(child_->descendant_needs_push_properties());
+ EXPECT_TRUE(grandchild1_->needs_push_properties());
+ EXPECT_FALSE(grandchild1_->descendant_needs_push_properties());
+ EXPECT_TRUE(grandchild2_->needs_push_properties());
+ EXPECT_FALSE(grandchild2_->descendant_needs_push_properties());
+ EXPECT_TRUE(grandchild3_->needs_push_properties());
+ EXPECT_FALSE(grandchild3_->descendant_needs_push_properties());
+
+ grandchild1_->RemoveFromParent();
+
+ EXPECT_FALSE(root_->needs_push_properties());
+ EXPECT_TRUE(root_->descendant_needs_push_properties());
+ EXPECT_TRUE(child_->needs_push_properties());
+ EXPECT_TRUE(child_->descendant_needs_push_properties());
+
+ grandchild2_->RemoveFromParent();
+
+ EXPECT_FALSE(root_->needs_push_properties());
+ EXPECT_TRUE(root_->descendant_needs_push_properties());
+ EXPECT_TRUE(child_->needs_push_properties());
+ EXPECT_TRUE(child_->descendant_needs_push_properties());
+
+ grandchild3_->RemoveFromParent();
+
+ EXPECT_FALSE(root_->needs_push_properties());
+ EXPECT_TRUE(root_->descendant_needs_push_properties());
+ EXPECT_TRUE(child_->needs_push_properties());
+ EXPECT_FALSE(child_->descendant_needs_push_properties());
+
+ EndTest();
+ break;
+ }
+ }
+};
+
+MULTI_THREAD_TEST_F(
+ LayerTreeHostTestPushPropertiesSetPropertiesWhileOutsideTree);
+
+class LayerTreeHostTestPushPropertiesSetPropertyInParentThenChild
+ : public LayerTreeHostTestCasePushPropertiesThreeGrandChildren {
+ protected:
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ int last_source_frame_number = layer_tree_host()->source_frame_number() - 1;
+ switch (last_source_frame_number) {
+ case 0:
+ layer_tree_host()->SetRootLayer(root_);
+ break;
+ case 1:
+ EXPECT_FALSE(root_->needs_push_properties());
+ EXPECT_FALSE(root_->descendant_needs_push_properties());
+ EXPECT_FALSE(child_->needs_push_properties());
+ EXPECT_FALSE(child_->descendant_needs_push_properties());
+ EXPECT_FALSE(grandchild1_->needs_push_properties());
+ EXPECT_FALSE(grandchild1_->descendant_needs_push_properties());
+ EXPECT_FALSE(grandchild2_->needs_push_properties());
+ EXPECT_FALSE(grandchild2_->descendant_needs_push_properties());
+ EXPECT_FALSE(grandchild3_->needs_push_properties());
+ EXPECT_FALSE(grandchild3_->descendant_needs_push_properties());
+
+ child_->SetPosition(gfx::Point(1, 1));
+ grandchild1_->SetPosition(gfx::Point(1, 1));
+ grandchild2_->SetPosition(gfx::Point(1, 1));
+
+ EXPECT_FALSE(root_->needs_push_properties());
+ EXPECT_TRUE(root_->descendant_needs_push_properties());
+ EXPECT_TRUE(child_->needs_push_properties());
+ EXPECT_TRUE(child_->descendant_needs_push_properties());
+ EXPECT_TRUE(grandchild1_->needs_push_properties());
+ EXPECT_FALSE(grandchild1_->descendant_needs_push_properties());
+ EXPECT_TRUE(grandchild2_->needs_push_properties());
+ EXPECT_FALSE(grandchild2_->descendant_needs_push_properties());
+ EXPECT_FALSE(grandchild3_->needs_push_properties());
+ EXPECT_FALSE(grandchild3_->descendant_needs_push_properties());
+
+ grandchild1_->RemoveFromParent();
+
+ EXPECT_FALSE(root_->needs_push_properties());
+ EXPECT_TRUE(root_->descendant_needs_push_properties());
+ EXPECT_TRUE(child_->needs_push_properties());
+ EXPECT_TRUE(child_->descendant_needs_push_properties());
+
+ grandchild2_->RemoveFromParent();
+
+ EXPECT_FALSE(root_->needs_push_properties());
+ EXPECT_TRUE(root_->descendant_needs_push_properties());
+ EXPECT_TRUE(child_->needs_push_properties());
+ EXPECT_FALSE(child_->descendant_needs_push_properties());
+
+ child_->RemoveFromParent();
+
+ EXPECT_FALSE(root_->needs_push_properties());
+ EXPECT_FALSE(root_->descendant_needs_push_properties());
+
+ EndTest();
+ break;
+ }
+ }
+};
+
+MULTI_THREAD_TEST_F(
+ LayerTreeHostTestPushPropertiesSetPropertyInParentThenChild);
+
+class LayerTreeHostTestPushPropertiesSetPropertyInChildThenParent
+ : public LayerTreeHostTestCasePushPropertiesThreeGrandChildren {
+ protected:
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ int last_source_frame_number = layer_tree_host()->source_frame_number() - 1;
+ switch (last_source_frame_number) {
+ case 0:
+ layer_tree_host()->SetRootLayer(root_);
+ break;
+ case 1:
+ EXPECT_FALSE(root_->needs_push_properties());
+ EXPECT_FALSE(root_->descendant_needs_push_properties());
+ EXPECT_FALSE(child_->needs_push_properties());
+ EXPECT_FALSE(child_->descendant_needs_push_properties());
+ EXPECT_FALSE(grandchild1_->needs_push_properties());
+ EXPECT_FALSE(grandchild1_->descendant_needs_push_properties());
+ EXPECT_FALSE(grandchild2_->needs_push_properties());
+ EXPECT_FALSE(grandchild2_->descendant_needs_push_properties());
+ EXPECT_FALSE(grandchild3_->needs_push_properties());
+ EXPECT_FALSE(grandchild3_->descendant_needs_push_properties());
+
+ grandchild1_->SetPosition(gfx::Point(1, 1));
+ grandchild2_->SetPosition(gfx::Point(1, 1));
+ child_->SetPosition(gfx::Point(1, 1));
+
+ EXPECT_FALSE(root_->needs_push_properties());
+ EXPECT_TRUE(root_->descendant_needs_push_properties());
+ EXPECT_TRUE(child_->needs_push_properties());
+ EXPECT_TRUE(child_->descendant_needs_push_properties());
+ EXPECT_TRUE(grandchild1_->needs_push_properties());
+ EXPECT_FALSE(grandchild1_->descendant_needs_push_properties());
+ EXPECT_TRUE(grandchild2_->needs_push_properties());
+ EXPECT_FALSE(grandchild2_->descendant_needs_push_properties());
+ EXPECT_FALSE(grandchild3_->needs_push_properties());
+ EXPECT_FALSE(grandchild3_->descendant_needs_push_properties());
+
+ grandchild1_->RemoveFromParent();
+
+ EXPECT_FALSE(root_->needs_push_properties());
+ EXPECT_TRUE(root_->descendant_needs_push_properties());
+ EXPECT_TRUE(child_->needs_push_properties());
+ EXPECT_TRUE(child_->descendant_needs_push_properties());
+
+ grandchild2_->RemoveFromParent();
+
+ EXPECT_FALSE(root_->needs_push_properties());
+ EXPECT_TRUE(root_->descendant_needs_push_properties());
+ EXPECT_TRUE(child_->needs_push_properties());
+ EXPECT_FALSE(child_->descendant_needs_push_properties());
+
+ child_->RemoveFromParent();
+
+ EXPECT_FALSE(root_->needs_push_properties());
+ EXPECT_FALSE(root_->descendant_needs_push_properties());
+
+ EndTest();
+ break;
+ }
+ }
+};
+
+MULTI_THREAD_TEST_F(
+ LayerTreeHostTestPushPropertiesSetPropertyInChildThenParent);
+
+// This test verifies that the tree activation callback is invoked correctly.
+class LayerTreeHostTestTreeActivationCallback : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestTreeActivationCallback()
+ : num_commits_(0), callback_count_(0) {}
+
+ virtual void BeginTest() OVERRIDE {
+ EXPECT_TRUE(HasImplThread());
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual bool PrepareToDrawOnThread(LayerTreeHostImpl* host_impl,
+ LayerTreeHostImpl::FrameData* frame_data,
+ bool result) OVERRIDE {
+ ++num_commits_;
+ switch (num_commits_) {
+ case 1:
+ EXPECT_EQ(0, callback_count_);
+ callback_count_ = 0;
+ SetCallback(true);
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 2:
+ EXPECT_EQ(1, callback_count_);
+ callback_count_ = 0;
+ SetCallback(false);
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 3:
+ EXPECT_EQ(0, callback_count_);
+ callback_count_ = 0;
+ EndTest();
+ break;
+ default:
+ ADD_FAILURE() << num_commits_;
+ EndTest();
+ break;
+ }
+ return LayerTreeHostTest::PrepareToDrawOnThread(host_impl, frame_data,
+ result);
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ EXPECT_EQ(3, num_commits_);
+ }
+
+ void SetCallback(bool enable) {
+ output_surface()->SetTreeActivationCallback(enable ?
+ base::Bind(&LayerTreeHostTestTreeActivationCallback::ActivationCallback,
+ base::Unretained(this)) :
+ base::Closure());
+ }
+
+ void ActivationCallback() {
+ ++callback_count_;
+ }
+
+ int num_commits_;
+ int callback_count_;
+};
+
+TEST_F(LayerTreeHostTestTreeActivationCallback, DirectRenderer) {
+ RunTest(true, false, true);
+}
+
+TEST_F(LayerTreeHostTestTreeActivationCallback, DelegatingRenderer) {
+ RunTest(true, true, true);
+}
+
+class LayerInvalidateCausesDraw : public LayerTreeHostTest {
+ public:
+ LayerInvalidateCausesDraw() : num_commits_(0), num_draws_(0) {}
+
+ virtual void BeginTest() OVERRIDE {
+ ASSERT_TRUE(!!invalidate_layer_)
+ << "Derived tests must set this in SetupTree";
+
+ // One initial commit.
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ // After commit, invalidate the layer. This should cause a commit.
+ if (layer_tree_host()->source_frame_number() == 1)
+ invalidate_layer_->SetNeedsDisplay();
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ num_draws_++;
+ if (impl->active_tree()->source_frame_number() == 1)
+ EndTest();
+ }
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ num_commits_++;
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ EXPECT_GE(2, num_commits_);
+ EXPECT_GE(2, num_draws_);
+ }
+
+ protected:
+ scoped_refptr<Layer> invalidate_layer_;
+
+ private:
+ int num_commits_;
+ int num_draws_;
+};
+
+// VideoLayer must support being invalidated and then passing that along
+// to the compositor thread, even though no resources are updated in
+// response to that invalidation.
+class LayerTreeHostTestVideoLayerInvalidate : public LayerInvalidateCausesDraw {
+ public:
+ virtual void SetupTree() OVERRIDE {
+ LayerTreeHostTest::SetupTree();
+ scoped_refptr<VideoLayer> video_layer = VideoLayer::Create(&provider_);
+ video_layer->SetBounds(gfx::Size(10, 10));
+ video_layer->SetIsDrawable(true);
+ layer_tree_host()->root_layer()->AddChild(video_layer);
+
+ invalidate_layer_ = video_layer;
+ }
+
+ private:
+ FakeVideoFrameProvider provider_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestVideoLayerInvalidate);
+
+// IOSurfaceLayer must support being invalidated and then passing that along
+// to the compositor thread, even though no resources are updated in
+// response to that invalidation.
+class LayerTreeHostTestIOSurfaceLayerInvalidate
+ : public LayerInvalidateCausesDraw {
+ public:
+ virtual void SetupTree() OVERRIDE {
+ LayerTreeHostTest::SetupTree();
+ scoped_refptr<IOSurfaceLayer> layer = IOSurfaceLayer::Create();
+ layer->SetBounds(gfx::Size(10, 10));
+ uint32_t fake_io_surface_id = 7;
+ layer->SetIOSurfaceProperties(fake_io_surface_id, layer->bounds());
+ layer->SetIsDrawable(true);
+ layer_tree_host()->root_layer()->AddChild(layer);
+
+ invalidate_layer_ = layer;
+ }
+};
+
+// TODO(danakj): IOSurface layer can not be transported. crbug.com/239335
+SINGLE_AND_MULTI_THREAD_DIRECT_RENDERER_TEST_F(
+ LayerTreeHostTestIOSurfaceLayerInvalidate);
+
+class LayerTreeHostTestPushHiddenLayer : public LayerTreeHostTest {
+ protected:
+ virtual void SetupTree() OVERRIDE {
+ root_layer_ = Layer::Create();
+ root_layer_->SetAnchorPoint(gfx::PointF());
+ root_layer_->SetPosition(gfx::Point());
+ root_layer_->SetBounds(gfx::Size(10, 10));
+
+ parent_layer_ = SolidColorLayer::Create();
+ parent_layer_->SetAnchorPoint(gfx::PointF());
+ parent_layer_->SetPosition(gfx::Point());
+ parent_layer_->SetBounds(gfx::Size(10, 10));
+ parent_layer_->SetIsDrawable(true);
+ root_layer_->AddChild(parent_layer_);
+
+ child_layer_ = SolidColorLayer::Create();
+ child_layer_->SetAnchorPoint(gfx::PointF());
+ child_layer_->SetPosition(gfx::Point());
+ child_layer_->SetBounds(gfx::Size(10, 10));
+ child_layer_->SetIsDrawable(true);
+ parent_layer_->AddChild(child_layer_);
+
+ layer_tree_host()->SetRootLayer(root_layer_);
+ LayerTreeHostTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE { PostSetNeedsCommitToMainThread(); }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ switch (layer_tree_host()->source_frame_number()) {
+ case 1:
+ // The layer type used does not need to push properties every frame.
+ EXPECT_FALSE(child_layer_->needs_push_properties());
+
+ // Change the bounds of the child layer, but make it skipped
+ // by CalculateDrawProperties.
+ parent_layer_->SetOpacity(0.f);
+ child_layer_->SetBounds(gfx::Size(5, 5));
+ break;
+ case 2:
+ // The bounds of the child layer were pushed to the impl side.
+ EXPECT_FALSE(child_layer_->needs_push_properties());
+
+ EndTest();
+ break;
+ }
+ }
+
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ LayerImpl* root = impl->active_tree()->root_layer();
+ LayerImpl* parent = root->children()[0];
+ LayerImpl* child = parent->children()[0];
+
+ switch (impl->active_tree()->source_frame_number()) {
+ case 1:
+ EXPECT_EQ(gfx::Size(5, 5).ToString(), child->bounds().ToString());
+ break;
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ scoped_refptr<Layer> root_layer_;
+ scoped_refptr<SolidColorLayer> parent_layer_;
+ scoped_refptr<SolidColorLayer> child_layer_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestPushHiddenLayer);
+
+class LayerTreeHostTestUpdateLayerInEmptyViewport : public LayerTreeHostTest {
+ protected:
+ virtual void InitializeSettings(LayerTreeSettings* settings) OVERRIDE {
+ settings->impl_side_painting = true;
+ }
+
+ virtual void SetupTree() OVERRIDE {
+ root_layer_ = FakePictureLayer::Create(&client_);
+ root_layer_->SetAnchorPoint(gfx::PointF());
+ root_layer_->SetBounds(gfx::Size(10, 10));
+
+ layer_tree_host()->SetRootLayer(root_layer_);
+ LayerTreeHostTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ // The viewport is empty, but we still need to update layers on the main
+ // thread.
+ layer_tree_host()->SetViewportSize(gfx::Size(0, 0));
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommit() OVERRIDE {
+ // The layer should be updated even though the viewport is empty, so we
+ // are capable of drawing it on the impl tree.
+ EXPECT_GT(root_layer_->update_count(), 0u);
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ FakeContentLayerClient client_;
+ scoped_refptr<FakePictureLayer> root_layer_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostTestUpdateLayerInEmptyViewport);
+
+class LayerTreeHostTestAbortEvictedTextures : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestAbortEvictedTextures()
+ : num_will_begin_frames_(0), num_impl_commits_(0) {}
+
+ protected:
+ virtual void SetupTree() OVERRIDE {
+ scoped_refptr<SolidColorLayer> root_layer = SolidColorLayer::Create();
+ root_layer->SetBounds(gfx::Size(200, 200));
+ root_layer->SetIsDrawable(true);
+
+ layer_tree_host()->SetRootLayer(root_layer);
+ LayerTreeHostTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE { PostSetNeedsCommitToMainThread(); }
+
+ virtual void WillBeginFrame() OVERRIDE {
+ num_will_begin_frames_++;
+ switch (num_will_begin_frames_) {
+ case 2:
+ // Send a redraw to the compositor thread. This will (wrongly) be
+ // ignored unless aborting resets the texture state.
+ layer_tree_host()->SetNeedsRedraw();
+ break;
+ }
+ }
+
+ virtual void BeginCommitOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ num_impl_commits_++;
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ switch (impl->SourceAnimationFrameNumber()) {
+ case 1:
+ // Prevent draws until commit.
+ impl->active_tree()->SetContentsTexturesPurged();
+ EXPECT_FALSE(impl->CanDraw());
+ // Trigger an abortable commit.
+ impl->SetNeedsCommit();
+ break;
+ case 2:
+ EndTest();
+ break;
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ // Ensure that the commit was truly aborted.
+ EXPECT_EQ(2, num_will_begin_frames_);
+ EXPECT_EQ(1, num_impl_commits_);
+ }
+
+ private:
+ int num_will_begin_frames_;
+ int num_impl_commits_;
+};
+
+// Commits can only be aborted when using the thread proxy.
+MULTI_THREAD_TEST_F(LayerTreeHostTestAbortEvictedTextures);
+
+} // namespace
+
+} // namespace cc
diff --git a/chromium/cc/trees/layer_tree_host_unittest_animation.cc b/chromium/cc/trees/layer_tree_host_unittest_animation.cc
new file mode 100644
index 00000000000..dbc427504d8
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_host_unittest_animation.cc
@@ -0,0 +1,806 @@
+// 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 "cc/trees/layer_tree_host.h"
+
+#include "cc/animation/animation_curve.h"
+#include "cc/animation/layer_animation_controller.h"
+#include "cc/animation/timing_function.h"
+#include "cc/layers/layer.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/test/animation_test_common.h"
+#include "cc/test/fake_content_layer.h"
+#include "cc/test/fake_content_layer_client.h"
+#include "cc/test/layer_tree_test.h"
+#include "cc/trees/layer_tree_impl.h"
+
+namespace cc {
+namespace {
+
+class LayerTreeHostAnimationTest : public LayerTreeTest {
+ public:
+ virtual void SetupTree() OVERRIDE {
+ LayerTreeTest::SetupTree();
+ layer_tree_host()->root_layer()->set_layer_animation_delegate(this);
+ }
+};
+
+// Makes sure that SetNeedsAnimate does not cause the CommitRequested() state to
+// be set.
+class LayerTreeHostAnimationTestSetNeedsAnimateShouldNotSetCommitRequested
+ : public LayerTreeHostAnimationTest {
+ public:
+ LayerTreeHostAnimationTestSetNeedsAnimateShouldNotSetCommitRequested()
+ : num_commits_(0) {}
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void Animate(base::TimeTicks monotonic_time) OVERRIDE {
+ // We skip the first commit becasue its the commit that populates the
+ // impl thread with a tree. After the second commit, the test is done.
+ if (num_commits_ != 1)
+ return;
+
+ layer_tree_host()->SetNeedsAnimate();
+ // Right now, CommitRequested is going to be true, because during
+ // BeginFrame, we force CommitRequested to true to prevent requests from
+ // hitting the impl thread. But, when the next DidCommit happens, we should
+ // verify that CommitRequested has gone back to false.
+ }
+
+ virtual void DidCommit() OVERRIDE {
+ if (!num_commits_) {
+ EXPECT_FALSE(layer_tree_host()->CommitRequested());
+ layer_tree_host()->SetNeedsAnimate();
+ EXPECT_FALSE(layer_tree_host()->CommitRequested());
+ }
+
+ // Verifies that the SetNeedsAnimate we made in ::Animate did not
+ // trigger CommitRequested.
+ EXPECT_FALSE(layer_tree_host()->CommitRequested());
+ EndTest();
+ num_commits_++;
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ int num_commits_;
+};
+
+MULTI_THREAD_TEST_F(
+ LayerTreeHostAnimationTestSetNeedsAnimateShouldNotSetCommitRequested);
+
+// Trigger a frame with SetNeedsCommit. Then, inside the resulting animate
+// callback, request another frame using SetNeedsAnimate. End the test when
+// animate gets called yet-again, indicating that the proxy is correctly
+// handling the case where SetNeedsAnimate() is called inside the BeginFrame
+// flow.
+class LayerTreeHostAnimationTestSetNeedsAnimateInsideAnimationCallback
+ : public LayerTreeHostAnimationTest {
+ public:
+ LayerTreeHostAnimationTestSetNeedsAnimateInsideAnimationCallback()
+ : num_animates_(0) {}
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void Animate(base::TimeTicks) OVERRIDE {
+ if (!num_animates_) {
+ layer_tree_host()->SetNeedsAnimate();
+ num_animates_++;
+ return;
+ }
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ int num_animates_;
+};
+
+MULTI_THREAD_TEST_F(
+ LayerTreeHostAnimationTestSetNeedsAnimateInsideAnimationCallback);
+
+// Add a layer animation and confirm that
+// LayerTreeHostImpl::updateAnimationState does get called and continues to
+// get called.
+class LayerTreeHostAnimationTestAddAnimation
+ : public LayerTreeHostAnimationTest {
+ public:
+ LayerTreeHostAnimationTestAddAnimation()
+ : num_animates_(0),
+ received_animation_started_notification_(false),
+ start_time_(0.0) {
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ PostAddInstantAnimationToMainThread(layer_tree_host()->root_layer());
+ }
+
+ virtual void UpdateAnimationState(
+ LayerTreeHostImpl* host_impl,
+ bool has_unfinished_animation) OVERRIDE {
+ if (!num_animates_) {
+ // The animation had zero duration so LayerTreeHostImpl should no
+ // longer need to animate its layers.
+ EXPECT_FALSE(has_unfinished_animation);
+ num_animates_++;
+ return;
+ }
+
+ if (received_animation_started_notification_) {
+ EXPECT_LT(0.0, start_time_);
+
+ LayerAnimationController* controller_impl =
+ host_impl->active_tree()->root_layer()->layer_animation_controller();
+ Animation* animation_impl =
+ controller_impl->GetAnimation(Animation::Opacity);
+ if (animation_impl)
+ controller_impl->RemoveAnimation(animation_impl->id());
+
+ EndTest();
+ }
+ }
+
+ virtual void NotifyAnimationStarted(double wall_clock_time) OVERRIDE {
+ received_animation_started_notification_ = true;
+ start_time_ = wall_clock_time;
+ if (num_animates_) {
+ EXPECT_LT(0.0, start_time_);
+
+ LayerAnimationController* controller =
+ layer_tree_host()->root_layer()->layer_animation_controller();
+ Animation* animation =
+ controller->GetAnimation(Animation::Opacity);
+ if (animation)
+ controller->RemoveAnimation(animation->id());
+
+ EndTest();
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ int num_animates_;
+ bool received_animation_started_notification_;
+ double start_time_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestAddAnimation);
+
+// Add a layer animation to a layer, but continually fail to draw. Confirm that
+// after a while, we do eventually force a draw.
+class LayerTreeHostAnimationTestCheckerboardDoesNotStarveDraws
+ : public LayerTreeHostAnimationTest {
+ public:
+ LayerTreeHostAnimationTestCheckerboardDoesNotStarveDraws()
+ : started_animating_(false) {}
+
+ virtual void BeginTest() OVERRIDE {
+ PostAddAnimationToMainThread(layer_tree_host()->root_layer());
+ }
+
+ virtual void AnimateLayers(
+ LayerTreeHostImpl* host_impl,
+ base::TimeTicks monotonic_time) OVERRIDE {
+ started_animating_ = true;
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ if (started_animating_)
+ EndTest();
+ }
+
+ virtual bool PrepareToDrawOnThread(
+ LayerTreeHostImpl* host_impl,
+ LayerTreeHostImpl::FrameData* frame,
+ bool result) OVERRIDE {
+ return false;
+ }
+
+ virtual void AfterTest() OVERRIDE { }
+
+ private:
+ bool started_animating_;
+};
+
+// Starvation can only be an issue with the MT compositor.
+MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestCheckerboardDoesNotStarveDraws);
+
+// Ensures that animations eventually get deleted.
+class LayerTreeHostAnimationTestAnimationsGetDeleted
+ : public LayerTreeHostAnimationTest {
+ public:
+ LayerTreeHostAnimationTestAnimationsGetDeleted()
+ : started_animating_(false) {}
+
+ virtual void BeginTest() OVERRIDE {
+ PostAddAnimationToMainThread(layer_tree_host()->root_layer());
+ }
+
+ virtual void AnimateLayers(
+ LayerTreeHostImpl* host_impl,
+ base::TimeTicks monotonic_time) OVERRIDE {
+ bool have_animations = !host_impl->animation_registrar()->
+ active_animation_controllers().empty();
+ if (!started_animating_ && have_animations) {
+ started_animating_ = true;
+ return;
+ }
+
+ if (started_animating_ && !have_animations)
+ EndTest();
+ }
+
+ virtual void NotifyAnimationFinished(double time) OVERRIDE {
+ // Animations on the impl-side controller only get deleted during a commit,
+ // so we need to schedule a commit.
+ layer_tree_host()->SetNeedsCommit();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ bool started_animating_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestAnimationsGetDeleted);
+
+// Ensures that animations continue to be ticked when we are backgrounded.
+class LayerTreeHostAnimationTestTickAnimationWhileBackgrounded
+ : public LayerTreeHostAnimationTest {
+ public:
+ LayerTreeHostAnimationTestTickAnimationWhileBackgrounded()
+ : num_animates_(0) {}
+
+ virtual void BeginTest() OVERRIDE {
+ PostAddAnimationToMainThread(layer_tree_host()->root_layer());
+ }
+
+ // Use WillAnimateLayers to set visible false before the animation runs and
+ // causes a commit, so we block the second visible animate in single-thread
+ // mode.
+ virtual void WillAnimateLayers(
+ LayerTreeHostImpl* host_impl,
+ base::TimeTicks monotonic_time) OVERRIDE {
+ // Verify that the host can draw, it's just not visible.
+ EXPECT_TRUE(host_impl->CanDraw());
+ if (num_animates_ < 2) {
+ if (!num_animates_) {
+ // We have a long animation running. It should continue to tick even
+ // if we are not visible.
+ PostSetVisibleToMainThread(false);
+ }
+ num_animates_++;
+ return;
+ }
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ int num_animates_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostAnimationTestTickAnimationWhileBackgrounded);
+
+// Ensure that an animation's timing function is respected.
+class LayerTreeHostAnimationTestAddAnimationWithTimingFunction
+ : public LayerTreeHostAnimationTest {
+ public:
+ LayerTreeHostAnimationTestAddAnimationWithTimingFunction() {}
+
+ virtual void SetupTree() OVERRIDE {
+ LayerTreeHostAnimationTest::SetupTree();
+ content_ = FakeContentLayer::Create(&client_);
+ content_->SetBounds(gfx::Size(4, 4));
+ layer_tree_host()->root_layer()->AddChild(content_);
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ PostAddAnimationToMainThread(content_.get());
+ }
+
+ virtual void AnimateLayers(
+ LayerTreeHostImpl* host_impl,
+ base::TimeTicks monotonic_time) OVERRIDE {
+ LayerAnimationController* controller_impl =
+ host_impl->active_tree()->root_layer()->children()[0]->
+ layer_animation_controller();
+ Animation* animation =
+ controller_impl->GetAnimation(Animation::Opacity);
+ if (!animation)
+ return;
+
+ const FloatAnimationCurve* curve =
+ animation->curve()->ToFloatAnimationCurve();
+ float start_opacity = curve->GetValue(0.0);
+ float end_opacity = curve->GetValue(curve->Duration());
+ float linearly_interpolated_opacity =
+ 0.25f * end_opacity + 0.75f * start_opacity;
+ double time = curve->Duration() * 0.25;
+ // If the linear timing function associated with this animation was not
+ // picked up, then the linearly interpolated opacity would be different
+ // because of the default ease timing function.
+ EXPECT_FLOAT_EQ(linearly_interpolated_opacity, curve->GetValue(time));
+
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ FakeContentLayerClient client_;
+ scoped_refptr<FakeContentLayer> content_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostAnimationTestAddAnimationWithTimingFunction);
+
+// Ensures that main thread animations have their start times synchronized with
+// impl thread animations.
+class LayerTreeHostAnimationTestSynchronizeAnimationStartTimes
+ : public LayerTreeHostAnimationTest {
+ public:
+ LayerTreeHostAnimationTestSynchronizeAnimationStartTimes()
+ : main_start_time_(-1.0),
+ impl_start_time_(-1.0) {}
+
+ virtual void SetupTree() OVERRIDE {
+ LayerTreeHostAnimationTest::SetupTree();
+ content_ = FakeContentLayer::Create(&client_);
+ content_->SetBounds(gfx::Size(4, 4));
+ content_->set_layer_animation_delegate(this);
+ layer_tree_host()->root_layer()->AddChild(content_);
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ PostAddAnimationToMainThread(content_.get());
+ }
+
+ virtual void NotifyAnimationStarted(double time) OVERRIDE {
+ LayerAnimationController* controller =
+ layer_tree_host()->root_layer()->children()[0]->
+ layer_animation_controller();
+ Animation* animation =
+ controller->GetAnimation(Animation::Opacity);
+ main_start_time_ = animation->start_time();
+ controller->RemoveAnimation(animation->id());
+
+ if (impl_start_time_ > 0.0)
+ EndTest();
+ }
+
+ virtual void UpdateAnimationState(
+ LayerTreeHostImpl* impl_host,
+ bool has_unfinished_animation) OVERRIDE {
+ LayerAnimationController* controller =
+ impl_host->active_tree()->root_layer()->children()[0]->
+ layer_animation_controller();
+ Animation* animation =
+ controller->GetAnimation(Animation::Opacity);
+ if (!animation)
+ return;
+
+ impl_start_time_ = animation->start_time();
+ controller->RemoveAnimation(animation->id());
+
+ if (main_start_time_ > 0.0)
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ EXPECT_FLOAT_EQ(impl_start_time_, main_start_time_);
+ }
+
+ private:
+ double main_start_time_;
+ double impl_start_time_;
+ FakeContentLayerClient client_;
+ scoped_refptr<FakeContentLayer> content_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostAnimationTestSynchronizeAnimationStartTimes);
+
+// Ensures that notify animation finished is called.
+class LayerTreeHostAnimationTestAnimationFinishedEvents
+ : public LayerTreeHostAnimationTest {
+ public:
+ LayerTreeHostAnimationTestAnimationFinishedEvents() {}
+
+ virtual void BeginTest() OVERRIDE {
+ PostAddInstantAnimationToMainThread(layer_tree_host()->root_layer());
+ }
+
+ virtual void NotifyAnimationFinished(double time) OVERRIDE {
+ LayerAnimationController* controller =
+ layer_tree_host()->root_layer()->layer_animation_controller();
+ Animation* animation =
+ controller->GetAnimation(Animation::Opacity);
+ if (animation)
+ controller->RemoveAnimation(animation->id());
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostAnimationTestAnimationFinishedEvents);
+
+// Ensures that when opacity is being animated, this value does not cause the
+// subtree to be skipped.
+class LayerTreeHostAnimationTestDoNotSkipLayersWithAnimatedOpacity
+ : public LayerTreeHostAnimationTest {
+ public:
+ LayerTreeHostAnimationTestDoNotSkipLayersWithAnimatedOpacity()
+ : update_check_layer_(FakeContentLayer::Create(&client_)) {
+ }
+
+ virtual void SetupTree() OVERRIDE {
+ update_check_layer_->SetOpacity(0.f);
+ layer_tree_host()->SetRootLayer(update_check_layer_);
+ LayerTreeHostAnimationTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ PostAddAnimationToMainThread(update_check_layer_.get());
+ }
+
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ LayerAnimationController* controller_impl =
+ host_impl->active_tree()->root_layer()->layer_animation_controller();
+ Animation* animation_impl =
+ controller_impl->GetAnimation(Animation::Opacity);
+ controller_impl->RemoveAnimation(animation_impl->id());
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ // Update() should have been called once, proving that the layer was not
+ // skipped.
+ EXPECT_EQ(1u, update_check_layer_->update_count());
+
+ // clear update_check_layer_ so LayerTreeHost dies.
+ update_check_layer_ = NULL;
+ }
+
+ private:
+ FakeContentLayerClient client_;
+ scoped_refptr<FakeContentLayer> update_check_layer_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostAnimationTestDoNotSkipLayersWithAnimatedOpacity);
+
+// Layers added to tree with existing active animations should have the
+// animation correctly recognized.
+class LayerTreeHostAnimationTestLayerAddedWithAnimation
+ : public LayerTreeHostAnimationTest {
+ public:
+ LayerTreeHostAnimationTestLayerAddedWithAnimation() {}
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommit() OVERRIDE {
+ if (layer_tree_host()->source_frame_number() == 1) {
+ scoped_refptr<Layer> layer = Layer::Create();
+ layer->set_layer_animation_delegate(this);
+
+ // Any valid AnimationCurve will do here.
+ scoped_ptr<AnimationCurve> curve(EaseTimingFunction::Create());
+ scoped_ptr<Animation> animation(
+ Animation::Create(curve.Pass(), 1, 1,
+ Animation::Opacity));
+ layer->layer_animation_controller()->AddAnimation(animation.Pass());
+
+ // We add the animation *before* attaching the layer to the tree.
+ layer_tree_host()->root_layer()->AddChild(layer);
+ }
+ }
+
+ virtual void AnimateLayers(
+ LayerTreeHostImpl* impl_host,
+ base::TimeTicks monotonic_time) OVERRIDE {
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostAnimationTestLayerAddedWithAnimation);
+
+class LayerTreeHostAnimationTestCompositeAndReadbackAnimateCount
+ : public LayerTreeHostAnimationTest {
+ public:
+ LayerTreeHostAnimationTestCompositeAndReadbackAnimateCount()
+ : animated_commit_(-1) {
+ }
+
+ virtual void Animate(base::TimeTicks) OVERRIDE {
+ // We shouldn't animate on the CompositeAndReadback-forced commit, but we
+ // should for the SetNeedsCommit-triggered commit.
+ animated_commit_ = layer_tree_host()->source_frame_number();
+ EXPECT_NE(2, animated_commit_);
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommit() OVERRIDE {
+ switch (layer_tree_host()->source_frame_number()) {
+ case 1:
+ layer_tree_host()->SetNeedsCommit();
+ break;
+ case 2: {
+ char pixels[4];
+ layer_tree_host()->CompositeAndReadback(&pixels, gfx::Rect(0, 0, 1, 1));
+ break;
+ }
+ case 3:
+ // This is finishing the readback's commit.
+ break;
+ case 4:
+ // This is finishing the followup commit.
+ EndTest();
+ break;
+ default:
+ NOTREACHED();
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ EXPECT_EQ(3, animated_commit_);
+ }
+
+ private:
+ int animated_commit_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestCompositeAndReadbackAnimateCount);
+
+class LayerTreeHostAnimationTestContinuousAnimate
+ : public LayerTreeHostAnimationTest {
+ public:
+ LayerTreeHostAnimationTestContinuousAnimate()
+ : num_commit_complete_(0),
+ num_draw_layers_(0) {
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void Animate(base::TimeTicks) OVERRIDE {
+ if (num_draw_layers_ == 2)
+ return;
+ layer_tree_host()->SetNeedsAnimate();
+ }
+
+ virtual void Layout() OVERRIDE {
+ layer_tree_host()->root_layer()->SetNeedsDisplay();
+ }
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* tree_impl) OVERRIDE {
+ if (num_draw_layers_ == 1)
+ num_commit_complete_++;
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ num_draw_layers_++;
+ if (num_draw_layers_ == 2)
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ // Check that we didn't commit twice between first and second draw.
+ EXPECT_EQ(1, num_commit_complete_);
+ }
+
+ private:
+ int num_commit_complete_;
+ int num_draw_layers_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestContinuousAnimate);
+
+// Make sure the main thread can still execute animations when CanDraw() is not
+// true.
+class LayerTreeHostAnimationTestRunAnimationWhenNotCanDraw
+ : public LayerTreeHostAnimationTest {
+ public:
+ LayerTreeHostAnimationTestRunAnimationWhenNotCanDraw() : started_times_(0) {}
+
+ virtual void SetupTree() OVERRIDE {
+ LayerTreeHostAnimationTest::SetupTree();
+ content_ = FakeContentLayer::Create(&client_);
+ content_->SetBounds(gfx::Size(4, 4));
+ content_->set_layer_animation_delegate(this);
+ layer_tree_host()->root_layer()->AddChild(content_);
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ layer_tree_host()->SetViewportSize(gfx::Size());
+ PostAddAnimationToMainThread(content_.get());
+ }
+
+ virtual void NotifyAnimationStarted(double wall_clock_time) OVERRIDE {
+ started_times_++;
+ }
+
+ virtual void NotifyAnimationFinished(double wall_clock_time) OVERRIDE {
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ EXPECT_EQ(1, started_times_);
+ }
+
+ private:
+ int started_times_;
+ FakeContentLayerClient client_;
+ scoped_refptr<FakeContentLayer> content_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostAnimationTestRunAnimationWhenNotCanDraw);
+
+// Make sure the main thread can still execute animations when the renderer is
+// backgrounded.
+class LayerTreeHostAnimationTestRunAnimationWhenNotVisible
+ : public LayerTreeHostAnimationTest {
+ public:
+ LayerTreeHostAnimationTestRunAnimationWhenNotVisible() : started_times_(0) {}
+
+ virtual void SetupTree() OVERRIDE {
+ LayerTreeHostAnimationTest::SetupTree();
+ content_ = FakeContentLayer::Create(&client_);
+ content_->SetBounds(gfx::Size(4, 4));
+ content_->set_layer_animation_delegate(this);
+ layer_tree_host()->root_layer()->AddChild(content_);
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ visible_ = true;
+ PostAddAnimationToMainThread(content_.get());
+ }
+
+ virtual void DidCommit() OVERRIDE {
+ visible_ = false;
+ layer_tree_host()->SetVisible(false);
+ }
+
+ virtual void NotifyAnimationStarted(double wall_clock_time) OVERRIDE {
+ EXPECT_FALSE(visible_);
+ started_times_++;
+ }
+
+ virtual void NotifyAnimationFinished(double wall_clock_time) OVERRIDE {
+ EXPECT_FALSE(visible_);
+ EXPECT_EQ(1, started_times_);
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ bool visible_;
+ int started_times_;
+ FakeContentLayerClient client_;
+ scoped_refptr<FakeContentLayer> content_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostAnimationTestRunAnimationWhenNotVisible);
+
+// Animations should not be started when frames are being skipped due to
+// checkerboard.
+class LayerTreeHostAnimationTestCheckerboardDoesntStartAnimations
+ : public LayerTreeHostAnimationTest {
+ virtual void SetupTree() OVERRIDE {
+ LayerTreeHostAnimationTest::SetupTree();
+ content_ = FakeContentLayer::Create(&client_);
+ content_->SetBounds(gfx::Size(4, 4));
+ content_->set_layer_animation_delegate(this);
+ layer_tree_host()->root_layer()->AddChild(content_);
+ }
+
+ virtual void InitializeSettings(LayerTreeSettings* settings) OVERRIDE {
+ // Make sure that drawing many times doesn't cause a checkerboarded
+ // animation to start so we avoid flake in this test.
+ settings->timeout_and_draw_when_animation_checkerboards = false;
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ prevented_draw_ = 0;
+ added_animations_ = 0;
+ started_times_ = 0;
+ finished_times_ = 0;
+
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DispatchAddInstantAnimation(Layer* layer_to_receive_animation)
+ OVERRIDE {
+ LayerTreeHostAnimationTest::DispatchAddInstantAnimation(
+ layer_to_receive_animation);
+ added_animations_++;
+ }
+
+ virtual bool PrepareToDrawOnThread(LayerTreeHostImpl* host_impl,
+ LayerTreeHostImpl::FrameData* frame_data,
+ bool result) OVERRIDE {
+ if (added_animations_ < 2)
+ return result;
+ if (TestEnded())
+ return result;
+ // Act like there is checkerboard when the second animation wants to draw.
+ ++prevented_draw_;
+ return false;
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ switch (layer_tree_host()->source_frame_number()) {
+ case 1:
+ // The animation is longer than 1 BeginFrame interval.
+ AddOpacityTransitionToLayer(content_.get(), 0.1, 0.2f, 0.8f, false);
+ added_animations_++;
+ break;
+ case 2:
+ // This second animation will not be drawn so it should not start.
+ AddAnimatedTransformToLayer(content_.get(), 0.1, 5, 5);
+ added_animations_++;
+ break;
+ }
+ }
+
+ virtual void NotifyAnimationStarted(double wall_clock_time) OVERRIDE {
+ if (TestEnded())
+ return;
+ started_times_++;
+ }
+
+ virtual void NotifyAnimationFinished(double wall_clock_time) OVERRIDE {
+ // We should be checkerboarding already, but it should still finish the
+ // first animation.
+ EXPECT_EQ(2, added_animations_);
+ finished_times_++;
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ // Make sure we tried to draw the second animation but failed.
+ EXPECT_LT(0, prevented_draw_);
+ // The first animation should be started, but the second should not because
+ // of checkerboard.
+ EXPECT_EQ(1, started_times_);
+ // The first animation should still be finished.
+ EXPECT_EQ(1, finished_times_);
+ }
+
+ int prevented_draw_;
+ int added_animations_;
+ int started_times_;
+ int finished_times_;
+ FakeContentLayerClient client_;
+ scoped_refptr<FakeContentLayer> content_;
+};
+
+MULTI_THREAD_TEST_F(
+ LayerTreeHostAnimationTestCheckerboardDoesntStartAnimations);
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/trees/layer_tree_host_unittest_context.cc b/chromium/cc/trees/layer_tree_host_unittest_context.cc
new file mode 100644
index 00000000000..3404c1ea9df
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_host_unittest_context.cc
@@ -0,0 +1,1905 @@
+// 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 "cc/trees/layer_tree_host.h"
+
+#include "base/basictypes.h"
+#include "cc/debug/test_context_provider.h"
+#include "cc/debug/test_web_graphics_context_3d.h"
+#include "cc/layers/content_layer.h"
+#include "cc/layers/heads_up_display_layer.h"
+#include "cc/layers/io_surface_layer.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/layers/picture_layer.h"
+#include "cc/layers/scrollbar_layer.h"
+#include "cc/layers/texture_layer.h"
+#include "cc/layers/texture_layer_impl.h"
+#include "cc/layers/video_layer.h"
+#include "cc/layers/video_layer_impl.h"
+#include "cc/output/filter_operations.h"
+#include "cc/test/fake_content_layer.h"
+#include "cc/test/fake_content_layer_client.h"
+#include "cc/test/fake_content_layer_impl.h"
+#include "cc/test/fake_delegated_renderer_layer.h"
+#include "cc/test/fake_delegated_renderer_layer_impl.h"
+#include "cc/test/fake_layer_tree_host_client.h"
+#include "cc/test/fake_output_surface.h"
+#include "cc/test/fake_scoped_ui_resource.h"
+#include "cc/test/fake_scrollbar.h"
+#include "cc/test/fake_scrollbar_layer.h"
+#include "cc/test/fake_video_frame_provider.h"
+#include "cc/test/layer_tree_test.h"
+#include "cc/test/render_pass_test_common.h"
+#include "cc/trees/layer_tree_host_impl.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "cc/trees/single_thread_proxy.h"
+#include "gpu/GLES2/gl2extchromium.h"
+#include "media/base/media.h"
+
+using media::VideoFrame;
+using WebKit::WebGraphicsContext3D;
+
+namespace cc {
+namespace {
+
+// These tests deal with losing the 3d graphics context.
+class LayerTreeHostContextTest : public LayerTreeTest {
+ public:
+ LayerTreeHostContextTest()
+ : LayerTreeTest(),
+ context3d_(NULL),
+ times_to_fail_create_(0),
+ times_to_fail_initialize_(0),
+ times_to_lose_on_create_(0),
+ times_to_lose_during_commit_(0),
+ times_to_lose_during_draw_(0),
+ times_to_fail_recreate_(0),
+ times_to_fail_reinitialize_(0),
+ times_to_lose_on_recreate_(0),
+ times_to_fail_create_offscreen_(0),
+ times_to_fail_recreate_offscreen_(0),
+ times_to_expect_create_failed_(0),
+ times_create_failed_(0),
+ times_offscreen_created_(0),
+ committed_at_least_once_(false),
+ context_should_support_io_surface_(false),
+ fallback_context_works_(false) {
+ media::InitializeMediaLibraryForTesting();
+ }
+
+ void LoseContext() {
+ context3d_->loseContextCHROMIUM(GL_GUILTY_CONTEXT_RESET_ARB,
+ GL_INNOCENT_CONTEXT_RESET_ARB);
+ context3d_ = NULL;
+ }
+
+ virtual scoped_ptr<TestWebGraphicsContext3D> CreateContext3d() {
+ return TestWebGraphicsContext3D::Create();
+ }
+
+ virtual scoped_ptr<OutputSurface> CreateOutputSurface(bool fallback)
+ OVERRIDE {
+ if (times_to_fail_create_) {
+ --times_to_fail_create_;
+ ExpectCreateToFail();
+ return scoped_ptr<OutputSurface>();
+ }
+
+ scoped_ptr<TestWebGraphicsContext3D> context3d = CreateContext3d();
+ context3d_ = context3d.get();
+
+ if (context_should_support_io_surface_) {
+ context3d_->set_have_extension_io_surface(true);
+ context3d_->set_have_extension_egl_image(true);
+ }
+
+ if (times_to_fail_initialize_ && !(fallback && fallback_context_works_)) {
+ --times_to_fail_initialize_;
+ // Make the context get lost during reinitialization.
+ // The number of times MakeCurrent succeeds is not important, and
+ // can be changed if needed to make this pass with future changes.
+ context3d_->set_times_make_current_succeeds(2);
+ ExpectCreateToFail();
+ } else if (times_to_lose_on_create_) {
+ --times_to_lose_on_create_;
+ LoseContext();
+ ExpectCreateToFail();
+ }
+
+ if (delegating_renderer()) {
+ return FakeOutputSurface::CreateDelegating3d(
+ context3d.PassAs<WebGraphicsContext3D>()).PassAs<OutputSurface>();
+ }
+ return FakeOutputSurface::Create3d(
+ context3d.PassAs<WebGraphicsContext3D>()).PassAs<OutputSurface>();
+ }
+
+ scoped_ptr<TestWebGraphicsContext3D> CreateOffscreenContext3d() {
+ if (!context3d_)
+ return scoped_ptr<TestWebGraphicsContext3D>();
+
+ ++times_offscreen_created_;
+
+ if (times_to_fail_create_offscreen_) {
+ --times_to_fail_create_offscreen_;
+ ExpectCreateToFail();
+ return scoped_ptr<TestWebGraphicsContext3D>();
+ }
+
+ scoped_ptr<TestWebGraphicsContext3D> offscreen_context3d =
+ TestWebGraphicsContext3D::Create().Pass();
+ DCHECK(offscreen_context3d);
+ context3d_->add_share_group_context(offscreen_context3d.get());
+
+ return offscreen_context3d.Pass();
+ }
+
+ virtual scoped_refptr<cc::ContextProvider>
+ OffscreenContextProviderForMainThread() OVERRIDE {
+ DCHECK(!HasImplThread());
+
+ if (!offscreen_contexts_main_thread_.get() ||
+ offscreen_contexts_main_thread_->DestroyedOnMainThread()) {
+ offscreen_contexts_main_thread_ = TestContextProvider::Create(
+ base::Bind(&LayerTreeHostContextTest::CreateOffscreenContext3d,
+ base::Unretained(this)));
+ if (offscreen_contexts_main_thread_.get() &&
+ !offscreen_contexts_main_thread_->BindToCurrentThread())
+ offscreen_contexts_main_thread_ = NULL;
+ }
+ return offscreen_contexts_main_thread_;
+ }
+
+ virtual scoped_refptr<cc::ContextProvider>
+ OffscreenContextProviderForCompositorThread() OVERRIDE {
+ DCHECK(HasImplThread());
+
+ if (!offscreen_contexts_compositor_thread_.get() ||
+ offscreen_contexts_compositor_thread_->DestroyedOnMainThread()) {
+ offscreen_contexts_compositor_thread_ = TestContextProvider::Create(
+ base::Bind(&LayerTreeHostContextTest::CreateOffscreenContext3d,
+ base::Unretained(this)));
+ }
+ return offscreen_contexts_compositor_thread_;
+ }
+
+ virtual bool PrepareToDrawOnThread(LayerTreeHostImpl* host_impl,
+ LayerTreeHostImpl::FrameData* frame,
+ bool result) OVERRIDE {
+ EXPECT_TRUE(result);
+ if (!times_to_lose_during_draw_)
+ return result;
+
+ --times_to_lose_during_draw_;
+ context3d_->set_times_make_current_succeeds(0);
+
+ times_to_fail_create_ = times_to_fail_recreate_;
+ times_to_fail_recreate_ = 0;
+ times_to_fail_initialize_ = times_to_fail_reinitialize_;
+ times_to_fail_reinitialize_ = 0;
+ times_to_lose_on_create_ = times_to_lose_on_recreate_;
+ times_to_lose_on_recreate_ = 0;
+ times_to_fail_create_offscreen_ = times_to_fail_recreate_offscreen_;
+ times_to_fail_recreate_offscreen_ = 0;
+
+ return result;
+ }
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ committed_at_least_once_ = true;
+
+ if (!times_to_lose_during_commit_)
+ return;
+ --times_to_lose_during_commit_;
+ LoseContext();
+
+ times_to_fail_create_ = times_to_fail_recreate_;
+ times_to_fail_recreate_ = 0;
+ times_to_fail_initialize_ = times_to_fail_reinitialize_;
+ times_to_fail_reinitialize_ = 0;
+ times_to_lose_on_create_ = times_to_lose_on_recreate_;
+ times_to_lose_on_recreate_ = 0;
+ times_to_fail_create_offscreen_ = times_to_fail_recreate_offscreen_;
+ times_to_fail_recreate_offscreen_ = 0;
+ }
+
+ virtual void DidFailToInitializeOutputSurface() OVERRIDE {
+ ++times_create_failed_;
+ }
+
+ virtual void TearDown() OVERRIDE {
+ LayerTreeTest::TearDown();
+ EXPECT_EQ(times_to_expect_create_failed_, times_create_failed_);
+ }
+
+ void ExpectCreateToFail() {
+ ++times_to_expect_create_failed_;
+ }
+
+ protected:
+ TestWebGraphicsContext3D* context3d_;
+ int times_to_fail_create_;
+ int times_to_fail_initialize_;
+ int times_to_lose_on_create_;
+ int times_to_lose_during_commit_;
+ int times_to_lose_during_draw_;
+ int times_to_fail_recreate_;
+ int times_to_fail_reinitialize_;
+ int times_to_lose_on_recreate_;
+ int times_to_fail_create_offscreen_;
+ int times_to_fail_recreate_offscreen_;
+ int times_to_expect_create_failed_;
+ int times_create_failed_;
+ int times_offscreen_created_;
+ bool committed_at_least_once_;
+ bool context_should_support_io_surface_;
+ bool fallback_context_works_;
+
+ scoped_refptr<TestContextProvider> offscreen_contexts_main_thread_;
+ scoped_refptr<TestContextProvider> offscreen_contexts_compositor_thread_;
+};
+
+class LayerTreeHostContextTestLostContextSucceeds
+ : public LayerTreeHostContextTest {
+ public:
+ LayerTreeHostContextTestLostContextSucceeds()
+ : LayerTreeHostContextTest(),
+ test_case_(0),
+ num_losses_(0),
+ recovered_context_(true),
+ first_initialized_(false) {}
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidInitializeOutputSurface(bool succeeded) OVERRIDE {
+ EXPECT_TRUE(succeeded);
+
+ if (first_initialized_)
+ ++num_losses_;
+ else
+ first_initialized_ = true;
+
+ recovered_context_ = true;
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ EXPECT_EQ(11u, test_case_);
+ EXPECT_EQ(8 + 10 + 10 + 1, num_losses_);
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ // If the last frame had a context loss, then we'll commit again to
+ // recover.
+ if (!recovered_context_)
+ return;
+ if (times_to_lose_during_commit_)
+ return;
+ if (times_to_lose_during_draw_)
+ return;
+
+ recovered_context_ = false;
+ if (NextTestCase())
+ InvalidateAndSetNeedsCommit();
+ else
+ EndTest();
+ }
+
+ virtual void InvalidateAndSetNeedsCommit() {
+ // Cause damage so we try to draw.
+ layer_tree_host()->root_layer()->SetNeedsDisplay();
+ layer_tree_host()->SetNeedsCommit();
+ }
+
+ bool NextTestCase() {
+ static const TestCase kTests[] = {
+ // Losing the context and failing to recreate it (or losing it again
+ // immediately) a small number of times should succeed.
+ { 1, // times_to_lose_during_commit
+ 0, // times_to_lose_during_draw
+ 3, // times_to_fail_reinitialize
+ 0, // times_to_fail_recreate
+ 0, // times_to_lose_on_recreate
+ 0, // times_to_fail_recreate_offscreen
+ false, // fallback_context_works
+ },
+ { 0, // times_to_lose_during_commit
+ 1, // times_to_lose_during_draw
+ 3, // times_to_fail_reinitialize
+ 0, // times_to_fail_recreate
+ 0, // times_to_lose_on_recreate
+ 0, // times_to_fail_recreate_offscreen
+ false, // fallback_context_works
+ },
+ { 1, // times_to_lose_during_commit
+ 0, // times_to_lose_during_draw
+ 0, // times_to_fail_reinitialize
+ 3, // times_to_fail_recreate
+ 0, // times_to_lose_on_recreate
+ 0, // times_to_fail_recreate_offscreen
+ false, // fallback_context_works
+ },
+ { 0, // times_to_lose_during_commit
+ 1, // times_to_lose_during_draw
+ 0, // times_to_fail_reinitialize
+ 3, // times_to_fail_recreate
+ 0, // times_to_lose_on_recreate
+ 0, // times_to_fail_recreate_offscreen
+ false, // fallback_context_works
+ },
+ { 1, // times_to_lose_during_commit
+ 0, // times_to_lose_during_draw
+ 0, // times_to_fail_reinitialize
+ 0, // times_to_fail_recreate
+ 3, // times_to_lose_on_recreate
+ 0, // times_to_fail_recreate_offscreen
+ false, // fallback_context_works
+ },
+ { 0, // times_to_lose_during_commit
+ 1, // times_to_lose_during_draw
+ 0, // times_to_fail_reinitialize
+ 0, // times_to_fail_recreate
+ 3, // times_to_lose_on_recreate
+ 0, // times_to_fail_recreate_offscreen
+ false, // fallback_context_works
+ },
+ { 1, // times_to_lose_during_commit
+ 0, // times_to_lose_during_draw
+ 0, // times_to_fail_reinitialize
+ 0, // times_to_fail_recreate
+ 0, // times_to_lose_on_recreate
+ 3, // times_to_fail_recreate_offscreen
+ false, // fallback_context_works
+ },
+ { 0, // times_to_lose_during_commit
+ 1, // times_to_lose_during_draw
+ 0, // times_to_fail_reinitialize
+ 0, // times_to_fail_recreate
+ 0, // times_to_lose_on_recreate
+ 3, // times_to_fail_recreate_offscreen
+ false, // fallback_context_works
+ },
+ // Losing the context and recreating it any number of times should
+ // succeed.
+ { 10, // times_to_lose_during_commit
+ 0, // times_to_lose_during_draw
+ 0, // times_to_fail_reinitialize
+ 0, // times_to_fail_recreate
+ 0, // times_to_lose_on_recreate
+ 0, // times_to_fail_recreate_offscreen
+ false, // fallback_context_works
+ },
+ { 0, // times_to_lose_during_commit
+ 10, // times_to_lose_during_draw
+ 0, // times_to_fail_reinitialize
+ 0, // times_to_fail_recreate
+ 0, // times_to_lose_on_recreate
+ 0, // times_to_fail_recreate_offscreen
+ false, // fallback_context_works
+ },
+ // Losing the context, failing to reinitialize it, and making a fallback
+ // context should work.
+ { 0, // times_to_lose_during_commit
+ 1, // times_to_lose_during_draw
+ 10, // times_to_fail_reinitialize
+ 0, // times_to_fail_recreate
+ 0, // times_to_lose_on_recreate
+ 0, // times_to_fail_recreate_offscreen
+ true, // fallback_context_works
+ },
+ };
+
+ if (test_case_ >= arraysize(kTests))
+ return false;
+
+ times_to_lose_during_commit_ =
+ kTests[test_case_].times_to_lose_during_commit;
+ times_to_lose_during_draw_ =
+ kTests[test_case_].times_to_lose_during_draw;
+ times_to_fail_reinitialize_ = kTests[test_case_].times_to_fail_reinitialize;
+ times_to_fail_recreate_ = kTests[test_case_].times_to_fail_recreate;
+ times_to_lose_on_recreate_ = kTests[test_case_].times_to_lose_on_recreate;
+ times_to_fail_recreate_offscreen_ =
+ kTests[test_case_].times_to_fail_recreate_offscreen;
+ fallback_context_works_ = kTests[test_case_].fallback_context_works;
+ ++test_case_;
+ return true;
+ }
+
+ struct TestCase {
+ int times_to_lose_during_commit;
+ int times_to_lose_during_draw;
+ int times_to_fail_reinitialize;
+ int times_to_fail_recreate;
+ int times_to_lose_on_recreate;
+ int times_to_fail_recreate_offscreen;
+ bool fallback_context_works;
+ };
+
+ protected:
+ size_t test_case_;
+ int num_losses_;
+ bool recovered_context_;
+ bool first_initialized_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostContextTestLostContextSucceeds);
+
+class LayerTreeHostContextTestLostContextSucceedsWithContent
+ : public LayerTreeHostContextTestLostContextSucceeds {
+ public:
+ LayerTreeHostContextTestLostContextSucceedsWithContent()
+ : LayerTreeHostContextTestLostContextSucceeds() {}
+
+ virtual void SetupTree() OVERRIDE {
+ root_ = Layer::Create();
+ root_->SetBounds(gfx::Size(10, 10));
+ root_->SetAnchorPoint(gfx::PointF());
+ root_->SetIsDrawable(true);
+
+ content_ = FakeContentLayer::Create(&client_);
+ content_->SetBounds(gfx::Size(10, 10));
+ content_->SetAnchorPoint(gfx::PointF());
+ content_->SetIsDrawable(true);
+ if (use_surface_) {
+ content_->SetForceRenderSurface(true);
+ // Filters require us to create an offscreen context.
+ FilterOperations filters;
+ filters.Append(FilterOperation::CreateGrayscaleFilter(0.5f));
+ content_->SetFilters(filters);
+ content_->SetBackgroundFilters(filters);
+ }
+
+ root_->AddChild(content_);
+
+ layer_tree_host()->SetRootLayer(root_);
+ LayerTreeHostContextTest::SetupTree();
+ }
+
+ virtual void InvalidateAndSetNeedsCommit() OVERRIDE {
+ // Invalidate the render surface so we don't try to use a cached copy of the
+ // surface. We want to make sure to test the drawing paths for drawing to
+ // a child surface.
+ content_->SetNeedsDisplay();
+ LayerTreeHostContextTestLostContextSucceeds::InvalidateAndSetNeedsCommit();
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ FakeContentLayerImpl* content_impl = static_cast<FakeContentLayerImpl*>(
+ host_impl->active_tree()->root_layer()->children()[0]);
+ // Even though the context was lost, we should have a resource. The
+ // TestWebGraphicsContext3D ensures that this resource is created with
+ // the active context.
+ EXPECT_TRUE(content_impl->HaveResourceForTileAt(0, 0));
+
+ cc::ContextProvider* contexts =
+ host_impl->resource_provider()->offscreen_context_provider();
+ if (use_surface_) {
+ EXPECT_TRUE(contexts->Context3d());
+ // TODO(danakj): Make a fake GrContext.
+ // EXPECT_TRUE(contexts->GrContext());
+ } else {
+ EXPECT_FALSE(contexts);
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ LayerTreeHostContextTestLostContextSucceeds::AfterTest();
+ if (use_surface_) {
+ // 1 create to start with +
+ // 6 from test cases that fail on initializing the renderer (after the
+ // offscreen context is created) +
+ // 6 from test cases that lose the offscreen context directly +
+ // 4 from test cases that create a fallback +
+ // All the test cases that recreate both contexts only once
+ // per time it is lost.
+ EXPECT_EQ(6 + 6 + 1 + 4 + num_losses_, times_offscreen_created_);
+ } else {
+ EXPECT_EQ(0, times_offscreen_created_);
+ }
+ }
+
+ protected:
+ bool use_surface_;
+ FakeContentLayerClient client_;
+ scoped_refptr<Layer> root_;
+ scoped_refptr<ContentLayer> content_;
+};
+
+TEST_F(LayerTreeHostContextTestLostContextSucceedsWithContent,
+ NoSurface_SingleThread_DirectRenderer) {
+ use_surface_ = false;
+ RunTest(false, false, false);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextSucceedsWithContent,
+ NoSurface_SingleThread_DelegatingRenderer) {
+ use_surface_ = false;
+ RunTest(false, true, false);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextSucceedsWithContent,
+ NoSurface_MultiThread_DirectRenderer_MainThreadPaint) {
+ use_surface_ = false;
+ RunTest(true, false, false);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextSucceedsWithContent,
+ NoSurface_MultiThread_DirectRenderer_ImplSidePaint) {
+ use_surface_ = false;
+ RunTest(true, false, true);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextSucceedsWithContent,
+ NoSurface_MultiThread_DelegatingRenderer_MainThreadPaint) {
+ use_surface_ = false;
+ RunTest(true, true, false);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextSucceedsWithContent,
+ NoSurface_MultiThread_DelegatingRenderer_ImplSidePaint) {
+ use_surface_ = false;
+ RunTest(true, true, true);
+}
+
+// Surfaces don't exist with a delegating renderer.
+TEST_F(LayerTreeHostContextTestLostContextSucceedsWithContent,
+ WithSurface_SingleThread_DirectRenderer) {
+ use_surface_ = true;
+ RunTest(false, false, false);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextSucceedsWithContent,
+ WithSurface_MultiThread_DirectRenderer_MainThreadPaint) {
+ use_surface_ = true;
+ RunTest(true, false, false);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextSucceedsWithContent,
+ WithSurface_MultiThread_DirectRenderer_ImplSidePaint) {
+ use_surface_ = true;
+ RunTest(true, false, true);
+}
+
+class LayerTreeHostContextTestOffscreenContextFails
+ : public LayerTreeHostContextTest {
+ public:
+ virtual void SetupTree() OVERRIDE {
+ root_ = Layer::Create();
+ root_->SetBounds(gfx::Size(10, 10));
+ root_->SetAnchorPoint(gfx::PointF());
+ root_->SetIsDrawable(true);
+
+ content_ = FakeContentLayer::Create(&client_);
+ content_->SetBounds(gfx::Size(10, 10));
+ content_->SetAnchorPoint(gfx::PointF());
+ content_->SetIsDrawable(true);
+ content_->SetForceRenderSurface(true);
+ // Filters require us to create an offscreen context.
+ FilterOperations filters;
+ filters.Append(FilterOperation::CreateGrayscaleFilter(0.5f));
+ content_->SetFilters(filters);
+ content_->SetBackgroundFilters(filters);
+
+ root_->AddChild(content_);
+
+ layer_tree_host()->SetRootLayer(root_);
+ LayerTreeHostContextTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ times_to_fail_create_offscreen_ = 1;
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ cc::ContextProvider* contexts =
+ host_impl->resource_provider()->offscreen_context_provider();
+ EXPECT_FALSE(contexts);
+
+ // This did not lead to create failure.
+ times_to_expect_create_failed_ = 0;
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ protected:
+ FakeContentLayerClient client_;
+ scoped_refptr<Layer> root_;
+ scoped_refptr<ContentLayer> content_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostContextTestOffscreenContextFails);
+
+class LayerTreeHostContextTestLostContextFails
+ : public LayerTreeHostContextTest {
+ public:
+ LayerTreeHostContextTestLostContextFails()
+ : LayerTreeHostContextTest(),
+ num_commits_(0),
+ first_initialized_(false) {
+ times_to_lose_during_commit_ = 1;
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidInitializeOutputSurface(bool succeeded) OVERRIDE {
+ if (first_initialized_) {
+ EXPECT_FALSE(succeeded);
+ EndTest();
+ } else {
+ first_initialized_ = true;
+ }
+ }
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ LayerTreeHostContextTest::CommitCompleteOnThread(host_impl);
+
+ ++num_commits_;
+ if (num_commits_ == 1) {
+ // When the context is ok, we should have these things.
+ EXPECT_TRUE(host_impl->output_surface());
+ EXPECT_TRUE(host_impl->renderer());
+ EXPECT_TRUE(host_impl->resource_provider());
+ return;
+ }
+
+ // When context recreation fails we shouldn't be left with any of them.
+ EXPECT_FALSE(host_impl->output_surface());
+ EXPECT_FALSE(host_impl->renderer());
+ EXPECT_FALSE(host_impl->resource_provider());
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ int num_commits_;
+ bool first_initialized_;
+};
+
+TEST_F(LayerTreeHostContextTestLostContextFails,
+ FailReinitialize100_SingleThread_DirectRenderer) {
+ times_to_fail_reinitialize_ = 100;
+ times_to_fail_recreate_ = 0;
+ times_to_lose_on_recreate_ = 0;
+ RunTest(false, false, false);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextFails,
+ FailReinitialize100_SingleThread_DelegatingRenderer) {
+ times_to_fail_reinitialize_ = 100;
+ times_to_fail_recreate_ = 0;
+ times_to_lose_on_recreate_ = 0;
+ RunTest(false, true, false);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextFails,
+ FailReinitialize100_MultiThread_DirectRenderer_MainThreadPaint) {
+ times_to_fail_reinitialize_ = 100;
+ times_to_fail_recreate_ = 0;
+ times_to_lose_on_recreate_ = 0;
+ RunTest(true, false, false);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextFails,
+ FailReinitialize100_MultiThread_DirectRenderer_ImplSidePaint) {
+ times_to_fail_reinitialize_ = 100;
+ times_to_fail_recreate_ = 0;
+ times_to_lose_on_recreate_ = 0;
+ RunTest(true, false, true);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextFails,
+ FailReinitialize100_MultiThread_DelegatingRenderer_MainThreadPaint) {
+ times_to_fail_reinitialize_ = 100;
+ times_to_fail_recreate_ = 0;
+ times_to_lose_on_recreate_ = 0;
+ RunTest(true, true, false);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextFails,
+ FailReinitialize100_MultiThread_DelegatingRenderer_ImplSidePaint) {
+ times_to_fail_reinitialize_ = 100;
+ times_to_fail_recreate_ = 0;
+ times_to_lose_on_recreate_ = 0;
+ RunTest(true, true, true);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextFails,
+ FailRecreate100_SingleThread_DirectRenderer) {
+ times_to_fail_reinitialize_ = 0;
+ times_to_fail_recreate_ = 100;
+ times_to_lose_on_recreate_ = 0;
+ RunTest(false, false, false);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextFails,
+ FailRecreate100_SingleThread_DelegatingRenderer) {
+ times_to_fail_reinitialize_ = 0;
+ times_to_fail_recreate_ = 100;
+ times_to_lose_on_recreate_ = 0;
+ RunTest(false, true, false);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextFails,
+ FailRecreate100_MultiThread_DirectRenderer_MainThreadPaint) {
+ times_to_fail_reinitialize_ = 0;
+ times_to_fail_recreate_ = 100;
+ times_to_lose_on_recreate_ = 0;
+ RunTest(true, false, false);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextFails,
+ FailRecreate100_MultiThread_DirectRenderer_ImplSidePaint) {
+ times_to_fail_reinitialize_ = 0;
+ times_to_fail_recreate_ = 100;
+ times_to_lose_on_recreate_ = 0;
+ RunTest(true, false, true);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextFails,
+ FailRecreate100_MultiThread_DelegatingRenderer_MainThreadPaint) {
+ times_to_fail_reinitialize_ = 0;
+ times_to_fail_recreate_ = 100;
+ times_to_lose_on_recreate_ = 0;
+ RunTest(true, true, false);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextFails,
+ FailRecreate100_MultiThread_DelegatingRenderer_ImplSidePaint) {
+ times_to_fail_reinitialize_ = 0;
+ times_to_fail_recreate_ = 100;
+ times_to_lose_on_recreate_ = 0;
+ RunTest(true, true, true);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextFails,
+ LoseOnRecreate100_SingleThread_DirectRenderer) {
+ times_to_fail_reinitialize_ = 0;
+ times_to_fail_recreate_ = 0;
+ times_to_lose_on_recreate_ = 100;
+ RunTest(false, false, false);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextFails,
+ LoseOnRecreate100_SingleThread_DelegatingRenderer) {
+ times_to_fail_reinitialize_ = 0;
+ times_to_fail_recreate_ = 0;
+ times_to_lose_on_recreate_ = 100;
+ RunTest(false, true, false);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextFails,
+ LoseOnRecreate100_MultiThread_DirectRenderer_MainThreadPaint) {
+ times_to_fail_reinitialize_ = 0;
+ times_to_fail_recreate_ = 0;
+ times_to_lose_on_recreate_ = 100;
+ RunTest(true, false, false);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextFails,
+ LoseOnRecreate100_MultiThread_DirectRenderer_ImplSidePaint) {
+ times_to_fail_reinitialize_ = 0;
+ times_to_fail_recreate_ = 0;
+ times_to_lose_on_recreate_ = 100;
+ RunTest(true, false, true);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextFails,
+ LoseOnRecreate100_MultiThread_DelegatingRenderer_MainThreadPaint) {
+ times_to_fail_reinitialize_ = 0;
+ times_to_fail_recreate_ = 0;
+ times_to_lose_on_recreate_ = 100;
+ RunTest(true, true, false);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextFails,
+ LoseOnRecreate100_MultiThread_DelegatingRenderer_ImplSidePaint) {
+ times_to_fail_reinitialize_ = 0;
+ times_to_fail_recreate_ = 0;
+ times_to_lose_on_recreate_ = 100;
+ RunTest(true, true, true);
+}
+
+class LayerTreeHostContextTestFinishAllRenderingAfterLoss
+ : public LayerTreeHostContextTest {
+ public:
+ virtual void BeginTest() OVERRIDE {
+ // Lose the context until the compositor gives up on it.
+ first_initialized_ = false;
+ times_to_lose_during_commit_ = 1;
+ times_to_fail_reinitialize_ = 10;
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidInitializeOutputSurface(bool succeeded) OVERRIDE {
+ if (first_initialized_) {
+ EXPECT_FALSE(succeeded);
+ layer_tree_host()->FinishAllRendering();
+ EndTest();
+ } else {
+ first_initialized_ = true;
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ bool first_initialized_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostContextTestFinishAllRenderingAfterLoss);
+
+class LayerTreeHostContextTestLostContextAndEvictTextures
+ : public LayerTreeHostContextTest {
+ public:
+ LayerTreeHostContextTestLostContextAndEvictTextures()
+ : LayerTreeHostContextTest(),
+ layer_(FakeContentLayer::Create(&client_)),
+ impl_host_(0),
+ num_commits_(0) {}
+
+ virtual void SetupTree() OVERRIDE {
+ layer_->SetBounds(gfx::Size(10, 20));
+ layer_tree_host()->SetRootLayer(layer_);
+ LayerTreeHostContextTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ void PostEvictTextures() {
+ if (HasImplThread()) {
+ ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &LayerTreeHostContextTestLostContextAndEvictTextures::
+ EvictTexturesOnImplThread,
+ base::Unretained(this)));
+ } else {
+ DebugScopedSetImplThread impl(proxy());
+ EvictTexturesOnImplThread();
+ }
+ }
+
+ void EvictTexturesOnImplThread() {
+ impl_host_->EvictTexturesForTesting();
+ if (lose_after_evict_)
+ LoseContext();
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ if (num_commits_ > 1)
+ return;
+ EXPECT_TRUE(layer_->HaveBackingAt(0, 0));
+ PostEvictTextures();
+ }
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ LayerTreeHostContextTest::CommitCompleteOnThread(impl);
+ if (num_commits_ > 1)
+ return;
+ ++num_commits_;
+ if (!lose_after_evict_)
+ LoseContext();
+ impl_host_ = impl;
+ }
+
+ virtual void DidInitializeOutputSurface(bool succeeded) OVERRIDE {
+ EXPECT_TRUE(succeeded);
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ protected:
+ bool lose_after_evict_;
+ FakeContentLayerClient client_;
+ scoped_refptr<FakeContentLayer> layer_;
+ LayerTreeHostImpl* impl_host_;
+ int num_commits_;
+};
+
+TEST_F(LayerTreeHostContextTestLostContextAndEvictTextures,
+ LoseAfterEvict_SingleThread_DirectRenderer) {
+ lose_after_evict_ = true;
+ RunTest(false, false, false);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextAndEvictTextures,
+ LoseAfterEvict_SingleThread_DelegatingRenderer) {
+ lose_after_evict_ = true;
+ RunTest(false, true, false);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextAndEvictTextures,
+ LoseAfterEvict_MultiThread_DirectRenderer_MainThreadPaint) {
+ lose_after_evict_ = true;
+ RunTest(true, false, false);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextAndEvictTextures,
+ LoseAfterEvict_MultiThread_DirectRenderer_ImplSidePaint) {
+ lose_after_evict_ = true;
+ RunTest(true, false, true);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextAndEvictTextures,
+ LoseAfterEvict_MultiThread_DelegatingRenderer_MainThreadPaint) {
+ lose_after_evict_ = true;
+ RunTest(true, true, false);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextAndEvictTextures,
+ LoseAfterEvict_MultiThread_DelegatingRenderer_ImplSidePaint) {
+ lose_after_evict_ = true;
+ RunTest(true, true, true);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextAndEvictTextures,
+ LoseBeforeEvict_SingleThread_DirectRenderer) {
+ lose_after_evict_ = false;
+ RunTest(false, false, false);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextAndEvictTextures,
+ LoseBeforeEvict_SingleThread_DelegatingRenderer) {
+ lose_after_evict_ = false;
+ RunTest(false, true, false);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextAndEvictTextures,
+ LoseBeforeEvict_MultiThread_DirectRenderer_MainThreadPaint) {
+ lose_after_evict_ = false;
+ RunTest(true, false, false);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextAndEvictTextures,
+ LoseBeforeEvict_MultiThread_DirectRenderer_ImplSidePaint) {
+ lose_after_evict_ = false;
+ RunTest(true, false, true);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextAndEvictTextures,
+ LoseBeforeEvict_MultiThread_DelegatingRenderer_MainThreadPaint) {
+ lose_after_evict_ = false;
+ RunTest(true, true, false);
+}
+
+TEST_F(LayerTreeHostContextTestLostContextAndEvictTextures,
+ LoseBeforeEvict_MultiThread_DelegatingRenderer_ImplSidePaint) {
+ lose_after_evict_ = false;
+ RunTest(true, true, true);
+}
+
+class LayerTreeHostContextTestLostContextWhileUpdatingResources
+ : public LayerTreeHostContextTest {
+ public:
+ LayerTreeHostContextTestLostContextWhileUpdatingResources()
+ : parent_(FakeContentLayer::Create(&client_)),
+ num_children_(50),
+ times_to_lose_on_end_query_(3) {}
+
+ virtual scoped_ptr<TestWebGraphicsContext3D> CreateContext3d() OVERRIDE {
+ scoped_ptr<TestWebGraphicsContext3D> context =
+ LayerTreeHostContextTest::CreateContext3d();
+ if (times_to_lose_on_end_query_) {
+ --times_to_lose_on_end_query_;
+ context->set_times_end_query_succeeds(5);
+ }
+ return context.Pass();
+ }
+
+ virtual void SetupTree() OVERRIDE {
+ parent_->SetBounds(gfx::Size(num_children_, 1));
+
+ for (int i = 0; i < num_children_; i++) {
+ scoped_refptr<FakeContentLayer> child =
+ FakeContentLayer::Create(&client_);
+ child->SetPosition(gfx::PointF(i, 0.f));
+ child->SetBounds(gfx::Size(1, 1));
+ parent_->AddChild(child);
+ }
+
+ layer_tree_host()->SetRootLayer(parent_);
+ LayerTreeHostContextTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ LayerTreeHostContextTest::CommitCompleteOnThread(impl);
+ EndTest();
+ }
+
+ virtual void DidInitializeOutputSurface(bool succeeded) OVERRIDE {
+ EXPECT_TRUE(succeeded);
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ EXPECT_EQ(0, times_to_lose_on_end_query_);
+ }
+
+ private:
+ FakeContentLayerClient client_;
+ scoped_refptr<FakeContentLayer> parent_;
+ int num_children_;
+ int times_to_lose_on_end_query_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostContextTestLostContextWhileUpdatingResources);
+
+class LayerTreeHostContextTestLayersNotified
+ : public LayerTreeHostContextTest {
+ public:
+ LayerTreeHostContextTestLayersNotified()
+ : LayerTreeHostContextTest(),
+ num_commits_(0) {}
+
+ virtual void SetupTree() OVERRIDE {
+ root_ = FakeContentLayer::Create(&client_);
+ child_ = FakeContentLayer::Create(&client_);
+ grandchild_ = FakeContentLayer::Create(&client_);
+
+ root_->AddChild(child_);
+ child_->AddChild(grandchild_);
+
+ layer_tree_host()->SetRootLayer(root_);
+ LayerTreeHostContextTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ LayerTreeHostContextTest::DidActivateTreeOnThread(host_impl);
+
+ FakeContentLayerImpl* root = static_cast<FakeContentLayerImpl*>(
+ host_impl->active_tree()->root_layer());
+ FakeContentLayerImpl* child = static_cast<FakeContentLayerImpl*>(
+ root->children()[0]);
+ FakeContentLayerImpl* grandchild = static_cast<FakeContentLayerImpl*>(
+ child->children()[0]);
+
+ ++num_commits_;
+ switch (num_commits_) {
+ case 1:
+ EXPECT_EQ(0u, root->lost_output_surface_count());
+ EXPECT_EQ(0u, child->lost_output_surface_count());
+ EXPECT_EQ(0u, grandchild->lost_output_surface_count());
+ // Lose the context and struggle to recreate it.
+ LoseContext();
+ times_to_fail_create_ = 1;
+ break;
+ case 2:
+ EXPECT_EQ(1u, root->lost_output_surface_count());
+ EXPECT_EQ(1u, child->lost_output_surface_count());
+ EXPECT_EQ(1u, grandchild->lost_output_surface_count());
+ // Lose the context and again during recreate.
+ LoseContext();
+ times_to_lose_on_create_ = 1;
+ break;
+ case 3:
+ EXPECT_EQ(3u, root->lost_output_surface_count());
+ EXPECT_EQ(3u, child->lost_output_surface_count());
+ EXPECT_EQ(3u, grandchild->lost_output_surface_count());
+ // Lose the context and again during reinitialization.
+ LoseContext();
+ times_to_fail_initialize_ = 1;
+ break;
+ case 4:
+ EXPECT_EQ(5u, root->lost_output_surface_count());
+ EXPECT_EQ(5u, child->lost_output_surface_count());
+ EXPECT_EQ(5u, grandchild->lost_output_surface_count());
+ EndTest();
+ break;
+ default:
+ NOTREACHED();
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ int num_commits_;
+
+ FakeContentLayerClient client_;
+ scoped_refptr<FakeContentLayer> root_;
+ scoped_refptr<FakeContentLayer> child_;
+ scoped_refptr<FakeContentLayer> grandchild_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostContextTestLayersNotified);
+
+class LayerTreeHostContextTestDontUseLostResources
+ : public LayerTreeHostContextTest {
+ public:
+ virtual void SetupTree() OVERRIDE {
+ scoped_refptr<Layer> root_ = Layer::Create();
+ root_->SetBounds(gfx::Size(10, 10));
+ root_->SetAnchorPoint(gfx::PointF());
+ root_->SetIsDrawable(true);
+
+ scoped_refptr<FakeDelegatedRendererLayer> delegated_ =
+ FakeDelegatedRendererLayer::Create(NULL);
+ delegated_->SetBounds(gfx::Size(10, 10));
+ delegated_->SetAnchorPoint(gfx::PointF());
+ delegated_->SetIsDrawable(true);
+ root_->AddChild(delegated_);
+
+ scoped_refptr<ContentLayer> content_ = ContentLayer::Create(&client_);
+ content_->SetBounds(gfx::Size(10, 10));
+ content_->SetAnchorPoint(gfx::PointF());
+ content_->SetIsDrawable(true);
+ root_->AddChild(content_);
+
+ scoped_refptr<TextureLayer> texture_ = TextureLayer::Create(NULL);
+ texture_->SetBounds(gfx::Size(10, 10));
+ texture_->SetAnchorPoint(gfx::PointF());
+ texture_->SetIsDrawable(true);
+ root_->AddChild(texture_);
+
+ scoped_refptr<ContentLayer> mask_ = ContentLayer::Create(&client_);
+ mask_->SetBounds(gfx::Size(10, 10));
+ mask_->SetAnchorPoint(gfx::PointF());
+
+ scoped_refptr<ContentLayer> content_with_mask_ =
+ ContentLayer::Create(&client_);
+ content_with_mask_->SetBounds(gfx::Size(10, 10));
+ content_with_mask_->SetAnchorPoint(gfx::PointF());
+ content_with_mask_->SetIsDrawable(true);
+ content_with_mask_->SetMaskLayer(mask_.get());
+ root_->AddChild(content_with_mask_);
+
+ scoped_refptr<VideoLayer> video_color_ = VideoLayer::Create(
+ &color_frame_provider_);
+ video_color_->SetBounds(gfx::Size(10, 10));
+ video_color_->SetAnchorPoint(gfx::PointF());
+ video_color_->SetIsDrawable(true);
+ root_->AddChild(video_color_);
+
+ scoped_refptr<VideoLayer> video_hw_ = VideoLayer::Create(
+ &hw_frame_provider_);
+ video_hw_->SetBounds(gfx::Size(10, 10));
+ video_hw_->SetAnchorPoint(gfx::PointF());
+ video_hw_->SetIsDrawable(true);
+ root_->AddChild(video_hw_);
+
+ scoped_refptr<VideoLayer> video_scaled_hw_ = VideoLayer::Create(
+ &scaled_hw_frame_provider_);
+ video_scaled_hw_->SetBounds(gfx::Size(10, 10));
+ video_scaled_hw_->SetAnchorPoint(gfx::PointF());
+ video_scaled_hw_->SetIsDrawable(true);
+ root_->AddChild(video_scaled_hw_);
+
+ if (!delegating_renderer()) {
+ // TODO(danakj): IOSurface layer can not be transported. crbug.com/239335
+ scoped_refptr<IOSurfaceLayer> io_surface_ = IOSurfaceLayer::Create();
+ io_surface_->SetBounds(gfx::Size(10, 10));
+ io_surface_->SetAnchorPoint(gfx::PointF());
+ io_surface_->SetIsDrawable(true);
+ io_surface_->SetIOSurfaceProperties(1, gfx::Size(10, 10));
+ root_->AddChild(io_surface_);
+ }
+
+ // Enable the hud.
+ LayerTreeDebugState debug_state;
+ debug_state.show_property_changed_rects = true;
+ layer_tree_host()->SetDebugState(debug_state);
+
+ scoped_refptr<ScrollbarLayer> scrollbar_ = ScrollbarLayer::Create(
+ scoped_ptr<Scrollbar>(new FakeScrollbar).Pass(),
+ content_->id());
+ scrollbar_->SetBounds(gfx::Size(10, 10));
+ scrollbar_->SetAnchorPoint(gfx::PointF());
+ scrollbar_->SetIsDrawable(true);
+ root_->AddChild(scrollbar_);
+
+ layer_tree_host()->SetRootLayer(root_);
+ LayerTreeHostContextTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ context_should_support_io_surface_ = true;
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ LayerTreeHostContextTest::CommitCompleteOnThread(host_impl);
+
+ ResourceProvider* resource_provider = host_impl->resource_provider();
+
+ if (host_impl->active_tree()->source_frame_number() == 0) {
+ // Set up impl resources on the first commit.
+
+ scoped_ptr<TestRenderPass> pass_for_quad = TestRenderPass::Create();
+ pass_for_quad->SetNew(
+ // AppendOneOfEveryQuadType() makes a RenderPass quad with this id.
+ RenderPass::Id(1, 1),
+ gfx::Rect(0, 0, 10, 10),
+ gfx::Rect(0, 0, 10, 10),
+ gfx::Transform());
+
+ scoped_ptr<TestRenderPass> pass = TestRenderPass::Create();
+ pass->SetNew(
+ RenderPass::Id(2, 1),
+ gfx::Rect(0, 0, 10, 10),
+ gfx::Rect(0, 0, 10, 10),
+ gfx::Transform());
+ pass->AppendOneOfEveryQuadType(resource_provider, RenderPass::Id(2, 1));
+
+ ScopedPtrVector<RenderPass> pass_list;
+ pass_list.push_back(pass_for_quad.PassAs<RenderPass>());
+ pass_list.push_back(pass.PassAs<RenderPass>());
+
+ // First child is the delegated layer.
+ FakeDelegatedRendererLayerImpl* delegated_impl =
+ static_cast<FakeDelegatedRendererLayerImpl*>(
+ host_impl->active_tree()->root_layer()->children()[0]);
+ delegated_impl->SetFrameDataForRenderPasses(&pass_list);
+ EXPECT_TRUE(pass_list.empty());
+
+ // Third child is the texture layer.
+ TextureLayerImpl* texture_impl =
+ static_cast<TextureLayerImpl*>(
+ host_impl->active_tree()->root_layer()->children()[2]);
+ texture_impl->set_texture_id(
+ resource_provider->GraphicsContext3D()->createTexture());
+
+ DCHECK(resource_provider->GraphicsContext3D());
+ ResourceProvider::ResourceId texture = resource_provider->CreateResource(
+ gfx::Size(4, 4),
+ resource_provider->default_resource_type(),
+ ResourceProvider::TextureUsageAny);
+ ResourceProvider::ScopedWriteLockGL lock(resource_provider, texture);
+
+ gpu::Mailbox mailbox;
+ resource_provider->GraphicsContext3D()->genMailboxCHROMIUM(mailbox.name);
+ unsigned sync_point =
+ resource_provider->GraphicsContext3D()->insertSyncPoint();
+
+ color_video_frame_ = VideoFrame::CreateColorFrame(
+ gfx::Size(4, 4), 0x80, 0x80, 0x80, base::TimeDelta());
+ hw_video_frame_ = VideoFrame::WrapNativeTexture(
+ new VideoFrame::MailboxHolder(
+ mailbox,
+ sync_point,
+ VideoFrame::MailboxHolder::TextureNoLongerNeededCallback()),
+ GL_TEXTURE_2D,
+ gfx::Size(4, 4), gfx::Rect(0, 0, 4, 4), gfx::Size(4, 4),
+ base::TimeDelta(),
+ VideoFrame::ReadPixelsCB(),
+ base::Closure());
+ scaled_hw_video_frame_ = VideoFrame::WrapNativeTexture(
+ new VideoFrame::MailboxHolder(
+ mailbox,
+ sync_point,
+ VideoFrame::MailboxHolder::TextureNoLongerNeededCallback()),
+ GL_TEXTURE_2D,
+ gfx::Size(4, 4), gfx::Rect(0, 0, 3, 2), gfx::Size(4, 4),
+ base::TimeDelta(),
+ VideoFrame::ReadPixelsCB(),
+ base::Closure());
+
+ color_frame_provider_.set_frame(color_video_frame_);
+ hw_frame_provider_.set_frame(hw_video_frame_);
+ scaled_hw_frame_provider_.set_frame(scaled_hw_video_frame_);
+ return;
+ }
+
+ if (host_impl->active_tree()->source_frame_number() == 3) {
+ // On the third commit we're recovering from context loss. Hardware
+ // video frames should not be reused by the VideoFrameProvider, but
+ // software frames can be.
+ hw_frame_provider_.set_frame(NULL);
+ scaled_hw_frame_provider_.set_frame(NULL);
+ }
+ }
+
+ virtual bool PrepareToDrawOnThread(LayerTreeHostImpl* host_impl,
+ LayerTreeHostImpl::FrameData* frame,
+ bool result) OVERRIDE {
+ if (host_impl->active_tree()->source_frame_number() == 2) {
+ // Lose the context during draw on the second commit. This will cause
+ // a third commit to recover.
+ if (context3d_)
+ context3d_->set_times_bind_texture_succeeds(4);
+ }
+ return true;
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ ASSERT_TRUE(layer_tree_host()->hud_layer());
+ // End the test once we know the 3nd frame drew.
+ if (layer_tree_host()->source_frame_number() == 4)
+ EndTest();
+ else
+ layer_tree_host()->SetNeedsCommit();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ FakeContentLayerClient client_;
+
+ scoped_refptr<Layer> root_;
+ scoped_refptr<DelegatedRendererLayer> delegated_;
+ scoped_refptr<ContentLayer> content_;
+ scoped_refptr<TextureLayer> texture_;
+ scoped_refptr<ContentLayer> mask_;
+ scoped_refptr<ContentLayer> content_with_mask_;
+ scoped_refptr<VideoLayer> video_color_;
+ scoped_refptr<VideoLayer> video_hw_;
+ scoped_refptr<VideoLayer> video_scaled_hw_;
+ scoped_refptr<IOSurfaceLayer> io_surface_;
+ scoped_refptr<ScrollbarLayer> scrollbar_;
+
+ scoped_refptr<VideoFrame> color_video_frame_;
+ scoped_refptr<VideoFrame> hw_video_frame_;
+ scoped_refptr<VideoFrame> scaled_hw_video_frame_;
+
+ FakeVideoFrameProvider color_frame_provider_;
+ FakeVideoFrameProvider hw_frame_provider_;
+ FakeVideoFrameProvider scaled_hw_frame_provider_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostContextTestDontUseLostResources);
+
+class LayerTreeHostContextTestLosesFirstOutputSurface
+ : public LayerTreeHostContextTest {
+ public:
+ LayerTreeHostContextTestLosesFirstOutputSurface() {
+ // Always fail. This needs to be set before LayerTreeHost is created.
+ times_to_lose_on_create_ = 1000;
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ virtual void DidInitializeOutputSurface(bool succeeded) OVERRIDE {
+ EXPECT_FALSE(succeeded);
+
+ // If we make it this far without crashing, we pass!
+ EndTest();
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ EXPECT_TRUE(false);
+ }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostContextTestLosesFirstOutputSurface);
+
+class LayerTreeHostContextTestRetriesFirstInitializationAndSucceeds
+ : public LayerTreeHostContextTest {
+ public:
+ virtual void AfterTest() OVERRIDE {}
+
+ virtual void BeginTest() OVERRIDE {
+ times_to_fail_initialize_ = 2;
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ EndTest();
+ }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostContextTestRetriesFirstInitializationAndSucceeds);
+
+class LayerTreeHostContextTestRetryWorksWithForcedInit
+ : public LayerTreeHostContextTestRetriesFirstInitializationAndSucceeds {
+ public:
+ virtual void DidFailToInitializeOutputSurface() OVERRIDE {
+ LayerTreeHostContextTestRetriesFirstInitializationAndSucceeds
+ ::DidFailToInitializeOutputSurface();
+
+ if (times_create_failed_ == 1) {
+ // CompositeAndReadback force recreates the output surface, which should
+ // fail.
+ char pixels[4];
+ EXPECT_FALSE(layer_tree_host()->CompositeAndReadback(
+ &pixels, gfx::Rect(1, 1)));
+ }
+ }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostContextTestRetryWorksWithForcedInit);
+
+class LayerTreeHostContextTestCompositeAndReadbackBeforeOutputSurfaceInit
+ : public LayerTreeHostContextTest {
+ public:
+ virtual void BeginTest() OVERRIDE {
+ // This must be called immediately after creating LTH, before the first
+ // OutputSurface is initialized.
+ ASSERT_TRUE(layer_tree_host()->output_surface_lost());
+
+ times_output_surface_created_ = 0;
+
+ char pixels[4];
+ bool result = layer_tree_host()->CompositeAndReadback(
+ &pixels, gfx::Rect(1, 1));
+ EXPECT_EQ(!delegating_renderer(), result);
+ EXPECT_EQ(1, times_output_surface_created_);
+
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidInitializeOutputSurface(bool succeeded) OVERRIDE {
+ EXPECT_TRUE(succeeded);
+ ++times_output_surface_created_;
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ // Should not try to create output surface again after successfully
+ // created by CompositeAndReadback.
+ EXPECT_EQ(1, times_output_surface_created_);
+ }
+
+ private:
+ int times_output_surface_created_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostContextTestCompositeAndReadbackBeforeOutputSurfaceInit);
+
+class ImplSidePaintingLayerTreeHostContextTest
+ : public LayerTreeHostContextTest {
+ public:
+ virtual void InitializeSettings(LayerTreeSettings* settings) OVERRIDE {
+ settings->impl_side_painting = true;
+ }
+};
+
+class LayerTreeHostContextTestImplSidePainting
+ : public ImplSidePaintingLayerTreeHostContextTest {
+ public:
+ virtual void SetupTree() OVERRIDE {
+ scoped_refptr<Layer> root = Layer::Create();
+ root->SetBounds(gfx::Size(10, 10));
+ root->SetAnchorPoint(gfx::PointF());
+ root->SetIsDrawable(true);
+
+ scoped_refptr<PictureLayer> picture = PictureLayer::Create(&client_);
+ picture->SetBounds(gfx::Size(10, 10));
+ picture->SetAnchorPoint(gfx::PointF());
+ picture->SetIsDrawable(true);
+ root->AddChild(picture);
+
+ layer_tree_host()->SetRootLayer(root);
+ LayerTreeHostContextTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ times_to_lose_during_commit_ = 1;
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ virtual void DidInitializeOutputSurface(bool succeeded) OVERRIDE {
+ EXPECT_TRUE(succeeded);
+ EndTest();
+ }
+
+ private:
+ FakeContentLayerClient client_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostContextTestImplSidePainting);
+
+class ScrollbarLayerLostContext : public LayerTreeHostContextTest {
+ public:
+ ScrollbarLayerLostContext() : commits_(0) {}
+
+ virtual void BeginTest() OVERRIDE {
+ scoped_refptr<Layer> scroll_layer = Layer::Create();
+ scrollbar_layer_ = FakeScrollbarLayer::Create(
+ false, true, scroll_layer->id());
+ scrollbar_layer_->SetBounds(gfx::Size(10, 100));
+ layer_tree_host()->root_layer()->AddChild(scrollbar_layer_);
+ layer_tree_host()->root_layer()->AddChild(scroll_layer);
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ LayerTreeHostContextTest::CommitCompleteOnThread(impl);
+
+ ++commits_;
+ switch (commits_) {
+ case 1:
+ // First (regular) update, we should upload 2 resources (thumb, and
+ // backtrack).
+ EXPECT_EQ(1, scrollbar_layer_->update_count());
+ LoseContext();
+ break;
+ case 2:
+ // Second update, after the lost context, we should still upload 2
+ // resources even if the contents haven't changed.
+ EXPECT_EQ(2, scrollbar_layer_->update_count());
+ EndTest();
+ break;
+ default:
+ NOTREACHED();
+ }
+ }
+
+ private:
+ int commits_;
+ scoped_refptr<FakeScrollbarLayer> scrollbar_layer_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(ScrollbarLayerLostContext);
+
+class LayerTreeHostContextTestFailsToCreateSurface
+ : public LayerTreeHostContextTest {
+ public:
+ LayerTreeHostContextTestFailsToCreateSurface()
+ : LayerTreeHostContextTest(),
+ failure_count_(0) {
+ times_to_lose_on_create_ = 10;
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ virtual void DidInitializeOutputSurface(bool success) OVERRIDE {
+ EXPECT_FALSE(success);
+ EXPECT_EQ(0, failure_count_);
+ times_to_lose_on_create_ = 0;
+ failure_count_++;
+ // Normally, the embedder should stop trying to use the compositor at
+ // this point, but let's force it back into action when we shouldn't.
+ char pixels[4];
+ EXPECT_FALSE(
+ layer_tree_host()->CompositeAndReadback(pixels, gfx::Rect(1, 1)));
+ // If we've made it this far without crashing, we've succeeded.
+ EndTest();
+ }
+
+ private:
+ int failure_count_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostContextTestFailsToCreateSurface);
+
+// Not reusing LayerTreeTest because it expects creating LTH to always succeed.
+class LayerTreeHostTestCannotCreateIfCannotCreateOutputSurface
+ : public testing::Test,
+ public FakeLayerTreeHostClient {
+ public:
+ LayerTreeHostTestCannotCreateIfCannotCreateOutputSurface()
+ : FakeLayerTreeHostClient(FakeLayerTreeHostClient::DIRECT_3D) {}
+
+ // FakeLayerTreeHostClient implementation.
+ virtual scoped_ptr<OutputSurface> CreateOutputSurface(bool fallback)
+ OVERRIDE {
+ return scoped_ptr<OutputSurface>();
+ }
+
+ void RunTest(bool threaded,
+ bool delegating_renderer,
+ bool impl_side_painting) {
+ scoped_ptr<base::Thread> impl_thread;
+ if (threaded) {
+ impl_thread.reset(new base::Thread("LayerTreeTest"));
+ ASSERT_TRUE(impl_thread->Start());
+ ASSERT_TRUE(impl_thread->message_loop_proxy().get());
+ }
+
+ LayerTreeSettings settings;
+ settings.impl_side_painting = impl_side_painting;
+ scoped_ptr<LayerTreeHost> layer_tree_host = LayerTreeHost::Create(
+ this,
+ settings,
+ impl_thread ? impl_thread->message_loop_proxy() : NULL);
+ EXPECT_FALSE(layer_tree_host);
+ }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostTestCannotCreateIfCannotCreateOutputSurface);
+
+class UIResourceLostTest : public LayerTreeHostContextTest {
+ public:
+ UIResourceLostTest() : time_step_(0) {}
+ virtual void BeginTest() OVERRIDE { PostSetNeedsCommitToMainThread(); }
+ virtual void AfterTest() OVERRIDE {}
+
+ protected:
+ int time_step_;
+ scoped_ptr<FakeScopedUIResource> ui_resource_;
+};
+
+// Losing context after an UI resource has been created.
+class UIResourceLostAfterCommit : public UIResourceLostTest {
+ public:
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ LayerTreeHostContextTest::CommitCompleteOnThread(impl);
+ switch (time_step_) {
+ case 0:
+ ui_resource_ = FakeScopedUIResource::Create(layer_tree_host());
+ // Expects a valid UIResourceId.
+ EXPECT_NE(0, ui_resource_->id());
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 1:
+ // The resource should have been created on LTHI after the commit.
+ if (!layer_tree_host()->settings().impl_side_painting)
+ EXPECT_NE(0u, impl->ResourceIdForUIResource(ui_resource_->id()));
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 2:
+ LoseContext();
+ break;
+ case 3:
+ // The resources should have been recreated. The bitmap callback should
+ // have been called once with the resource_lost flag set to true.
+ EXPECT_EQ(1, ui_resource_->lost_resource_count);
+ // Resource Id on the impl-side have been recreated as well. Note
+ // that the same UIResourceId persists after the context lost.
+ if (!layer_tree_host()->settings().impl_side_painting)
+ EXPECT_NE(0u, impl->ResourceIdForUIResource(ui_resource_->id()));
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 4:
+ // Release resource before ending test.
+ ui_resource_.reset();
+ EndTest();
+ break;
+ }
+ }
+
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ LayerTreeHostContextTest::DidActivateTreeOnThread(impl);
+ switch (time_step_) {
+ case 1:
+ if (layer_tree_host()->settings().impl_side_painting)
+ EXPECT_NE(0u, impl->ResourceIdForUIResource(ui_resource_->id()));
+ break;
+ case 3:
+ if (layer_tree_host()->settings().impl_side_painting)
+ EXPECT_NE(0u, impl->ResourceIdForUIResource(ui_resource_->id()));
+ break;
+ }
+ ++time_step_;
+ }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(UIResourceLostAfterCommit);
+
+// Losing context before UI resource requests can be commited. Three sequences
+// of creation/deletion are considered:
+// 1. Create one resource -> Context Lost => Expect the resource to have been
+// created.
+// 2. Delete an exisiting resource (test_id0_) -> create a second resource
+// (test_id1_) -> Context Lost => Expect the test_id0_ to be removed and
+// test_id1_ to have been created.
+// 3. Create one resource -> Delete that same resource -> Context Lost => Expect
+// the resource to not exist in the manager.
+class UIResourceLostBeforeCommit : public UIResourceLostTest {
+ public:
+ UIResourceLostBeforeCommit()
+ : test_id0_(0),
+ test_id1_(0) {}
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ LayerTreeHostContextTest::CommitCompleteOnThread(impl);
+ switch (time_step_) {
+ case 0:
+ // Sequence 1:
+ ui_resource_ = FakeScopedUIResource::Create(layer_tree_host());
+ LoseContext();
+ // Resource Id on the impl-side should no longer be valid after
+ // context is lost.
+ EXPECT_EQ(0u, impl->ResourceIdForUIResource(ui_resource_->id()));
+ break;
+ case 1:
+ // The resources should have been recreated.
+ EXPECT_EQ(2, ui_resource_->resource_create_count);
+ // "resource lost" callback was called once for the resource in the
+ // resource map.
+ EXPECT_EQ(1, ui_resource_->lost_resource_count);
+ // Resource Id on the impl-side have been recreated as well. Note
+ // that the same UIResourceId persists after the context lost.
+ if (!layer_tree_host()->settings().impl_side_painting)
+ EXPECT_NE(0u, impl->ResourceIdForUIResource(ui_resource_->id()));
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 2:
+ // Sequence 2:
+ // Currently one resource has been created.
+ test_id0_ = ui_resource_->id();
+ // Delete this resource.
+ ui_resource_.reset();
+ // Create another resource.
+ ui_resource_ = FakeScopedUIResource::Create(layer_tree_host());
+ test_id1_ = ui_resource_->id();
+ // Sanity check that two resource creations return different ids.
+ EXPECT_NE(test_id0_, test_id1_);
+ // Lose the context before commit.
+ LoseContext();
+ break;
+ case 3:
+ if (!layer_tree_host()->settings().impl_side_painting) {
+ // The previous resource should have been deleted.
+ EXPECT_EQ(0u, impl->ResourceIdForUIResource(test_id0_));
+ // The second resource should have been created.
+ EXPECT_NE(0u, impl->ResourceIdForUIResource(test_id1_));
+ }
+
+ // The second resource called the resource callback once and since the
+ // context is lost, a "resource lost" callback was also issued.
+ EXPECT_EQ(2, ui_resource_->resource_create_count);
+ EXPECT_EQ(1, ui_resource_->lost_resource_count);
+ // Clear the manager of resources.
+ ui_resource_.reset();
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 4:
+ // Sequence 3:
+ ui_resource_ = FakeScopedUIResource::Create(layer_tree_host());
+ test_id0_ = ui_resource_->id();
+ // Sanity check the UIResourceId should not be 0.
+ EXPECT_NE(0, test_id0_);
+ // Usually ScopedUIResource are deleted from the manager in their
+ // destructor (so usually ui_resource_.reset()). But here we need
+ // ui_resource_ for the next step, so call DeleteUIResource directly.
+ layer_tree_host()->DeleteUIResource(test_id0_);
+ LoseContext();
+ break;
+ case 5:
+ // Expect the resource callback to have been called once.
+ EXPECT_EQ(1, ui_resource_->resource_create_count);
+ // No "resource lost" callbacks.
+ EXPECT_EQ(0, ui_resource_->lost_resource_count);
+ if (!layer_tree_host()->settings().impl_side_painting) {
+ // The UI resource id should not be valid
+ EXPECT_EQ(0u, impl->ResourceIdForUIResource(test_id0_));
+ }
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 6:
+ ui_resource_.reset();
+ EndTest();
+ break;
+ }
+ }
+
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ LayerTreeHostContextTest::DidActivateTreeOnThread(impl);
+ switch (time_step_) {
+ case 1:
+ if (layer_tree_host()->settings().impl_side_painting)
+ EXPECT_NE(0u, impl->ResourceIdForUIResource(ui_resource_->id()));
+ break;
+ case 3:
+ if (layer_tree_host()->settings().impl_side_painting) {
+ EXPECT_EQ(0u, impl->ResourceIdForUIResource(test_id0_));
+ EXPECT_NE(0u, impl->ResourceIdForUIResource(test_id1_));
+ }
+ break;
+ case 5:
+ if (layer_tree_host()->settings().impl_side_painting)
+ EXPECT_EQ(0u, impl->ResourceIdForUIResource(test_id0_));
+ break;
+ }
+ ++time_step_;
+ }
+
+ private:
+ UIResourceId test_id0_;
+ UIResourceId test_id1_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(UIResourceLostBeforeCommit);
+
+// Losing UI resource before the pending trees is activated but after the
+// commit. Impl-side-painting only.
+class UIResourceLostBeforeActivateTree : public UIResourceLostTest {
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ LayerTreeHostContextTest::CommitCompleteOnThread(impl);
+ switch (time_step_) {
+ case 0:
+ ui_resource_ = FakeScopedUIResource::Create(layer_tree_host());
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 2:
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 3:
+ test_id_ = ui_resource_->id();
+ ui_resource_.reset();
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 4:
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 5:
+ EndTest();
+ break;
+ }
+ }
+
+ virtual void WillActivateTreeOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ switch (time_step_) {
+ case 0:
+ break;
+ case 1:
+ // The resource creation callback has been called.
+ EXPECT_EQ(1, ui_resource_->resource_create_count);
+ // The resource is not yet lost (sanity check).
+ EXPECT_EQ(0, ui_resource_->lost_resource_count);
+ // The resource should not have been created yet on the impl-side.
+ EXPECT_EQ(0u, impl->ResourceIdForUIResource(ui_resource_->id()));
+ LoseContext();
+ break;
+ case 3:
+ LoseContext();
+ break;
+ }
+ }
+
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ LayerTreeHostContextTest::DidActivateTreeOnThread(impl);
+ switch (time_step_) {
+ case 1:
+ // The pending requests on the impl-side should have been processed.
+ EXPECT_NE(0u, impl->ResourceIdForUIResource(ui_resource_->id()));
+ break;
+ case 2:
+ // The "lost resource" callback should have been called once.
+ EXPECT_EQ(1, ui_resource_->lost_resource_count);
+ break;
+ case 4:
+ // The resource is deleted and should not be in the manager. Use
+ // test_id_ since ui_resource_ has been deleted.
+ EXPECT_EQ(0u, impl->ResourceIdForUIResource(test_id_));
+ break;
+ }
+ ++time_step_;
+ }
+
+ private:
+ UIResourceId test_id_;
+};
+
+TEST_F(UIResourceLostBeforeActivateTree,
+ RunMultiThread_DirectRenderer_ImplSidePaint) {
+ RunTest(true, false, true);
+}
+
+TEST_F(UIResourceLostBeforeActivateTree,
+ RunMultiThread_DelegatingRenderer_ImplSidePaint) {
+ RunTest(true, true, true);
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/trees/layer_tree_host_unittest_damage.cc b/chromium/cc/trees/layer_tree_host_unittest_damage.cc
new file mode 100644
index 00000000000..805c9275c14
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_host_unittest_damage.cc
@@ -0,0 +1,391 @@
+// 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 "cc/trees/layer_tree_host.h"
+
+#include "cc/test/fake_content_layer.h"
+#include "cc/test/fake_content_layer_client.h"
+#include "cc/test/fake_scrollbar_layer.h"
+#include "cc/test/layer_tree_test.h"
+#include "cc/trees/damage_tracker.h"
+#include "cc/trees/layer_tree_impl.h"
+
+namespace cc {
+namespace {
+
+// These tests deal with damage tracking.
+class LayerTreeHostDamageTest : public LayerTreeTest {};
+
+class LayerTreeHostDamageTestNoDamageDoesNotSwap
+ : public LayerTreeHostDamageTest {
+ virtual void BeginTest() OVERRIDE {
+ expect_swap_and_succeed_ = 0;
+ did_swaps_ = 0;
+ did_swap_and_succeed_ = 0;
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void SetupTree() OVERRIDE {
+ scoped_refptr<FakeContentLayer> root = FakeContentLayer::Create(&client_);
+ root->SetBounds(gfx::Size(10, 10));
+
+ // Most of the layer isn't visible.
+ content_ = FakeContentLayer::Create(&client_);
+ content_->SetBounds(gfx::Size(2000, 100));
+ root->AddChild(content_);
+
+ layer_tree_host()->SetRootLayer(root);
+ LayerTreeHostDamageTest::SetupTree();
+ }
+
+ virtual bool PrepareToDrawOnThread(LayerTreeHostImpl* host_impl,
+ LayerTreeHostImpl::FrameData* frame_data,
+ bool result) OVERRIDE {
+ EXPECT_TRUE(result);
+
+ int source_frame = host_impl->active_tree()->source_frame_number();
+ switch (source_frame) {
+ case 0:
+ // The first frame has damage, so we should draw and swap.
+ ++expect_swap_and_succeed_;
+ break;
+ case 1:
+ // The second frame has no damage, so we should not draw and swap.
+ break;
+ case 2:
+ // The third frame has damage again, so we should draw and swap.
+ ++expect_swap_and_succeed_;
+ break;
+ case 3:
+ // The fourth frame has no visible damage, so we should not draw and
+ // swap.
+ EndTest();
+ break;
+ }
+ return result;
+ }
+
+ virtual void SwapBuffersOnThread(LayerTreeHostImpl* host_impl,
+ bool result) OVERRIDE {
+ ++did_swaps_;
+ if (result)
+ ++did_swap_and_succeed_;
+ EXPECT_EQ(expect_swap_and_succeed_, did_swap_and_succeed_);
+ }
+
+ virtual void DidCommit() OVERRIDE {
+ int next_frame = layer_tree_host()->source_frame_number();
+ switch (next_frame) {
+ case 1:
+ layer_tree_host()->SetNeedsCommit();
+ break;
+ case 2:
+ // Cause visible damage.
+ content_->SetNeedsDisplayRect(
+ gfx::Rect(layer_tree_host()->device_viewport_size()));
+ break;
+ case 3:
+ // Cause non-visible damage.
+ content_->SetNeedsDisplayRect(gfx::Rect(1990, 1990, 10, 10));
+ layer_tree_host()->SetNeedsCommit();
+ break;
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ EXPECT_EQ(4, did_swaps_);
+ EXPECT_EQ(2, expect_swap_and_succeed_);
+ EXPECT_EQ(expect_swap_and_succeed_, did_swap_and_succeed_);
+ }
+
+ FakeContentLayerClient client_;
+ scoped_refptr<FakeContentLayer> content_;
+ int expect_swap_and_succeed_;
+ int did_swaps_;
+ int did_swap_and_succeed_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostDamageTestNoDamageDoesNotSwap);
+
+class LayerTreeHostDamageTestNoDamageReadbackDoesDraw
+ : public LayerTreeHostDamageTest {
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void SetupTree() OVERRIDE {
+ scoped_refptr<FakeContentLayer> root = FakeContentLayer::Create(&client_);
+ root->SetBounds(gfx::Size(10, 10));
+
+ // Most of the layer isn't visible.
+ content_ = FakeContentLayer::Create(&client_);
+ content_->SetBounds(gfx::Size(100, 100));
+ root->AddChild(content_);
+
+ layer_tree_host()->SetRootLayer(root);
+ LayerTreeHostDamageTest::SetupTree();
+ }
+
+ virtual bool PrepareToDrawOnThread(LayerTreeHostImpl* host_impl,
+ LayerTreeHostImpl::FrameData* frame_data,
+ bool result) OVERRIDE {
+ EXPECT_TRUE(result);
+
+ int source_frame = host_impl->active_tree()->source_frame_number();
+ switch (source_frame) {
+ case 0:
+ // The first frame draws and clears any damage.
+ break;
+ case 1: {
+ // The second frame is a readback, we should have damage in the readback
+ // rect, but not swap.
+ RenderSurfaceImpl* root_surface =
+ host_impl->active_tree()->root_layer()->render_surface();
+ gfx::RectF root_damage =
+ root_surface->damage_tracker()->current_damage_rect();
+ root_damage.Intersect(root_surface->content_rect());
+ EXPECT_TRUE(root_damage.Contains(gfx::Rect(3, 3, 1, 1)));
+ break;
+ }
+ case 2:
+ // CompositeAndReadback causes a follow-up commit.
+ break;
+ case 3:
+ NOTREACHED();
+ break;
+ }
+ return result;
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ int next_frame = layer_tree_host()->source_frame_number();
+ switch (next_frame) {
+ case 1: {
+ char pixels[4];
+ layer_tree_host()->CompositeAndReadback(static_cast<void*>(&pixels),
+ gfx::Rect(3, 3, 1, 1));
+ EndTest();
+ break;
+ }
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ FakeContentLayerClient client_;
+ scoped_refptr<FakeContentLayer> content_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostDamageTestNoDamageReadbackDoesDraw);
+
+class LayerTreeHostDamageTestForcedFullDamage : public LayerTreeHostDamageTest {
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void SetupTree() OVERRIDE {
+ root_ = FakeContentLayer::Create(&client_);
+ child_ = FakeContentLayer::Create(&client_);
+
+ root_->SetBounds(gfx::Size(500, 500));
+ child_->SetPosition(gfx::Point(100, 100));
+ child_->SetBounds(gfx::Size(30, 30));
+
+ root_->AddChild(child_);
+ layer_tree_host()->SetRootLayer(root_);
+ LayerTreeHostDamageTest::SetupTree();
+ }
+
+ virtual bool PrepareToDrawOnThread(LayerTreeHostImpl* host_impl,
+ LayerTreeHostImpl::FrameData* frame_data,
+ bool result) OVERRIDE {
+ EXPECT_TRUE(result);
+
+ RenderSurfaceImpl* root_surface =
+ host_impl->active_tree()->root_layer()->render_surface();
+ gfx::RectF root_damage =
+ root_surface->damage_tracker()->current_damage_rect();
+ root_damage.Intersect(root_surface->content_rect());
+
+ int source_frame = host_impl->active_tree()->source_frame_number();
+ switch (source_frame) {
+ case 0:
+ // The first frame draws and clears any damage.
+ EXPECT_EQ(gfx::RectF(root_surface->content_rect()).ToString(),
+ root_damage.ToString());
+ EXPECT_FALSE(frame_data->has_no_damage);
+ break;
+ case 1:
+ // If we get a frame without damage then we don't draw.
+ EXPECT_EQ(gfx::RectF().ToString(), root_damage.ToString());
+ EXPECT_TRUE(frame_data->has_no_damage);
+
+ // Then we set full damage for the next frame.
+ host_impl->SetFullRootLayerDamage();
+ break;
+ case 2:
+ // The whole frame should be damaged as requested.
+ EXPECT_EQ(gfx::RectF(root_surface->content_rect()).ToString(),
+ root_damage.ToString());
+ EXPECT_FALSE(frame_data->has_no_damage);
+
+ // Just a part of the next frame should be damaged.
+ child_damage_rect_ = gfx::RectF(10, 11, 12, 13);
+ break;
+ case 3:
+ if (!delegating_renderer() &&
+ !host_impl->settings().impl_side_painting) {
+ // The update rect in the child should be damaged.
+ // TODO(danakj): Remove this when impl side painting is always on.
+ EXPECT_EQ(gfx::RectF(100+10, 100+11, 12, 13).ToString(),
+ root_damage.ToString());
+ } else {
+ // When using a delegating renderer, or using impl side painting, the
+ // entire child is considered damaged as we need to replace its
+ // resources with newly created ones.
+ EXPECT_EQ(gfx::RectF(child_->position(), child_->bounds()).ToString(),
+ root_damage.ToString());
+ }
+ EXPECT_FALSE(frame_data->has_no_damage);
+
+ // If we damage part of the frame, but also damage the full
+ // frame, then the whole frame should be damaged.
+ child_damage_rect_ = gfx::RectF(10, 11, 12, 13);
+ host_impl->SetFullRootLayerDamage();
+ break;
+ case 4:
+ // The whole frame is damaged.
+ EXPECT_EQ(gfx::RectF(root_surface->content_rect()).ToString(),
+ root_damage.ToString());
+ EXPECT_FALSE(frame_data->has_no_damage);
+
+ EndTest();
+ break;
+ }
+ return result;
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ if (!TestEnded())
+ layer_tree_host()->SetNeedsCommit();
+
+ if (!child_damage_rect_.IsEmpty()) {
+ child_->SetNeedsDisplayRect(child_damage_rect_);
+ child_damage_rect_ = gfx::RectF();
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ FakeContentLayerClient client_;
+ scoped_refptr<FakeContentLayer> root_;
+ scoped_refptr<FakeContentLayer> child_;
+ gfx::RectF child_damage_rect_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostDamageTestForcedFullDamage);
+
+class LayerTreeHostDamageTestScrollbarDoesDamage
+ : public LayerTreeHostDamageTest {
+ virtual void BeginTest() OVERRIDE {
+ did_swaps_ = 0;
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void SetupTree() OVERRIDE {
+ scoped_refptr<Layer> root_layer = Layer::Create();
+ root_layer->SetBounds(gfx::Size(400,400));
+ layer_tree_host()->SetRootLayer(root_layer);
+
+ scoped_refptr<Layer> content_layer = FakeContentLayer::Create(&client_);
+ content_layer->SetScrollable(true);
+ content_layer->SetScrollOffset(gfx::Vector2d(10, 20));
+ content_layer->SetMaxScrollOffset(gfx::Vector2d(30, 50));
+ content_layer->SetBounds(gfx::Size(100, 200));
+ root_layer->AddChild(content_layer);
+
+ scoped_refptr<Layer> scrollbar_layer =
+ FakeScrollbarLayer::Create(false, true, content_layer->id());
+ scrollbar_layer->SetPosition(gfx::Point(300, 300));
+ scrollbar_layer->SetBounds(gfx::Size(10, 100));
+ root_layer->AddChild(scrollbar_layer);
+
+ gfx::RectF content_rect(content_layer->position(),
+ content_layer->bounds());
+ gfx::RectF scrollbar_rect(scrollbar_layer->position(),
+ scrollbar_layer->bounds());
+ EXPECT_FALSE(content_rect.Intersects(scrollbar_rect));
+
+ LayerTreeHostDamageTest::SetupTree();
+ }
+
+ virtual bool PrepareToDrawOnThread(LayerTreeHostImpl* host_impl,
+ LayerTreeHostImpl::FrameData* frame_data,
+ bool result) OVERRIDE {
+ EXPECT_TRUE(result);
+ RenderSurfaceImpl* root_surface =
+ host_impl->active_tree()->root_layer()->render_surface();
+ gfx::RectF root_damage =
+ root_surface->damage_tracker()->current_damage_rect();
+ root_damage.Intersect(root_surface->content_rect());
+ switch (did_swaps_) {
+ case 0:
+ // The first frame has damage, so we should draw and swap.
+ break;
+ case 1:
+ // The second frame should damage the scrollbars.
+ EXPECT_TRUE(root_damage.Contains(gfx::Rect(300, 300, 10, 100)));
+ break;
+ case 2:
+ // The third frame should damage the scrollbars.
+ EXPECT_TRUE(root_damage.Contains(gfx::Rect(300, 300, 10, 100)));
+ break;
+ case 3:
+ // The fourth frame should not damage the scrollbars.
+ EXPECT_FALSE(root_damage.Intersects(gfx::Rect(300, 300, 10, 100)));
+ EndTest();
+ break;
+ }
+ return result;
+ }
+
+ virtual void SwapBuffersOnThread(LayerTreeHostImpl* host_impl,
+ bool result) OVERRIDE {
+ ++did_swaps_;
+ EXPECT_TRUE(result);
+ LayerImpl* root = host_impl->active_tree()->root_layer();
+ LayerImpl* scroll_layer = root->children()[0];
+ switch (did_swaps_) {
+ case 1:
+ host_impl->ScrollBegin(gfx::Point(1,1), InputHandler::Wheel);
+ EXPECT_TRUE(host_impl->ScrollBy(gfx::Point(),
+ gfx::Vector2dF(10.0, 10.0)));
+ break;
+ case 2:
+ scroll_layer->SetMaxScrollOffset(gfx::Vector2d(60, 100));
+ host_impl->SetNeedsRedraw();
+ break;
+ case 3:
+ // Test that modifying the position of the content layer (not
+ // scrolling) won't damage the scrollbar.
+ scroll_layer->SetPosition(gfx::Point(1,1));
+ scroll_layer->SetScrollOffset(scroll_layer->scroll_offset());
+ host_impl->SetNeedsRedraw();
+ break;
+ }
+
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ EXPECT_EQ(4, did_swaps_);
+ }
+
+ FakeContentLayerClient client_;
+ int did_swaps_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostDamageTestScrollbarDoesDamage);
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/trees/layer_tree_host_unittest_delegated.cc b/chromium/cc/trees/layer_tree_host_unittest_delegated.cc
new file mode 100644
index 00000000000..d48931328af
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_host_unittest_delegated.cc
@@ -0,0 +1,1475 @@
+// Copyright 2013 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 "cc/trees/layer_tree_host.h"
+
+#include "base/bind.h"
+#include "cc/layers/delegated_renderer_layer.h"
+#include "cc/layers/delegated_renderer_layer_client.h"
+#include "cc/layers/delegated_renderer_layer_impl.h"
+#include "cc/output/compositor_frame.h"
+#include "cc/output/compositor_frame_ack.h"
+#include "cc/output/delegated_frame_data.h"
+#include "cc/quads/shared_quad_state.h"
+#include "cc/quads/texture_draw_quad.h"
+#include "cc/test/fake_delegated_renderer_layer.h"
+#include "cc/test/fake_delegated_renderer_layer_impl.h"
+#include "cc/test/fake_output_surface.h"
+#include "cc/test/layer_tree_test.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "gpu/GLES2/gl2extchromium.h"
+
+namespace cc {
+namespace {
+
+// These tests deal with delegated renderer layers.
+class LayerTreeHostDelegatedTest : public LayerTreeTest {
+ protected:
+ scoped_ptr<DelegatedFrameData> CreateFrameData(gfx::Rect root_output_rect,
+ gfx::Rect root_damage_rect) {
+ scoped_ptr<DelegatedFrameData> frame(new DelegatedFrameData);
+
+ scoped_ptr<RenderPass> root_pass(RenderPass::Create());
+ root_pass->SetNew(RenderPass::Id(1, 1),
+ root_output_rect,
+ root_damage_rect,
+ gfx::Transform());
+ frame->render_pass_list.push_back(root_pass.Pass());
+ return frame.Pass();
+ }
+
+ scoped_ptr<DelegatedFrameData> CreateInvalidFrameData(
+ gfx::Rect root_output_rect,
+ gfx::Rect root_damage_rect) {
+ scoped_ptr<DelegatedFrameData> frame(new DelegatedFrameData);
+
+ scoped_ptr<RenderPass> root_pass(RenderPass::Create());
+ root_pass->SetNew(RenderPass::Id(1, 1),
+ root_output_rect,
+ root_damage_rect,
+ gfx::Transform());
+
+ scoped_ptr<SharedQuadState> shared_quad_state = SharedQuadState::Create();
+
+ gfx::Rect rect = root_output_rect;
+ gfx::Rect opaque_rect = root_output_rect;
+ // An invalid resource id! The resource isn't part of the frame.
+ unsigned resource_id = 5;
+ bool premultiplied_alpha = false;
+ gfx::PointF uv_top_left = gfx::PointF(0.f, 0.f);
+ gfx::PointF uv_bottom_right = gfx::PointF(1.f, 1.f);
+ SkColor background_color = 0;
+ float vertex_opacity[4] = {1.f, 1.f, 1.f, 1.f};
+ bool flipped = false;
+
+ scoped_ptr<TextureDrawQuad> invalid_draw_quad = TextureDrawQuad::Create();
+ invalid_draw_quad->SetNew(shared_quad_state.get(),
+ rect,
+ opaque_rect,
+ resource_id,
+ premultiplied_alpha,
+ uv_top_left,
+ uv_bottom_right,
+ background_color,
+ vertex_opacity,
+ flipped);
+ root_pass->quad_list.push_back(invalid_draw_quad.PassAs<DrawQuad>());
+
+ root_pass->shared_quad_state_list.push_back(shared_quad_state.Pass());
+
+ frame->render_pass_list.push_back(root_pass.Pass());
+ return frame.Pass();
+ }
+
+ void AddTransferableResource(DelegatedFrameData* frame,
+ ResourceProvider::ResourceId resource_id) {
+ TransferableResource resource;
+ resource.id = resource_id;
+ frame->resource_list.push_back(resource);
+ }
+
+ void AddTextureQuad(DelegatedFrameData* frame,
+ ResourceProvider::ResourceId resource_id) {
+ scoped_ptr<SharedQuadState> sqs = SharedQuadState::Create();
+ scoped_ptr<TextureDrawQuad> quad = TextureDrawQuad::Create();
+ float vertex_opacity[4] = { 1.f, 1.f, 1.f, 1.f };
+ quad->SetNew(sqs.get(),
+ gfx::Rect(0, 0, 10, 10),
+ gfx::Rect(0, 0, 10, 10),
+ resource_id,
+ false,
+ gfx::PointF(0.f, 0.f),
+ gfx::PointF(1.f, 1.f),
+ SK_ColorTRANSPARENT,
+ vertex_opacity,
+ false);
+ frame->render_pass_list[0]->shared_quad_state_list.push_back(sqs.Pass());
+ frame->render_pass_list[0]->quad_list.push_back(quad.PassAs<DrawQuad>());
+ }
+
+ scoped_ptr<DelegatedFrameData> CreateEmptyFrameData() {
+ scoped_ptr<DelegatedFrameData> frame(new DelegatedFrameData);
+ return frame.Pass();
+ }
+
+
+ static ResourceProvider::ResourceId AppendResourceId(
+ std::vector<ResourceProvider::ResourceId>* resources_in_last_sent_frame,
+ ResourceProvider::ResourceId resource_id) {
+ resources_in_last_sent_frame->push_back(resource_id);
+ return resource_id;
+ }
+
+ void ReturnUnusedResourcesFromParent(LayerTreeHostImpl* host_impl) {
+ DelegatedFrameData* delegated_frame_data =
+ output_surface()->last_sent_frame().delegated_frame_data.get();
+ if (!delegated_frame_data)
+ return;
+
+ std::vector<ResourceProvider::ResourceId> resources_in_last_sent_frame;
+ for (size_t i = 0; i < delegated_frame_data->render_pass_list.size(); ++i) {
+ RenderPass* pass = delegated_frame_data->render_pass_list.at(i);
+ for (size_t j = 0; j < pass->quad_list.size(); ++j) {
+ DrawQuad* quad = pass->quad_list[j];
+ quad->IterateResources(base::Bind(&AppendResourceId,
+ &resources_in_last_sent_frame));
+ }
+ }
+
+ std::vector<ResourceProvider::ResourceId> resources_to_return;
+
+ const TransferableResourceArray& resources_held_by_parent =
+ output_surface()->resources_held_by_parent();
+ for (size_t i = 0; i < resources_held_by_parent.size(); ++i) {
+ ResourceProvider::ResourceId resource_in_parent =
+ resources_held_by_parent[i].id;
+ bool resource_in_parent_is_not_part_of_frame =
+ std::find(resources_in_last_sent_frame.begin(),
+ resources_in_last_sent_frame.end(),
+ resource_in_parent) == resources_in_last_sent_frame.end();
+ if (resource_in_parent_is_not_part_of_frame)
+ resources_to_return.push_back(resource_in_parent);
+ }
+
+ if (resources_to_return.empty())
+ return;
+
+ CompositorFrameAck ack;
+ for (size_t i = 0; i < resources_to_return.size(); ++i)
+ output_surface()->ReturnResource(resources_to_return[i], &ack);
+ host_impl->OnSwapBuffersComplete(&ack);
+ }
+};
+
+class LayerTreeHostDelegatedTestCaseSingleDelegatedLayer
+ : public LayerTreeHostDelegatedTest,
+ public DelegatedRendererLayerClient {
+ public:
+ virtual void SetupTree() OVERRIDE {
+ root_ = Layer::Create();
+ root_->SetAnchorPoint(gfx::PointF());
+ root_->SetBounds(gfx::Size(10, 10));
+
+ delegated_ = FakeDelegatedRendererLayer::Create(this);
+ delegated_->SetAnchorPoint(gfx::PointF());
+ delegated_->SetBounds(gfx::Size(10, 10));
+ delegated_->SetIsDrawable(true);
+
+ root_->AddChild(delegated_);
+ layer_tree_host()->SetRootLayer(root_);
+ LayerTreeHostDelegatedTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ virtual void DidCommitFrameData() OVERRIDE {}
+
+ protected:
+ scoped_refptr<Layer> root_;
+ scoped_refptr<DelegatedRendererLayer> delegated_;
+};
+
+class LayerTreeHostDelegatedTestClientDidCommitCallback
+ : public LayerTreeHostDelegatedTestCaseSingleDelegatedLayer {
+ public:
+ LayerTreeHostDelegatedTestClientDidCommitCallback()
+ : LayerTreeHostDelegatedTestCaseSingleDelegatedLayer(),
+ num_did_commit_frame_data_(0) {}
+
+ virtual void DidCommit() OVERRIDE {
+ if (TestEnded())
+ return;
+
+ EXPECT_EQ(1, num_did_commit_frame_data_);
+ EndTest();
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ delegated_->SetFrameData(CreateFrameData(gfx::Rect(0, 0, 1, 1),
+ gfx::Rect(0, 0, 1, 1)));
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommitFrameData() OVERRIDE {
+ num_did_commit_frame_data_++;
+ }
+
+ protected:
+ int num_did_commit_frame_data_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostDelegatedTestClientDidCommitCallback);
+
+class LayerTreeHostDelegatedTestCreateChildId
+ : public LayerTreeHostDelegatedTestCaseSingleDelegatedLayer {
+ public:
+ LayerTreeHostDelegatedTestCreateChildId()
+ : LayerTreeHostDelegatedTestCaseSingleDelegatedLayer(),
+ num_activates_(0),
+ did_reset_child_id_(false) {}
+
+ virtual void DidCommit() OVERRIDE {
+ if (TestEnded())
+ return;
+ delegated_->SetFrameData(CreateFrameData(gfx::Rect(0, 0, 1, 1),
+ gfx::Rect(0, 0, 1, 1)));
+ }
+
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ LayerImpl* root_impl = host_impl->active_tree()->root_layer();
+ FakeDelegatedRendererLayerImpl* delegated_impl =
+ static_cast<FakeDelegatedRendererLayerImpl*>(root_impl->children()[0]);
+
+ WebKit::WebGraphicsContext3D* context =
+ host_impl->resource_provider()->GraphicsContext3D();
+
+ ++num_activates_;
+ switch (num_activates_) {
+ case 2:
+ EXPECT_TRUE(delegated_impl->ChildId());
+ EXPECT_FALSE(did_reset_child_id_);
+
+ context->loseContextCHROMIUM(GL_GUILTY_CONTEXT_RESET_ARB,
+ GL_INNOCENT_CONTEXT_RESET_ARB);
+ break;
+ case 3:
+ EXPECT_TRUE(delegated_impl->ChildId());
+ EXPECT_TRUE(did_reset_child_id_);
+ EndTest();
+ break;
+ }
+ }
+
+ virtual void InitializedRendererOnThread(LayerTreeHostImpl* host_impl,
+ bool success) OVERRIDE {
+ EXPECT_TRUE(success);
+
+ if (num_activates_ < 2)
+ return;
+
+ LayerImpl* root_impl = host_impl->active_tree()->root_layer();
+ FakeDelegatedRendererLayerImpl* delegated_impl =
+ static_cast<FakeDelegatedRendererLayerImpl*>(root_impl->children()[0]);
+
+ EXPECT_EQ(2, num_activates_);
+ EXPECT_FALSE(delegated_impl->ChildId());
+ did_reset_child_id_ = true;
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ protected:
+ int num_activates_;
+ bool did_reset_child_id_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostDelegatedTestCreateChildId);
+
+class LayerTreeHostDelegatedTestLayerUsesFrameDamage
+ : public LayerTreeHostDelegatedTestCaseSingleDelegatedLayer {
+ public:
+ LayerTreeHostDelegatedTestLayerUsesFrameDamage()
+ : LayerTreeHostDelegatedTestCaseSingleDelegatedLayer(),
+ first_draw_for_source_frame_(true) {}
+
+ virtual void DidCommit() OVERRIDE {
+ int next_source_frame_number = layer_tree_host()->source_frame_number();
+ switch (next_source_frame_number) {
+ case 1:
+ // The first time the layer gets a frame the whole layer should be
+ // damaged.
+ delegated_->SetFrameData(CreateFrameData(gfx::Rect(0, 0, 1, 1),
+ gfx::Rect(0, 0, 1, 1)));
+ break;
+ case 2:
+ // Should create a total amount of gfx::Rect(2, 2, 10, 6) damage.
+ // The frame size is 20x20 while the layer is 10x10, so this should
+ // produce a gfx::Rect(1, 1, 5, 3) damage rect.
+ delegated_->SetFrameData(CreateFrameData(gfx::Rect(0, 0, 20, 20),
+ gfx::Rect(2, 2, 5, 5)));
+ delegated_->SetFrameData(CreateFrameData(gfx::Rect(0, 0, 20, 20),
+ gfx::Rect(7, 2, 5, 6)));
+ break;
+ case 3:
+ // Should create zero damage.
+ layer_tree_host()->SetNeedsCommit();
+ break;
+ case 4:
+ // Should damage the full viewport.
+ delegated_->SetBounds(gfx::Size(2, 2));
+ break;
+ case 5:
+ // Should create zero damage.
+ layer_tree_host()->SetNeedsCommit();
+ break;
+ case 6:
+ // Should damage the full layer.
+ delegated_->SetBounds(gfx::Size(6, 6));
+ delegated_->SetFrameData(CreateFrameData(gfx::Rect(0, 0, 5, 5),
+ gfx::Rect(1, 1, 2, 2)));
+ break;
+ case 7:
+ // Should create zero damage.
+ layer_tree_host()->SetNeedsCommit();
+ break;
+ case 8:
+ // Should damage the full layer.
+ delegated_->SetDisplaySize(gfx::Size(10, 10));
+ break;
+ case 9:
+ // Should create zero damage.
+ layer_tree_host()->SetNeedsCommit();
+ break;
+ case 10:
+ // Setting an empty frame should damage the whole layer the
+ // first time.
+ delegated_->SetFrameData(CreateEmptyFrameData());
+ break;
+ case 11:
+ // Setting an empty frame shouldn't damage anything after the
+ // first time.
+ delegated_->SetFrameData(CreateEmptyFrameData());
+ break;
+ case 12:
+ // Having valid content to display agains should damage the whole layer.
+ delegated_->SetFrameData(CreateFrameData(gfx::Rect(0, 0, 10, 10),
+ gfx::Rect(5, 5, 1, 1)));
+ break;
+ case 13:
+ // An invalid frame isn't used, so it should not cause damage.
+ delegated_->SetFrameData(CreateInvalidFrameData(gfx::Rect(0, 0, 10, 10),
+ gfx::Rect(5, 5, 1, 1)));
+ break;
+ case 14:
+ // Should create gfx::Rect(1, 1, 2, 2) of damage. The frame size is
+ // 5x5 and the display size is now set to 10x10, so this should result
+ // in a gfx::Rect(2, 2, 4, 4) damage rect.
+ delegated_->SetFrameData(CreateFrameData(gfx::Rect(0, 0, 5, 5),
+ gfx::Rect(1, 1, 2, 2)));
+ break;
+ case 15:
+ // Should create zero damage.
+ layer_tree_host()->SetNeedsCommit();
+ break;
+ }
+ first_draw_for_source_frame_ = true;
+ }
+
+ virtual bool PrepareToDrawOnThread(LayerTreeHostImpl* host_impl,
+ LayerTreeHostImpl::FrameData* frame,
+ bool result) OVERRIDE {
+ EXPECT_TRUE(result);
+
+ if (!first_draw_for_source_frame_)
+ return result;
+
+ gfx::RectF damage_rect;
+ if (!frame->has_no_damage) {
+ damage_rect = frame->render_passes.back()->damage_rect;
+ } else {
+ // If there is no damage, then we have no render passes to send.
+ EXPECT_TRUE(frame->render_passes.empty());
+ }
+
+ switch (host_impl->active_tree()->source_frame_number()) {
+ case 0:
+ // First frame is damaged because of viewport resize.
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 10.f, 10.f).ToString(),
+ damage_rect.ToString());
+ break;
+ case 1:
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 10.f, 10.f).ToString(),
+ damage_rect.ToString());
+ break;
+ case 2:
+ EXPECT_EQ(gfx::RectF(1.f, 1.f, 5.f, 3.f).ToString(),
+ damage_rect.ToString());
+ break;
+ case 3:
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 0.f, 0.f).ToString(),
+ damage_rect.ToString());
+ break;
+ case 4:
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 10.f, 10.f).ToString(),
+ damage_rect.ToString());
+ break;
+ case 5:
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 0.f, 0.f).ToString(),
+ damage_rect.ToString());
+ break;
+ case 6:
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 6.f, 6.f).ToString(),
+ damage_rect.ToString());
+ break;
+ case 7:
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 0.f, 0.f).ToString(),
+ damage_rect.ToString());
+ break;
+ case 8:
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 6.f, 6.f).ToString(),
+ damage_rect.ToString());
+ break;
+ case 9:
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 0.f, 0.f).ToString(),
+ damage_rect.ToString());
+ break;
+ case 10:
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 6.f, 6.f).ToString(),
+ damage_rect.ToString());
+ break;
+ case 11:
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 0.f, 0.f).ToString(),
+ damage_rect.ToString());
+ break;
+ case 12:
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 6.f, 6.f).ToString(),
+ damage_rect.ToString());
+ break;
+ case 13:
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 0.f, 0.f).ToString(),
+ damage_rect.ToString());
+ break;
+ case 14:
+ EXPECT_EQ(gfx::RectF(2.f, 2.f, 4.f, 4.f).ToString(),
+ damage_rect.ToString());
+ break;
+ case 15:
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 0.f, 0.f).ToString(),
+ damage_rect.ToString());
+ EndTest();
+ break;
+ }
+
+ return result;
+ }
+
+ protected:
+ bool first_draw_for_source_frame_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostDelegatedTestLayerUsesFrameDamage);
+
+class LayerTreeHostDelegatedTestMergeResources
+ : public LayerTreeHostDelegatedTestCaseSingleDelegatedLayer {
+ public:
+ virtual void BeginTest() OVERRIDE {
+ // Push two frames to the delegated renderer layer with no commit between.
+
+ // The first frame has resource 999.
+ scoped_ptr<DelegatedFrameData> frame1 =
+ CreateFrameData(gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1));
+ AddTextureQuad(frame1.get(), 999);
+ AddTransferableResource(frame1.get(), 999);
+ delegated_->SetFrameData(frame1.Pass());
+
+ // The second frame uses resource 999 still, but also adds 555.
+ scoped_ptr<DelegatedFrameData> frame2 =
+ CreateFrameData(gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1));
+ AddTextureQuad(frame2.get(), 999);
+ AddTextureQuad(frame2.get(), 555);
+ AddTransferableResource(frame2.get(), 555);
+ delegated_->SetFrameData(frame2.Pass());
+
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ LayerImpl* root_impl = host_impl->active_tree()->root_layer();
+ FakeDelegatedRendererLayerImpl* delegated_impl =
+ static_cast<FakeDelegatedRendererLayerImpl*>(root_impl->children()[0]);
+
+ const ResourceProvider::ResourceIdMap& map =
+ host_impl->resource_provider()->GetChildToParentMap(
+ delegated_impl->ChildId());
+
+ // Both frames' resources should be in the parent's resource provider.
+ EXPECT_EQ(2u, map.size());
+ EXPECT_EQ(1u, map.count(999));
+ EXPECT_EQ(1u, map.count(555));
+
+ EXPECT_EQ(2u, delegated_impl->Resources().size());
+ EXPECT_EQ(1u, delegated_impl->Resources().count(map.find(999)->second));
+ EXPECT_EQ(1u, delegated_impl->Resources().count(map.find(555)->second));
+
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostDelegatedTestMergeResources);
+
+class LayerTreeHostDelegatedTestRemapResourcesInQuads
+ : public LayerTreeHostDelegatedTestCaseSingleDelegatedLayer {
+ public:
+ virtual void BeginTest() OVERRIDE {
+ // Generate a frame with two resources in it.
+ scoped_ptr<DelegatedFrameData> frame =
+ CreateFrameData(gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1));
+ AddTextureQuad(frame.get(), 999);
+ AddTransferableResource(frame.get(), 999);
+ AddTextureQuad(frame.get(), 555);
+ AddTransferableResource(frame.get(), 555);
+ delegated_->SetFrameData(frame.Pass());
+
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ LayerImpl* root_impl = host_impl->active_tree()->root_layer();
+ FakeDelegatedRendererLayerImpl* delegated_impl =
+ static_cast<FakeDelegatedRendererLayerImpl*>(root_impl->children()[0]);
+
+ const ResourceProvider::ResourceIdMap& map =
+ host_impl->resource_provider()->GetChildToParentMap(
+ delegated_impl->ChildId());
+
+ // The frame's resource should be in the parent's resource provider.
+ EXPECT_EQ(2u, map.size());
+ EXPECT_EQ(1u, map.count(999));
+ EXPECT_EQ(1u, map.count(555));
+
+ ResourceProvider::ResourceId parent_resource_id1 = map.find(999)->second;
+ EXPECT_NE(parent_resource_id1, 999u);
+ ResourceProvider::ResourceId parent_resource_id2 = map.find(555)->second;
+ EXPECT_NE(parent_resource_id2, 555u);
+
+ // The resources in the quads should be remapped to the parent's namespace.
+ const TextureDrawQuad* quad1 = TextureDrawQuad::MaterialCast(
+ delegated_impl->RenderPassesInDrawOrder()[0]->quad_list[0]);
+ EXPECT_EQ(parent_resource_id1, quad1->resource_id);
+ const TextureDrawQuad* quad2 = TextureDrawQuad::MaterialCast(
+ delegated_impl->RenderPassesInDrawOrder()[0]->quad_list[1]);
+ EXPECT_EQ(parent_resource_id2, quad2->resource_id);
+
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostDelegatedTestRemapResourcesInQuads);
+
+class LayerTreeHostDelegatedTestReturnUnusedResources
+ : public LayerTreeHostDelegatedTestCaseSingleDelegatedLayer {
+ public:
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ scoped_ptr<DelegatedFrameData> frame;
+ TransferableResourceArray resources;
+
+ int next_source_frame_number = layer_tree_host()->source_frame_number();
+ switch (next_source_frame_number) {
+ case 1:
+ // Generate a frame with two resources in it.
+ frame = CreateFrameData(gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1));
+ AddTextureQuad(frame.get(), 999);
+ AddTransferableResource(frame.get(), 999);
+ AddTextureQuad(frame.get(), 555);
+ AddTransferableResource(frame.get(), 555);
+ delegated_->SetFrameData(frame.Pass());
+ break;
+ case 2:
+ // Retrieve unused resources to the main thread.
+ // TODO(danakj): Shouldn't need to commit to get resources.
+ layer_tree_host()->SetNeedsCommit();
+ return;
+ case 3:
+ // All of the resources are in use.
+ delegated_->TakeUnusedResourcesForChildCompositor(&resources);
+ EXPECT_EQ(0u, resources.size());
+
+ // Keep using 999 but stop using 555.
+ frame = CreateFrameData(gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1));
+ AddTextureQuad(frame.get(), 999);
+ AddTextureQuad(frame.get(), 444);
+ AddTransferableResource(frame.get(), 444);
+ delegated_->SetFrameData(frame.Pass());
+ break;
+ case 4:
+ // Retrieve unused resources to the main thread.
+ // TODO(danakj): Shouldn't need to commit to get resources.
+ layer_tree_host()->SetNeedsCommit();
+ return;
+ case 5:
+ // 555 is no longer in use.
+ delegated_->TakeUnusedResourcesForChildCompositor(&resources);
+ EXPECT_EQ(1u, resources.size());
+ EXPECT_EQ(555u, resources[0].id);
+
+ // Stop using any resources.
+ frame = CreateFrameData(gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1));
+ delegated_->SetFrameData(frame.Pass());
+ break;
+ case 6:
+ // Postpone collecting resources for a frame. They should still be there
+ // the next frame.
+ layer_tree_host()->SetNeedsCommit();
+ return;
+ case 7:
+ // 444 and 999 are no longer in use.
+ delegated_->TakeUnusedResourcesForChildCompositor(&resources);
+ EXPECT_EQ(2u, resources.size());
+ if (resources[0].id == 999) {
+ EXPECT_EQ(999u, resources[0].id);
+ EXPECT_EQ(444u, resources[1].id);
+ } else {
+ EXPECT_EQ(444u, resources[0].id);
+ EXPECT_EQ(999u, resources[1].id);
+ }
+ EndTest();
+ break;
+ }
+
+ // Resource are never immediately released.
+ TransferableResourceArray empty_resources;
+ delegated_->TakeUnusedResourcesForChildCompositor(&empty_resources);
+ EXPECT_TRUE(empty_resources.empty());
+ }
+
+ virtual void SwapBuffersOnThread(LayerTreeHostImpl* host_impl,
+ bool result) OVERRIDE {
+ ReturnUnusedResourcesFromParent(host_impl);
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostDelegatedTestReturnUnusedResources);
+
+class LayerTreeHostDelegatedTestReusedResources
+ : public LayerTreeHostDelegatedTestCaseSingleDelegatedLayer {
+ public:
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ scoped_ptr<DelegatedFrameData> frame;
+ TransferableResourceArray resources;
+
+ int next_source_frame_number = layer_tree_host()->source_frame_number();
+ switch (next_source_frame_number) {
+ case 1:
+ // Generate a frame with some resources in it.
+ frame = CreateFrameData(gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1));
+ AddTextureQuad(frame.get(), 999);
+ AddTransferableResource(frame.get(), 999);
+ AddTextureQuad(frame.get(), 555);
+ AddTransferableResource(frame.get(), 555);
+ AddTextureQuad(frame.get(), 444);
+ AddTransferableResource(frame.get(), 444);
+ delegated_->SetFrameData(frame.Pass());
+ break;
+ case 2:
+ // Retrieve unused resources to the main thread.
+ // TODO(danakj): Shouldn't need to commit to get resources.
+ layer_tree_host()->SetNeedsCommit();
+ return;
+ case 3:
+ // All of the resources are in use.
+ delegated_->TakeUnusedResourcesForChildCompositor(&resources);
+ EXPECT_EQ(0u, resources.size());
+
+ // Keep using 999 but stop using 555 and 444.
+ frame = CreateFrameData(gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1));
+ AddTextureQuad(frame.get(), 999);
+ delegated_->SetFrameData(frame.Pass());
+
+ // Resource are not immediately released.
+ delegated_->TakeUnusedResourcesForChildCompositor(&resources);
+ EXPECT_EQ(0u, resources.size());
+
+ // Now using 555 and 444 again, but not 999.
+ frame = CreateFrameData(gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1));
+ AddTextureQuad(frame.get(), 555);
+ AddTextureQuad(frame.get(), 444);
+ delegated_->SetFrameData(frame.Pass());
+ break;
+ case 4:
+ // Retrieve unused resources to the main thread.
+ // TODO(danakj): Shouldn't need to commit to get resources.
+ layer_tree_host()->SetNeedsCommit();
+ return;
+ case 5:
+ // The 999 resource is the only unused one.
+ delegated_->TakeUnusedResourcesForChildCompositor(&resources);
+ EXPECT_EQ(1u, resources.size());
+ EXPECT_EQ(999u, resources[0].id);
+ EndTest();
+ break;
+ }
+ }
+
+ virtual void SwapBuffersOnThread(LayerTreeHostImpl* host_impl,
+ bool result) OVERRIDE {
+ ReturnUnusedResourcesFromParent(host_impl);
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostDelegatedTestReusedResources);
+
+class LayerTreeHostDelegatedTestFrameBeforeAck
+ : public LayerTreeHostDelegatedTestCaseSingleDelegatedLayer {
+ public:
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ scoped_ptr<DelegatedFrameData> frame;
+ TransferableResourceArray resources;
+
+ int next_source_frame_number = layer_tree_host()->source_frame_number();
+ switch (next_source_frame_number) {
+ case 1:
+ // Generate a frame with some resources in it.
+ frame = CreateFrameData(gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1));
+ AddTextureQuad(frame.get(), 999);
+ AddTransferableResource(frame.get(), 999);
+ AddTextureQuad(frame.get(), 555);
+ AddTransferableResource(frame.get(), 555);
+ AddTextureQuad(frame.get(), 444);
+ AddTransferableResource(frame.get(), 444);
+ delegated_->SetFrameData(frame.Pass());
+ break;
+ case 2:
+ // Retrieve unused resources to the main thread.
+ // TODO(danakj): Shouldn't need to commit to get resources.
+ layer_tree_host()->SetNeedsCommit();
+ return;
+ case 3:
+ // All of the resources are in use.
+ delegated_->TakeUnusedResourcesForChildCompositor(&resources);
+ EXPECT_EQ(0u, resources.size());
+
+ // Keep using 999 but stop using 555 and 444.
+ frame = CreateFrameData(gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1));
+ AddTextureQuad(frame.get(), 999);
+ delegated_->SetFrameData(frame.Pass());
+
+ // Resource are not immediately released.
+ delegated_->TakeUnusedResourcesForChildCompositor(&resources);
+ EXPECT_EQ(0u, resources.size());
+
+ // The parent compositor (this one) does a commit.
+ break;
+ case 4:
+ // Retrieve unused resources to the main thread.
+ // TODO(danakj): Shouldn't need to commit to get resources.
+ layer_tree_host()->SetNeedsCommit();
+ return;
+ case 5:
+ delegated_->TakeUnusedResourcesForChildCompositor(&resources);
+ EXPECT_EQ(2u, resources.size());
+ if (resources[0].id == 555) {
+ EXPECT_EQ(555u, resources[0].id);
+ EXPECT_EQ(444u, resources[1].id);
+ } else {
+ EXPECT_EQ(444u, resources[0].id);
+ EXPECT_EQ(555u, resources[1].id);
+ }
+
+ // The child compositor sends a frame before receiving an for the
+ // second frame. It uses 999, 444, and 555 again.
+ frame = CreateFrameData(gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1));
+ AddTextureQuad(frame.get(), 999);
+ AddTextureQuad(frame.get(), 555);
+ AddTextureQuad(frame.get(), 444);
+ delegated_->SetFrameData(frame.Pass());
+ break;
+ }
+ }
+
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ if (host_impl->active_tree()->source_frame_number() != 5)
+ return;
+
+ LayerImpl* root_impl = host_impl->active_tree()->root_layer();
+ FakeDelegatedRendererLayerImpl* delegated_impl =
+ static_cast<FakeDelegatedRendererLayerImpl*>(root_impl->children()[0]);
+
+ const ResourceProvider::ResourceIdMap& map =
+ host_impl->resource_provider()->GetChildToParentMap(
+ delegated_impl->ChildId());
+
+ // The bad frame should be dropped. So we should only have one quad (the
+ // one with resource 999) on the impl tree. And only 999 will be present
+ // in the parent's resource provider.
+ EXPECT_EQ(1u, map.size());
+ EXPECT_EQ(1u, map.count(999));
+
+ EXPECT_EQ(1u, delegated_impl->Resources().size());
+ EXPECT_EQ(1u, delegated_impl->Resources().count(map.find(999)->second));
+
+ const RenderPass* pass = delegated_impl->RenderPassesInDrawOrder()[0];
+ EXPECT_EQ(1u, pass->quad_list.size());
+ const TextureDrawQuad* quad = TextureDrawQuad::MaterialCast(
+ pass->quad_list[0]);
+ EXPECT_EQ(map.find(999)->second, quad->resource_id);
+
+ EndTest();
+ }
+
+ virtual void SwapBuffersOnThread(LayerTreeHostImpl* host_impl,
+ bool result) OVERRIDE {
+ ReturnUnusedResourcesFromParent(host_impl);
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostDelegatedTestFrameBeforeAck);
+
+class LayerTreeHostDelegatedTestFrameBeforeTakeResources
+ : public LayerTreeHostDelegatedTestCaseSingleDelegatedLayer {
+ public:
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ scoped_ptr<DelegatedFrameData> frame;
+ TransferableResourceArray resources;
+
+ int next_source_frame_number = layer_tree_host()->source_frame_number();
+ switch (next_source_frame_number) {
+ case 1:
+ // Generate a frame with some resources in it.
+ frame = CreateFrameData(gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1));
+ AddTextureQuad(frame.get(), 999);
+ AddTransferableResource(frame.get(), 999);
+ AddTextureQuad(frame.get(), 555);
+ AddTransferableResource(frame.get(), 555);
+ AddTextureQuad(frame.get(), 444);
+ AddTransferableResource(frame.get(), 444);
+ delegated_->SetFrameData(frame.Pass());
+ break;
+ case 2:
+ // Retrieve unused resources to the main thread.
+ // TODO(danakj): Shouldn't need to commit to get resources.
+ layer_tree_host()->SetNeedsCommit();
+ return;
+ case 3:
+ // All of the resources are in use.
+ delegated_->TakeUnusedResourcesForChildCompositor(&resources);
+ EXPECT_EQ(0u, resources.size());
+
+ // Keep using 999 but stop using 555 and 444.
+ frame = CreateFrameData(gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1));
+ AddTextureQuad(frame.get(), 999);
+ delegated_->SetFrameData(frame.Pass());
+
+ // Resource are not immediately released.
+ delegated_->TakeUnusedResourcesForChildCompositor(&resources);
+ EXPECT_EQ(0u, resources.size());
+
+ // The parent compositor (this one) does a commit.
+ break;
+ case 4:
+ // Retrieve unused resources to the main thread.
+ // TODO(danakj): Shouldn't need to commit to get resources.
+ layer_tree_host()->SetNeedsCommit();
+ return;
+ case 5:
+ // The child compositor sends a frame before taking resources back
+ // from the previous commit. This frame makes use of the resources 555
+ // and 444, which were just released during commit.
+ frame = CreateFrameData(gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1));
+ AddTextureQuad(frame.get(), 999);
+ AddTextureQuad(frame.get(), 555);
+ AddTextureQuad(frame.get(), 444);
+ delegated_->SetFrameData(frame.Pass());
+
+ // The resources are used by the new frame so are not returned.
+ delegated_->TakeUnusedResourcesForChildCompositor(&resources);
+ EXPECT_EQ(0u, resources.size());
+ break;
+ case 6:
+ // Retrieve unused resources to the main thread.
+ // TODO(danakj): Shouldn't need to commit to get resources.
+ layer_tree_host()->SetNeedsCommit();
+ return;
+ case 7:
+ delegated_->TakeUnusedResourcesForChildCompositor(&resources);
+ EXPECT_EQ(0u, resources.size());
+ EndTest();
+ break;
+ }
+ }
+
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ if (host_impl->active_tree()->source_frame_number() != 5)
+ return;
+
+ LayerImpl* root_impl = host_impl->active_tree()->root_layer();
+ FakeDelegatedRendererLayerImpl* delegated_impl =
+ static_cast<FakeDelegatedRendererLayerImpl*>(root_impl->children()[0]);
+
+ const ResourceProvider::ResourceIdMap& map =
+ host_impl->resource_provider()->GetChildToParentMap(
+ delegated_impl->ChildId());
+
+ // The third frame has all of the resources in it again, the delegated
+ // renderer layer should continue to own the resources for it.
+ EXPECT_EQ(3u, map.size());
+ EXPECT_EQ(1u, map.count(999));
+ EXPECT_EQ(1u, map.count(555));
+ EXPECT_EQ(1u, map.count(444));
+
+ EXPECT_EQ(3u, delegated_impl->Resources().size());
+ EXPECT_EQ(1u, delegated_impl->Resources().count(map.find(999)->second));
+ EXPECT_EQ(1u, delegated_impl->Resources().count(map.find(555)->second));
+ EXPECT_EQ(1u, delegated_impl->Resources().count(map.find(444)->second));
+
+ const RenderPass* pass = delegated_impl->RenderPassesInDrawOrder()[0];
+ EXPECT_EQ(3u, pass->quad_list.size());
+ const TextureDrawQuad* quad1 = TextureDrawQuad::MaterialCast(
+ pass->quad_list[0]);
+ EXPECT_EQ(map.find(999)->second, quad1->resource_id);
+ const TextureDrawQuad* quad2 = TextureDrawQuad::MaterialCast(
+ pass->quad_list[1]);
+ EXPECT_EQ(map.find(555)->second, quad2->resource_id);
+ const TextureDrawQuad* quad3 = TextureDrawQuad::MaterialCast(
+ pass->quad_list[2]);
+ EXPECT_EQ(map.find(444)->second, quad3->resource_id);
+ }
+
+ virtual void SwapBuffersOnThread(LayerTreeHostImpl* host_impl,
+ bool result) OVERRIDE {
+ ReturnUnusedResourcesFromParent(host_impl);
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostDelegatedTestFrameBeforeTakeResources);
+
+class LayerTreeHostDelegatedTestBadFrame
+ : public LayerTreeHostDelegatedTestCaseSingleDelegatedLayer {
+ public:
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ scoped_ptr<DelegatedFrameData> frame;
+ TransferableResourceArray resources;
+
+ int next_source_frame_number = layer_tree_host()->source_frame_number();
+ switch (next_source_frame_number) {
+ case 1:
+ // Generate a frame with some resources in it.
+ frame = CreateFrameData(gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1));
+ AddTextureQuad(frame.get(), 999);
+ AddTransferableResource(frame.get(), 999);
+ AddTextureQuad(frame.get(), 555);
+ AddTransferableResource(frame.get(), 555);
+ delegated_->SetFrameData(frame.Pass());
+ break;
+ case 2:
+ // Retrieve unused resources to the main thread.
+ // TODO(danakj): Shouldn't need to commit to get resources.
+ layer_tree_host()->SetNeedsCommit();
+ return;
+ case 3:
+ // All of the resources are in use.
+ delegated_->TakeUnusedResourcesForChildCompositor(&resources);
+ EXPECT_EQ(0u, resources.size());
+
+ // Generate a bad frame with a resource the layer doesn't have. The
+ // 885 and 775 resources are unknown, while ownership of the legit 444
+ // resource is passed in here. The bad frame does not use any of the
+ // previous resources, 999 or 555.
+ // A bad quad is present both before and after the good quad.
+ frame = CreateFrameData(gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1));
+ AddTextureQuad(frame.get(), 885);
+ AddTextureQuad(frame.get(), 444);
+ AddTransferableResource(frame.get(), 444);
+ AddTextureQuad(frame.get(), 775);
+ delegated_->SetFrameData(frame.Pass());
+
+ // The parent compositor (this one) does a commit.
+ break;
+ case 4:
+ // Retrieve unused resources to the main thread.
+ // TODO(danakj): Shouldn't need to commit to get resources.
+ layer_tree_host()->SetNeedsCommit();
+ return;
+ case 5:
+ // The bad frame's resource is given back to the child compositor.
+ delegated_->TakeUnusedResourcesForChildCompositor(&resources);
+ EXPECT_EQ(1u, resources.size());
+ EXPECT_EQ(444u, resources[0].id);
+
+ // Now send a good frame with 999 again.
+ frame = CreateFrameData(gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1));
+ AddTextureQuad(frame.get(), 999);
+ delegated_->SetFrameData(frame.Pass());
+ break;
+ case 6:
+ // Retrieve unused resources to the main thread.
+ // TODO(danakj): Shouldn't need to commit to get resources.
+ layer_tree_host()->SetNeedsCommit();
+ return;
+ case 7:
+ // The unused 555 from the last good frame is now released.
+ delegated_->TakeUnusedResourcesForChildCompositor(&resources);
+ EXPECT_EQ(1u, resources.size());
+ EXPECT_EQ(555u, resources[0].id);
+
+ EndTest();
+ break;
+ }
+ }
+
+ virtual void SwapBuffersOnThread(LayerTreeHostImpl* host_impl,
+ bool result) OVERRIDE {
+ if (host_impl->active_tree()->source_frame_number() < 1)
+ return;
+
+ ReturnUnusedResourcesFromParent(host_impl);
+
+ LayerImpl* root_impl = host_impl->active_tree()->root_layer();
+ FakeDelegatedRendererLayerImpl* delegated_impl =
+ static_cast<FakeDelegatedRendererLayerImpl*>(root_impl->children()[0]);
+
+ const ResourceProvider::ResourceIdMap& map =
+ host_impl->resource_provider()->GetChildToParentMap(
+ delegated_impl->ChildId());
+
+ switch (host_impl->active_tree()->source_frame_number()) {
+ case 1: {
+ // We have the first good frame with just 990 and 555 in it.
+ // layer.
+ EXPECT_EQ(2u, map.size());
+ EXPECT_EQ(1u, map.count(999));
+ EXPECT_EQ(1u, map.count(555));
+
+ EXPECT_EQ(2u, delegated_impl->Resources().size());
+ EXPECT_EQ(1u, delegated_impl->Resources().count(map.find(999)->second));
+ EXPECT_EQ(1u, delegated_impl->Resources().count(map.find(555)->second));
+
+ const RenderPass* pass = delegated_impl->RenderPassesInDrawOrder()[0];
+ EXPECT_EQ(2u, pass->quad_list.size());
+ const TextureDrawQuad* quad1 = TextureDrawQuad::MaterialCast(
+ pass->quad_list[0]);
+ EXPECT_EQ(map.find(999)->second, quad1->resource_id);
+ const TextureDrawQuad* quad2 = TextureDrawQuad::MaterialCast(
+ pass->quad_list[1]);
+ EXPECT_EQ(map.find(555)->second, quad2->resource_id);
+ break;
+ }
+ case 3: {
+ // We only keep resources from the last valid frame.
+ EXPECT_EQ(2u, map.size());
+ EXPECT_EQ(1u, map.count(999));
+ EXPECT_EQ(1u, map.count(555));
+
+ EXPECT_EQ(2u, delegated_impl->Resources().size());
+ EXPECT_EQ(1u, delegated_impl->Resources().count(map.find(999)->second));
+ EXPECT_EQ(1u, delegated_impl->Resources().count(map.find(555)->second));
+
+ // The bad frame is dropped though, we still have the frame with 999 and
+ // 555 in it.
+ const RenderPass* pass = delegated_impl->RenderPassesInDrawOrder()[0];
+ EXPECT_EQ(2u, pass->quad_list.size());
+ const TextureDrawQuad* quad1 = TextureDrawQuad::MaterialCast(
+ pass->quad_list[0]);
+ EXPECT_EQ(map.find(999)->second, quad1->resource_id);
+ const TextureDrawQuad* quad2 = TextureDrawQuad::MaterialCast(
+ pass->quad_list[1]);
+ EXPECT_EQ(map.find(555)->second, quad2->resource_id);
+ break;
+ }
+ case 5:
+ // Resources given to our parent compositor will be returned now, but
+ // the DelegatedRendererLayerImpl doesn't know about it until the next
+ // commit.
+ // TODO(danakj): Shouldn't need a commit to return resources to the
+ // DelegatedRendererLayerImpl or to the main thread.
+ break;
+ case 6: {
+ // We have the new good frame with just 999 in it.
+ EXPECT_EQ(1u, map.size());
+ EXPECT_EQ(1u, map.count(999));
+
+ EXPECT_EQ(1u, delegated_impl->Resources().size());
+ EXPECT_EQ(1u, delegated_impl->Resources().count(map.find(999)->second));
+
+ const RenderPass* pass = delegated_impl->RenderPassesInDrawOrder()[0];
+ EXPECT_EQ(1u, pass->quad_list.size());
+ const TextureDrawQuad* quad1 = TextureDrawQuad::MaterialCast(
+ pass->quad_list[0]);
+ EXPECT_EQ(map.find(999)->second, quad1->resource_id);
+ break;
+ }
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostDelegatedTestBadFrame);
+
+class LayerTreeHostDelegatedTestUnnamedResource
+ : public LayerTreeHostDelegatedTestCaseSingleDelegatedLayer {
+ public:
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommit() OVERRIDE {
+ scoped_ptr<DelegatedFrameData> frame;
+ TransferableResourceArray resources;
+
+ int next_source_frame_number = layer_tree_host()->source_frame_number();
+ switch (next_source_frame_number) {
+ case 1:
+ // This frame includes two resources in it, but only uses one.
+ frame = CreateFrameData(gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1));
+ AddTransferableResource(frame.get(), 999);
+ AddTextureQuad(frame.get(), 555);
+ AddTransferableResource(frame.get(), 555);
+ delegated_->SetFrameData(frame.Pass());
+ break;
+ case 2:
+ // The unused resource should be returned.
+ delegated_->TakeUnusedResourcesForChildCompositor(&resources);
+ EXPECT_EQ(1u, resources.size());
+ EXPECT_EQ(999u, resources[0].id);
+
+ EndTest();
+ break;
+ }
+ }
+
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ if (host_impl->active_tree()->source_frame_number() != 1)
+ return;
+
+ LayerImpl* root_impl = host_impl->active_tree()->root_layer();
+ FakeDelegatedRendererLayerImpl* delegated_impl =
+ static_cast<FakeDelegatedRendererLayerImpl*>(root_impl->children()[0]);
+
+ const ResourceProvider::ResourceIdMap& map =
+ host_impl->resource_provider()->GetChildToParentMap(
+ delegated_impl->ChildId());
+
+ // The layer only held on to the resource that was used.
+ EXPECT_EQ(1u, map.size());
+ EXPECT_EQ(1u, map.count(555));
+
+ EXPECT_EQ(1u, delegated_impl->Resources().size());
+ EXPECT_EQ(1u, delegated_impl->Resources().count(map.find(555)->second));
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostDelegatedTestUnnamedResource);
+
+class LayerTreeHostDelegatedTestDontLeakResource
+ : public LayerTreeHostDelegatedTestCaseSingleDelegatedLayer {
+ public:
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommit() OVERRIDE {
+ scoped_ptr<DelegatedFrameData> frame;
+ TransferableResourceArray resources;
+
+ int next_source_frame_number = layer_tree_host()->source_frame_number();
+ switch (next_source_frame_number) {
+ case 1:
+ // This frame includes two resources in it.
+ frame = CreateFrameData(gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1));
+ AddTextureQuad(frame.get(), 999);
+ AddTransferableResource(frame.get(), 999);
+ AddTextureQuad(frame.get(), 555);
+ AddTransferableResource(frame.get(), 555);
+ delegated_->SetFrameData(frame.Pass());
+
+ // But then we immediately stop using 999.
+ frame = CreateFrameData(gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1));
+ AddTextureQuad(frame.get(), 555);
+ delegated_->SetFrameData(frame.Pass());
+ break;
+ case 2:
+ // The unused resource should be returned.
+ delegated_->TakeUnusedResourcesForChildCompositor(&resources);
+ EXPECT_EQ(1u, resources.size());
+ EXPECT_EQ(999u, resources[0].id);
+
+ EndTest();
+ break;
+ }
+ }
+
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ if (host_impl->active_tree()->source_frame_number() != 1)
+ return;
+
+ LayerImpl* root_impl = host_impl->active_tree()->root_layer();
+ FakeDelegatedRendererLayerImpl* delegated_impl =
+ static_cast<FakeDelegatedRendererLayerImpl*>(root_impl->children()[0]);
+
+ const ResourceProvider::ResourceIdMap& map =
+ host_impl->resource_provider()->GetChildToParentMap(
+ delegated_impl->ChildId());
+
+ // The layer only held on to the resource that was used.
+ EXPECT_EQ(1u, map.size());
+ EXPECT_EQ(1u, map.count(555));
+
+ EXPECT_EQ(1u, delegated_impl->Resources().size());
+ EXPECT_EQ(1u, delegated_impl->Resources().count(map.find(555)->second));
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostDelegatedTestDontLeakResource);
+
+class LayerTreeHostDelegatedTestResourceSentToParent
+ : public LayerTreeHostDelegatedTestCaseSingleDelegatedLayer {
+ public:
+ virtual void DidCommitAndDrawFrame() OVERRIDE {
+ scoped_ptr<DelegatedFrameData> frame;
+ TransferableResourceArray resources;
+
+ int next_source_frame_number = layer_tree_host()->source_frame_number();
+ switch (next_source_frame_number) {
+ case 1:
+ // This frame includes two resources in it.
+ frame = CreateFrameData(gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1));
+ AddTextureQuad(frame.get(), 999);
+ AddTransferableResource(frame.get(), 999);
+ AddTextureQuad(frame.get(), 555);
+ AddTransferableResource(frame.get(), 555);
+ delegated_->SetFrameData(frame.Pass());
+ break;
+ case 2:
+ delegated_->TakeUnusedResourcesForChildCompositor(&resources);
+ EXPECT_EQ(0u, resources.size());
+
+ // 999 is in use in the grandparent compositor, generate a frame without
+ // it present.
+ frame = CreateFrameData(gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1));
+ AddTextureQuad(frame.get(), 555);
+ delegated_->SetFrameData(frame.Pass());
+ break;
+ case 3:
+ // Since 999 is in the grandparent it is not returned.
+ delegated_->TakeUnusedResourcesForChildCompositor(&resources);
+ EXPECT_EQ(0u, resources.size());
+
+ // The impl side will get back the resource at some point.
+ // TODO(danakj): The test should work without this.
+ layer_tree_host()->SetNeedsCommit();
+ break;
+ case 4:
+ // 999 was returned from the grandparent and could be released.
+ delegated_->TakeUnusedResourcesForChildCompositor(&resources);
+ EXPECT_EQ(1u, resources.size());
+ EXPECT_EQ(999u, resources[0].id);
+
+ EndTest();
+ break;
+ }
+ }
+
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ if (host_impl->active_tree()->source_frame_number() < 1)
+ return;
+
+ LayerImpl* root_impl = host_impl->active_tree()->root_layer();
+ FakeDelegatedRendererLayerImpl* delegated_impl =
+ static_cast<FakeDelegatedRendererLayerImpl*>(root_impl->children()[0]);
+
+ const ResourceProvider::ResourceIdMap& map =
+ host_impl->resource_provider()->GetChildToParentMap(
+ delegated_impl->ChildId());
+
+ switch (host_impl->active_tree()->source_frame_number()) {
+ case 1: {
+ EXPECT_EQ(2u, map.size());
+ EXPECT_EQ(1u, map.count(999));
+ EXPECT_EQ(1u, map.count(555));
+
+ EXPECT_EQ(2u, delegated_impl->Resources().size());
+ EXPECT_EQ(1u, delegated_impl->Resources().count(map.find(999)->second));
+ EXPECT_EQ(1u, delegated_impl->Resources().count(map.find(555)->second));
+
+ // The 999 resource will be sent to a grandparent compositor.
+ break;
+ }
+ case 2: {
+ EXPECT_EQ(2u, map.size());
+ EXPECT_EQ(1u, map.count(999));
+ EXPECT_EQ(1u, map.count(555));
+
+ // 999 is in the parent, so not held by delegated renderer layer.
+ EXPECT_EQ(1u, delegated_impl->Resources().size());
+ EXPECT_EQ(1u, delegated_impl->Resources().count(map.find(555)->second));
+
+ // Receive 999 back from the grandparent.
+ CompositorFrameAck ack;
+ output_surface()->ReturnResource(map.find(999)->second, &ack);
+ host_impl->OnSwapBuffersComplete(&ack);
+ break;
+ }
+ case 3:
+ // 999 should be released.
+ EXPECT_EQ(1u, map.size());
+ EXPECT_EQ(1u, map.count(555));
+
+ EXPECT_EQ(1u, delegated_impl->Resources().size());
+ EXPECT_EQ(1u, delegated_impl->Resources().count(map.find(555)->second));
+ break;
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ TransferableResource resource_in_grandparent;
+};
+
+SINGLE_AND_MULTI_THREAD_DELEGATING_RENDERER_TEST_F(
+ LayerTreeHostDelegatedTestResourceSentToParent);
+
+class LayerTreeHostDelegatedTestCommitWithoutTake
+ : public LayerTreeHostDelegatedTestCaseSingleDelegatedLayer {
+ public:
+ virtual void BeginTest() OVERRIDE {
+ // Prevent drawing with resources that are sent to the grandparent.
+ layer_tree_host()->SetViewportSize(gfx::Size());
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommit() OVERRIDE {
+ scoped_ptr<DelegatedFrameData> frame;
+ TransferableResourceArray resources;
+
+ int next_source_frame_number = layer_tree_host()->source_frame_number();
+ switch (next_source_frame_number) {
+ case 1:
+ frame = CreateFrameData(gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1));
+ AddTextureQuad(frame.get(), 999);
+ AddTransferableResource(frame.get(), 999);
+ AddTextureQuad(frame.get(), 555);
+ AddTransferableResource(frame.get(), 555);
+ AddTextureQuad(frame.get(), 444);
+ AddTransferableResource(frame.get(), 444);
+ delegated_->SetFrameData(frame.Pass());
+ break;
+ case 2:
+ delegated_->TakeUnusedResourcesForChildCompositor(&resources);
+ EXPECT_EQ(0u, resources.size());
+
+ // Stop using 999 and 444 in this frame and commit.
+ frame = CreateFrameData(gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1));
+ AddTextureQuad(frame.get(), 555);
+ delegated_->SetFrameData(frame.Pass());
+ break;
+ case 3:
+ // Don't take resources here, but set a new frame that uses 999 again.
+ frame = CreateFrameData(gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1));
+ AddTextureQuad(frame.get(), 999);
+ AddTextureQuad(frame.get(), 555);
+ delegated_->SetFrameData(frame.Pass());
+ break;
+ case 4:
+ // 999 and 555 are in use, but 444 should be returned now.
+ delegated_->TakeUnusedResourcesForChildCompositor(&resources);
+ EXPECT_EQ(1u, resources.size());
+ EXPECT_EQ(444u, resources[0].id);
+
+ EndTest();
+ break;
+ }
+ }
+
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ if (host_impl->active_tree()->source_frame_number() < 1)
+ return;
+
+ LayerImpl* root_impl = host_impl->active_tree()->root_layer();
+ FakeDelegatedRendererLayerImpl* delegated_impl =
+ static_cast<FakeDelegatedRendererLayerImpl*>(root_impl->children()[0]);
+
+ const ResourceProvider::ResourceIdMap& map =
+ host_impl->resource_provider()->GetChildToParentMap(
+ delegated_impl->ChildId());
+
+ switch (host_impl->active_tree()->source_frame_number()) {
+ case 1:
+ EXPECT_EQ(3u, map.size());
+ EXPECT_EQ(1u, map.count(999));
+ EXPECT_EQ(1u, map.count(555));
+ EXPECT_EQ(1u, map.count(444));
+
+ EXPECT_EQ(3u, delegated_impl->Resources().size());
+ EXPECT_EQ(1u, delegated_impl->Resources().count(map.find(999)->second));
+ EXPECT_EQ(1u, delegated_impl->Resources().count(map.find(555)->second));
+ EXPECT_EQ(1u, delegated_impl->Resources().count(map.find(444)->second));
+ break;
+ case 2:
+ EXPECT_EQ(1u, map.size());
+ EXPECT_EQ(1u, map.count(555));
+
+ EXPECT_EQ(1u, delegated_impl->Resources().size());
+ EXPECT_EQ(1u, delegated_impl->Resources().count(map.find(555)->second));
+ break;
+ case 3:
+ EXPECT_EQ(2u, map.size());
+ EXPECT_EQ(1u, map.count(999));
+ EXPECT_EQ(1u, map.count(555));
+
+ EXPECT_EQ(2u, delegated_impl->Resources().size());
+ EXPECT_EQ(1u, delegated_impl->Resources().count(map.find(999)->second));
+ EXPECT_EQ(1u, delegated_impl->Resources().count(map.find(555)->second));
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostDelegatedTestCommitWithoutTake);
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/trees/layer_tree_host_unittest_occlusion.cc b/chromium/cc/trees/layer_tree_host_unittest_occlusion.cc
new file mode 100644
index 00000000000..155b8428dd8
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_host_unittest_occlusion.cc
@@ -0,0 +1,477 @@
+// 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 "cc/trees/layer_tree_host.h"
+
+#include "cc/layers/layer.h"
+#include "cc/test/layer_tree_test.h"
+#include "cc/test/occlusion_tracker_test_common.h"
+
+namespace cc {
+namespace {
+
+class TestLayer : public Layer {
+ public:
+ static scoped_refptr<TestLayer> Create() {
+ return make_scoped_refptr(new TestLayer());
+ }
+
+ virtual bool Update(
+ ResourceUpdateQueue* update_queue,
+ const OcclusionTracker* occlusion) OVERRIDE {
+ if (!occlusion)
+ return false;
+
+ // Gain access to internals of the OcclusionTracker.
+ const TestOcclusionTracker* test_occlusion =
+ static_cast<const TestOcclusionTracker*>(occlusion);
+ occlusion_ = UnionRegions(
+ test_occlusion->occlusion_from_inside_target(),
+ test_occlusion->occlusion_from_outside_target());
+ return false;
+ }
+
+ const Region& occlusion() const { return occlusion_; }
+ const Region& expected_occlusion() const { return expected_occlusion_; }
+ void set_expected_occlusion(const Region& occlusion) {
+ expected_occlusion_ = occlusion;
+ }
+
+ private:
+ TestLayer() : Layer() {
+ SetIsDrawable(true);
+ }
+ virtual ~TestLayer() {}
+
+ Region occlusion_;
+ Region expected_occlusion_;
+};
+
+class LayerTreeHostOcclusionTest : public LayerTreeTest {
+ public:
+ LayerTreeHostOcclusionTest()
+ : root_(TestLayer::Create()),
+ child_(TestLayer::Create()),
+ child2_(TestLayer::Create()),
+ grand_child_(TestLayer::Create()),
+ mask_(TestLayer::Create()) {
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommit() OVERRIDE {
+ TestLayer* root = static_cast<TestLayer*>(layer_tree_host()->root_layer());
+ VerifyOcclusion(root);
+
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ void VerifyOcclusion(TestLayer* layer) const {
+ EXPECT_EQ(layer->expected_occlusion().ToString(),
+ layer->occlusion().ToString());
+
+ for (size_t i = 0; i < layer->children().size(); ++i) {
+ TestLayer* child = static_cast<TestLayer*>(layer->children()[i].get());
+ VerifyOcclusion(child);
+ }
+ }
+
+ void SetLayerPropertiesForTesting(TestLayer* layer,
+ TestLayer* parent,
+ const gfx::Transform& transform,
+ gfx::PointF position,
+ gfx::Size bounds,
+ bool opaque) const {
+ layer->RemoveAllChildren();
+ if (parent)
+ parent->AddChild(layer);
+ layer->SetTransform(transform);
+ layer->SetPosition(position);
+ layer->SetBounds(bounds);
+ layer->SetContentsOpaque(opaque);
+
+ layer->SetAnchorPoint(gfx::PointF());
+ }
+
+ protected:
+ scoped_refptr<TestLayer> root_;
+ scoped_refptr<TestLayer> child_;
+ scoped_refptr<TestLayer> child2_;
+ scoped_refptr<TestLayer> grand_child_;
+ scoped_refptr<TestLayer> mask_;
+
+ gfx::Transform identity_matrix_;
+};
+
+
+class LayerTreeHostOcclusionTestOcclusionSurfaceClipping
+ : public LayerTreeHostOcclusionTest {
+ public:
+ virtual void SetupTree() OVERRIDE {
+ // The child layer is a surface and the grand_child is opaque, but clipped
+ // to the child and root
+ SetLayerPropertiesForTesting(
+ root_.get(), NULL, identity_matrix_,
+ gfx::PointF(0.f, 0.f), gfx::Size(200, 200), true);
+ SetLayerPropertiesForTesting(
+ child_.get(), root_.get(), identity_matrix_,
+ gfx::PointF(10.f, 10.f), gfx::Size(500, 500), false);
+ SetLayerPropertiesForTesting(
+ grand_child_.get(), child_.get(), identity_matrix_,
+ gfx::PointF(-10.f, -10.f), gfx::Size(20, 500), true);
+
+ child_->SetMasksToBounds(true);
+ child_->SetForceRenderSurface(true);
+
+ child_->set_expected_occlusion(gfx::Rect(0, 0, 10, 190));
+ root_->set_expected_occlusion(gfx::Rect(10, 10, 10, 190));
+
+ layer_tree_host()->SetRootLayer(root_);
+ LayerTreeTest::SetupTree();
+ }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostOcclusionTestOcclusionSurfaceClipping);
+
+class LayerTreeHostOcclusionTestOcclusionSurfaceClippingOpaque
+ : public LayerTreeHostOcclusionTest {
+ public:
+ virtual void SetupTree() OVERRIDE {
+ // If the child layer is opaque, then it adds to the occlusion seen by the
+ // root_.
+ SetLayerPropertiesForTesting(
+ root_.get(), NULL, identity_matrix_,
+ gfx::PointF(0.f, 0.f), gfx::Size(200, 200), true);
+ SetLayerPropertiesForTesting(
+ child_.get(), root_.get(), identity_matrix_,
+ gfx::PointF(10.f, 10.f), gfx::Size(500, 500), true);
+ SetLayerPropertiesForTesting(
+ grand_child_.get(), child_.get(), identity_matrix_,
+ gfx::PointF(-10.f, -10.f), gfx::Size(20, 500), true);
+
+ child_->SetMasksToBounds(true);
+ child_->SetForceRenderSurface(true);
+
+ child_->set_expected_occlusion(gfx::Rect(0, 0, 10, 190));
+ root_->set_expected_occlusion(gfx::Rect(10, 10, 190, 190));
+
+ layer_tree_host()->SetRootLayer(root_);
+ LayerTreeTest::SetupTree();
+ }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostOcclusionTestOcclusionSurfaceClippingOpaque);
+
+class LayerTreeHostOcclusionTestOcclusionTwoChildren
+ : public LayerTreeHostOcclusionTest {
+ public:
+ virtual void SetupTree() OVERRIDE {
+ // Add a second child to the root layer and the regions should merge
+ SetLayerPropertiesForTesting(
+ root_.get(), NULL, identity_matrix_,
+ gfx::PointF(0.f, 0.f), gfx::Size(200, 200), true);
+ SetLayerPropertiesForTesting(
+ child_.get(), root_.get(), identity_matrix_,
+ gfx::PointF(10.f, 10.f), gfx::Size(500, 500), false);
+ SetLayerPropertiesForTesting(
+ grand_child_.get(), child_.get(), identity_matrix_,
+ gfx::PointF(-10.f, -10.f), gfx::Size(20, 500), true);
+ SetLayerPropertiesForTesting(
+ child2_.get(), root_.get(), identity_matrix_,
+ gfx::PointF(20.f, 10.f), gfx::Size(10, 500), true);
+
+ child_->SetMasksToBounds(true);
+ child_->SetForceRenderSurface(true);
+
+ grand_child_->set_expected_occlusion(gfx::Rect(10, 0, 10, 190));
+ child_->set_expected_occlusion(gfx::Rect(0, 0, 20, 190));
+ root_->set_expected_occlusion(gfx::Rect(10, 10, 20, 190));
+
+ layer_tree_host()->SetRootLayer(root_);
+ LayerTreeTest::SetupTree();
+ }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostOcclusionTestOcclusionTwoChildren);
+
+class LayerTreeHostOcclusionTestOcclusionMask
+ : public LayerTreeHostOcclusionTest {
+ public:
+ virtual void SetupTree() OVERRIDE {
+ // If the child layer has a mask on it, then it shouldn't contribute to
+ // occlusion on stuff below it.
+ SetLayerPropertiesForTesting(
+ root_.get(), NULL, identity_matrix_,
+ gfx::PointF(0.f, 0.f), gfx::Size(200, 200), true);
+ SetLayerPropertiesForTesting(
+ child2_.get(), root_.get(), identity_matrix_,
+ gfx::PointF(10.f, 10.f), gfx::Size(500, 500), true);
+ SetLayerPropertiesForTesting(
+ child_.get(), root_.get(), identity_matrix_,
+ gfx::PointF(20.f, 20.f), gfx::Size(500, 500), true);
+ SetLayerPropertiesForTesting(
+ grand_child_.get(), child_.get(), identity_matrix_,
+ gfx::PointF(-10.f, -10.f), gfx::Size(500, 500), true);
+
+ child_->SetMasksToBounds(true);
+ child_->SetForceRenderSurface(true);
+ child_->SetMaskLayer(mask_.get());
+
+ child_->set_expected_occlusion(gfx::Rect(0, 0, 180, 180));
+ root_->set_expected_occlusion(gfx::Rect(10, 10, 190, 190));
+
+ layer_tree_host()->SetRootLayer(root_);
+ LayerTreeTest::SetupTree();
+ }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostOcclusionTestOcclusionMask);
+
+class LayerTreeHostOcclusionTestOcclusionMaskBelowOcclusion
+ : public LayerTreeHostOcclusionTest {
+ public:
+ virtual void SetupTree() OVERRIDE {
+ // If the child layer with a mask is below child2, then child2 should
+ // contribute to occlusion on everything, and child shouldn't contribute
+ // to the root_.
+ SetLayerPropertiesForTesting(
+ root_.get(), NULL, identity_matrix_,
+ gfx::PointF(0.f, 0.f), gfx::Size(200, 200), true);
+ SetLayerPropertiesForTesting(
+ child_.get(), root_.get(), identity_matrix_,
+ gfx::PointF(10.f, 10.f), gfx::Size(500, 500), true);
+ SetLayerPropertiesForTesting(
+ grand_child_.get(), child_.get(), identity_matrix_,
+ gfx::PointF(-10.f, -10.f), gfx::Size(20, 500), true);
+ SetLayerPropertiesForTesting(
+ child2_.get(), root_.get(), identity_matrix_,
+ gfx::PointF(20.f, 10.f), gfx::Size(10, 500), true);
+
+ child_->SetMasksToBounds(true);
+ child_->SetForceRenderSurface(true);
+ child_->SetMaskLayer(mask_.get());
+
+ grand_child_->set_expected_occlusion(gfx::Rect(10, 0, 10, 190));
+ child_->set_expected_occlusion(gfx::Rect(0, 0, 20, 190));
+ root_->set_expected_occlusion(gfx::Rect(20, 10, 10, 190));
+
+ layer_tree_host()->SetRootLayer(root_);
+ LayerTreeTest::SetupTree();
+ }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostOcclusionTestOcclusionMaskBelowOcclusion);
+
+class LayerTreeHostOcclusionTestOcclusionOpacity
+ : public LayerTreeHostOcclusionTest {
+ public:
+ virtual void SetupTree() OVERRIDE {
+ // If the child layer has a non-opaque opacity, then it shouldn't
+ // contribute to occlusion on stuff below it
+ SetLayerPropertiesForTesting(
+ root_.get(), NULL, identity_matrix_,
+ gfx::PointF(0.f, 0.f), gfx::Size(200, 200), true);
+ SetLayerPropertiesForTesting(
+ child2_.get(), root_.get(), identity_matrix_,
+ gfx::PointF(20.f, 10.f), gfx::Size(10, 500), true);
+ SetLayerPropertiesForTesting(
+ child_.get(), root_.get(), identity_matrix_,
+ gfx::PointF(10.f, 10.f), gfx::Size(500, 500), true);
+ SetLayerPropertiesForTesting(
+ grand_child_.get(), child_.get(), identity_matrix_,
+ gfx::PointF(-10.f, -10.f), gfx::Size(20, 500), true);
+
+ child_->SetMasksToBounds(true);
+ child_->SetForceRenderSurface(true);
+ child_->SetOpacity(0.5f);
+
+ child_->set_expected_occlusion(gfx::Rect(0, 0, 10, 190));
+ root_->set_expected_occlusion(gfx::Rect(20, 10, 10, 190));
+
+ layer_tree_host()->SetRootLayer(root_);
+ LayerTreeTest::SetupTree();
+ }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostOcclusionTestOcclusionOpacity);
+
+class LayerTreeHostOcclusionTestOcclusionOpacityBelowOcclusion
+ : public LayerTreeHostOcclusionTest {
+ public:
+ virtual void SetupTree() OVERRIDE {
+ // If the child layer with non-opaque opacity is below child2, then
+ // child2 should contribute to occlusion on everything, and child shouldn't
+ // contribute to the root_.
+ SetLayerPropertiesForTesting(
+ root_.get(), NULL, identity_matrix_,
+ gfx::PointF(0.f, 0.f), gfx::Size(200, 200), true);
+ SetLayerPropertiesForTesting(
+ child_.get(), root_.get(), identity_matrix_,
+ gfx::PointF(10.f, 10.f), gfx::Size(500, 500), true);
+ SetLayerPropertiesForTesting(
+ grand_child_.get(), child_.get(), identity_matrix_,
+ gfx::PointF(-10.f, -10.f), gfx::Size(20, 500), true);
+ SetLayerPropertiesForTesting(
+ child2_.get(), root_.get(), identity_matrix_,
+ gfx::PointF(20.f, 10.f), gfx::Size(10, 500), true);
+
+ child_->SetMasksToBounds(true);
+ child_->SetForceRenderSurface(true);
+ child_->SetOpacity(0.5f);
+
+ grand_child_->set_expected_occlusion(gfx::Rect(10, 0, 10, 190));
+ child_->set_expected_occlusion(gfx::Rect(0, 0, 20, 190));
+ root_->set_expected_occlusion(gfx::Rect(20, 10, 10, 190));
+
+ layer_tree_host()->SetRootLayer(root_);
+ LayerTreeTest::SetupTree();
+ }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostOcclusionTestOcclusionOpacityBelowOcclusion);
+
+class LayerTreeHostOcclusionTestOcclusionOpacityFilter
+ : public LayerTreeHostOcclusionTest {
+ public:
+ virtual void SetupTree() OVERRIDE {
+ gfx::Transform child_transform;
+ child_transform.Translate(250.0, 250.0);
+ child_transform.Rotate(90.0);
+ child_transform.Translate(-250.0, -250.0);
+
+ FilterOperations filters;
+ filters.Append(FilterOperation::CreateOpacityFilter(0.5f));
+
+ // If the child layer has a filter that changes alpha values, and is below
+ // child2, then child2 should contribute to occlusion on everything,
+ // and child shouldn't contribute to the root
+ SetLayerPropertiesForTesting(
+ root_.get(), NULL, identity_matrix_,
+ gfx::PointF(0.f, 0.f), gfx::Size(200, 200), true);
+ SetLayerPropertiesForTesting(
+ child_.get(), root_.get(), child_transform,
+ gfx::PointF(30.f, 30.f), gfx::Size(500, 500), true);
+ SetLayerPropertiesForTesting(
+ grand_child_.get(), child_.get(), identity_matrix_,
+ gfx::PointF(10.f, 10.f), gfx::Size(500, 500), true);
+ SetLayerPropertiesForTesting(
+ child2_.get(), root_.get(), identity_matrix_,
+ gfx::PointF(10.f, 70.f), gfx::Size(500, 500), true);
+
+ child_->SetMasksToBounds(true);
+ child_->SetFilters(filters);
+
+ grand_child_->set_expected_occlusion(gfx::Rect(40, 330, 130, 190));
+ child_->set_expected_occlusion(UnionRegions(
+ gfx::Rect(10, 330, 160, 170), gfx::Rect(40, 500, 130, 20)));
+ root_->set_expected_occlusion(gfx::Rect(10, 70, 190, 130));
+
+ layer_tree_host()->SetRootLayer(root_);
+ LayerTreeTest::SetupTree();
+ }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostOcclusionTestOcclusionOpacityFilter);
+
+class LayerTreeHostOcclusionTestOcclusionBlurFilter
+ : public LayerTreeHostOcclusionTest {
+ public:
+ virtual void SetupTree() OVERRIDE {
+ gfx::Transform child_transform;
+ child_transform.Translate(250.0, 250.0);
+ child_transform.Rotate(90.0);
+ child_transform.Translate(-250.0, -250.0);
+
+ FilterOperations filters;
+ filters.Append(FilterOperation::CreateBlurFilter(10.f));
+
+ // If the child layer has a filter that moves pixels/changes alpha, and is
+ // below child2, then child should not inherit occlusion from outside its
+ // subtree, and should not contribute to the root
+ SetLayerPropertiesForTesting(
+ root_.get(), NULL, identity_matrix_,
+ gfx::PointF(0.f, 0.f), gfx::Size(200, 200), true);
+ SetLayerPropertiesForTesting(
+ child_.get(), root_.get(), child_transform,
+ gfx::PointF(30.f, 30.f), gfx::Size(500, 500), true);
+ SetLayerPropertiesForTesting(
+ grand_child_.get(), child_.get(), identity_matrix_,
+ gfx::PointF(10.f, 10.f), gfx::Size(500, 500), true);
+ SetLayerPropertiesForTesting(
+ child2_.get(), root_.get(), identity_matrix_,
+ gfx::PointF(10.f, 70.f), gfx::Size(500, 500), true);
+
+ child_->SetMasksToBounds(true);
+ child_->SetFilters(filters);
+
+ child_->set_expected_occlusion(gfx::Rect(10, 330, 160, 170));
+ root_->set_expected_occlusion(gfx::Rect(10, 70, 190, 130));
+
+ layer_tree_host()->SetRootLayer(root_);
+ LayerTreeTest::SetupTree();
+ }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostOcclusionTestOcclusionBlurFilter);
+
+class LayerTreeHostOcclusionTestManySurfaces
+ : public LayerTreeHostOcclusionTest {
+ public:
+ virtual void SetupTree() OVERRIDE {
+ // We create enough RenderSurfaces that it will trigger Vector reallocation
+ // while computing occlusion.
+ std::vector<scoped_refptr<TestLayer> > layers;
+ int num_surfaces = 200;
+ int root_width = 400;
+ int root_height = 400;
+
+ for (int i = 0; i < num_surfaces; ++i) {
+ layers.push_back(TestLayer::Create());
+ if (i == 0) {
+ SetLayerPropertiesForTesting(
+ layers.back().get(), NULL, identity_matrix_,
+ gfx::PointF(0.f, 0.f),
+ gfx::Size(root_width, root_height), true);
+ } else {
+ SetLayerPropertiesForTesting(
+ layers.back().get(), layers[layers.size() - 2].get(),
+ identity_matrix_,
+ gfx::PointF(1.f, 1.f),
+ gfx::Size(root_width-i, root_height-i), true);
+ layers.back()->SetForceRenderSurface(true);
+ }
+ }
+
+ for (int i = 1; i < num_surfaces; ++i) {
+ scoped_refptr<TestLayer> child = TestLayer::Create();
+ SetLayerPropertiesForTesting(
+ child.get(), layers[i].get(), identity_matrix_,
+ gfx::PointF(0.f, 0.f), gfx::Size(root_width, root_height), false);
+ }
+
+ for (int i = 0; i < num_surfaces-1; ++i) {
+ gfx::Rect expected_occlusion(1, 1, root_width-i-1, root_height-i-1);
+ layers[i]->set_expected_occlusion(expected_occlusion);
+ }
+
+ layer_tree_host()->SetRootLayer(layers[0]);
+ LayerTreeTest::SetupTree();
+ }
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostOcclusionTestManySurfaces);
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/trees/layer_tree_host_unittest_picture.cc b/chromium/cc/trees/layer_tree_host_unittest_picture.cc
new file mode 100644
index 00000000000..731f69214af
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_host_unittest_picture.cc
@@ -0,0 +1,119 @@
+// Copyright 2013 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 "cc/trees/layer_tree_host.h"
+
+#include "cc/test/fake_content_layer_client.h"
+#include "cc/test/fake_picture_layer.h"
+#include "cc/test/fake_picture_layer_impl.h"
+#include "cc/test/layer_tree_test.h"
+#include "cc/trees/layer_tree_impl.h"
+
+namespace cc {
+namespace {
+
+// These tests deal with picture layers.
+class LayerTreeHostPictureTest : public LayerTreeTest {
+ protected:
+ virtual void InitializeSettings(LayerTreeSettings* settings) OVERRIDE {
+ // PictureLayer can only be used with impl side painting enabled.
+ settings->impl_side_painting = true;
+ }
+};
+
+class LayerTreeHostPictureTestTwinLayer
+ : public LayerTreeHostPictureTest {
+ virtual void SetupTree() OVERRIDE {
+ LayerTreeHostPictureTest::SetupTree();
+
+ scoped_refptr<FakePictureLayer> picture =
+ FakePictureLayer::Create(&client_);
+ layer_tree_host()->root_layer()->AddChild(picture);
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ activates_ = 0;
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DidCommit() OVERRIDE {
+ switch (layer_tree_host()->source_frame_number()) {
+ case 2:
+ // Drop the picture layer from the tree.
+ layer_tree_host()->root_layer()->children()[0]->RemoveFromParent();
+ break;
+ case 3:
+ // Add a new picture layer.
+ scoped_refptr<FakePictureLayer> picture =
+ FakePictureLayer::Create(&client_);
+ layer_tree_host()->root_layer()->AddChild(picture);
+ break;
+ }
+ }
+
+ virtual void WillActivateTreeOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ LayerImpl* pending_root_impl = impl->pending_tree()->root_layer();
+ LayerImpl* active_root_impl = impl->active_tree()->root_layer();
+
+ if (pending_root_impl->children().empty()) {
+ EXPECT_EQ(2, activates_);
+ return;
+ }
+
+ FakePictureLayerImpl* pending_picture_impl =
+ static_cast<FakePictureLayerImpl*>(pending_root_impl->children()[0]);
+
+ if (!active_root_impl) {
+ EXPECT_EQ(0, activates_);
+ EXPECT_EQ(NULL, pending_picture_impl->twin_layer());
+ return;
+ }
+
+ if (active_root_impl->children().empty()) {
+ EXPECT_EQ(3, activates_);
+ EXPECT_EQ(NULL, pending_picture_impl->twin_layer());
+ return;
+ }
+
+ FakePictureLayerImpl* active_picture_impl =
+ static_cast<FakePictureLayerImpl*>(active_root_impl->children()[0]);
+
+ // After the first activation, when we commit again, we'll have a pending
+ // and active layer. Then we recreate a picture layer in the 4th activate
+ // and the next commit will have a pending and active twin again.
+ EXPECT_TRUE(activates_ == 1 || activates_ == 4);
+
+ EXPECT_EQ(pending_picture_impl, active_picture_impl->twin_layer());
+ EXPECT_EQ(active_picture_impl, pending_picture_impl->twin_layer());
+ }
+
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ LayerImpl* active_root_impl = impl->active_tree()->root_layer();
+
+ if (active_root_impl->children().empty()) {
+ EXPECT_EQ(2, activates_);
+ } else {
+ FakePictureLayerImpl* active_picture_impl =
+ static_cast<FakePictureLayerImpl*>(active_root_impl->children()[0]);
+
+ EXPECT_EQ(NULL, active_picture_impl->twin_layer());
+ }
+
+ ++activates_;
+ if (activates_ <= 4)
+ PostSetNeedsCommitToMainThread();
+ else
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ FakeContentLayerClient client_;
+ int activates_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostPictureTestTwinLayer);
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/trees/layer_tree_host_unittest_scroll.cc b/chromium/cc/trees/layer_tree_host_unittest_scroll.cc
new file mode 100644
index 00000000000..303e92496b4
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_host_unittest_scroll.cc
@@ -0,0 +1,1028 @@
+// 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 "cc/trees/layer_tree_host.h"
+
+#include "base/memory/weak_ptr.h"
+#include "cc/layers/content_layer.h"
+#include "cc/layers/layer.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/test/fake_content_layer_client.h"
+#include "cc/test/fake_layer_tree_host_client.h"
+#include "cc/test/geometry_test_utils.h"
+#include "cc/test/layer_tree_test.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "ui/gfx/point_conversions.h"
+#include "ui/gfx/size_conversions.h"
+#include "ui/gfx/vector2d_conversions.h"
+
+namespace cc {
+namespace {
+
+class LayerTreeHostScrollTest : public LayerTreeTest {};
+
+class LayerTreeHostScrollTestScrollSimple : public LayerTreeHostScrollTest {
+ public:
+ LayerTreeHostScrollTestScrollSimple()
+ : initial_scroll_(10, 20),
+ second_scroll_(40, 5),
+ scroll_amount_(2, -1),
+ num_scrolls_(0) {}
+
+ virtual void BeginTest() OVERRIDE {
+ layer_tree_host()->root_layer()->SetScrollable(true);
+ layer_tree_host()->root_layer()
+ ->SetMaxScrollOffset(gfx::Vector2d(100, 100));
+ layer_tree_host()->root_layer()->SetScrollOffset(initial_scroll_);
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void Layout() OVERRIDE {
+ Layer* root = layer_tree_host()->root_layer();
+ if (!layer_tree_host()->source_frame_number()) {
+ EXPECT_VECTOR_EQ(initial_scroll_, root->scroll_offset());
+ } else {
+ EXPECT_VECTOR_EQ(initial_scroll_ + scroll_amount_, root->scroll_offset());
+
+ // Pretend like Javascript updated the scroll position itself.
+ root->SetScrollOffset(second_scroll_);
+ }
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ LayerImpl* root = impl->active_tree()->root_layer();
+ EXPECT_VECTOR_EQ(gfx::Vector2d(), root->ScrollDelta());
+
+ root->SetScrollable(true);
+ root->SetMaxScrollOffset(gfx::Vector2d(100, 100));
+ root->ScrollBy(scroll_amount_);
+
+ switch (impl->active_tree()->source_frame_number()) {
+ case 0:
+ EXPECT_VECTOR_EQ(initial_scroll_, root->scroll_offset());
+ EXPECT_VECTOR_EQ(scroll_amount_, root->ScrollDelta());
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 1:
+ EXPECT_VECTOR_EQ(root->scroll_offset(), second_scroll_);
+ EXPECT_VECTOR_EQ(root->ScrollDelta(), scroll_amount_);
+ EndTest();
+ break;
+ }
+ }
+
+ virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta,
+ float scale) OVERRIDE {
+ num_scrolls_++;
+ }
+
+ virtual void AfterTest() OVERRIDE { EXPECT_EQ(1, num_scrolls_); }
+
+ private:
+ gfx::Vector2d initial_scroll_;
+ gfx::Vector2d second_scroll_;
+ gfx::Vector2d scroll_amount_;
+ int num_scrolls_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostScrollTestScrollSimple);
+
+class LayerTreeHostScrollTestScrollMultipleRedraw
+ : public LayerTreeHostScrollTest {
+ public:
+ LayerTreeHostScrollTestScrollMultipleRedraw()
+ : initial_scroll_(40, 10), scroll_amount_(-3, 17), num_scrolls_(0) {}
+
+ virtual void BeginTest() OVERRIDE {
+ layer_tree_host()->root_layer()->SetScrollable(true);
+ layer_tree_host()->root_layer()->SetScrollOffset(initial_scroll_);
+ layer_tree_host()->root_layer()->SetBounds(gfx::Size(200, 200));
+ layer_tree_host()->root_layer()
+ ->SetMaxScrollOffset(gfx::Vector2d(100, 100));
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void BeginCommitOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ Layer* root = layer_tree_host()->root_layer();
+ switch (layer_tree_host()->source_frame_number()) {
+ case 0:
+ EXPECT_VECTOR_EQ(root->scroll_offset(), initial_scroll_);
+ break;
+ case 1:
+ EXPECT_VECTOR_EQ(root->scroll_offset(),
+ initial_scroll_ + scroll_amount_ + scroll_amount_);
+ case 2:
+ EXPECT_VECTOR_EQ(root->scroll_offset(),
+ initial_scroll_ + scroll_amount_ + scroll_amount_);
+ break;
+ }
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ LayerImpl* root = impl->active_tree()->root_layer();
+ if (impl->active_tree()->source_frame_number() == 0 &&
+ impl->SourceAnimationFrameNumber() == 1) {
+ // First draw after first commit.
+ EXPECT_VECTOR_EQ(root->ScrollDelta(), gfx::Vector2d());
+ root->ScrollBy(scroll_amount_);
+ EXPECT_VECTOR_EQ(root->ScrollDelta(), scroll_amount_);
+
+ EXPECT_VECTOR_EQ(root->scroll_offset(), initial_scroll_);
+ PostSetNeedsRedrawToMainThread();
+ } else if (impl->active_tree()->source_frame_number() == 0 &&
+ impl->SourceAnimationFrameNumber() == 2) {
+ // Second draw after first commit.
+ EXPECT_EQ(root->ScrollDelta(), scroll_amount_);
+ root->ScrollBy(scroll_amount_);
+ EXPECT_VECTOR_EQ(root->ScrollDelta(), scroll_amount_ + scroll_amount_);
+
+ EXPECT_VECTOR_EQ(root->scroll_offset(), initial_scroll_);
+ PostSetNeedsCommitToMainThread();
+ } else if (impl->active_tree()->source_frame_number() == 1) {
+ // Third or later draw after second commit.
+ EXPECT_GE(impl->SourceAnimationFrameNumber(), 3);
+ EXPECT_VECTOR_EQ(root->ScrollDelta(), gfx::Vector2d());
+ EXPECT_VECTOR_EQ(root->scroll_offset(),
+ initial_scroll_ + scroll_amount_ + scroll_amount_);
+ EndTest();
+ }
+ }
+
+ virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta,
+ float scale) OVERRIDE {
+ num_scrolls_++;
+ }
+
+ virtual void AfterTest() OVERRIDE { EXPECT_EQ(1, num_scrolls_); }
+
+ private:
+ gfx::Vector2d initial_scroll_;
+ gfx::Vector2d scroll_amount_;
+ int num_scrolls_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostScrollTestScrollMultipleRedraw);
+
+class LayerTreeHostScrollTestScrollAbortedCommit
+ : public LayerTreeHostScrollTest {
+ public:
+ LayerTreeHostScrollTestScrollAbortedCommit()
+ : initial_scroll_(50, 60),
+ impl_scroll_(-3, 2),
+ second_main_scroll_(14, -3),
+ impl_scale_(2.f),
+ num_will_begin_frames_(0),
+ num_did_begin_frames_(0),
+ num_will_commits_(0),
+ num_did_commits_(0),
+ num_impl_commits_(0),
+ num_impl_scrolls_(0) {}
+
+ virtual void BeginTest() OVERRIDE { PostSetNeedsCommitToMainThread(); }
+
+ virtual void SetupTree() OVERRIDE {
+ LayerTreeHostScrollTest::SetupTree();
+ scoped_refptr<Layer> root_scroll_layer = Layer::Create();
+ root_scroll_layer->SetScrollable(true);
+ root_scroll_layer->SetScrollOffset(initial_scroll_);
+ root_scroll_layer->SetBounds(gfx::Size(200, 200));
+ root_scroll_layer->SetMaxScrollOffset(gfx::Vector2d(100, 100));
+ root_scroll_layer->SetIsDrawable(true);
+ layer_tree_host()->root_layer()->AddChild(root_scroll_layer);
+
+ layer_tree_host()->SetPageScaleFactorAndLimits(1.f, 0.01f, 100.f);
+ }
+
+ virtual void WillBeginFrame() OVERRIDE {
+ num_will_begin_frames_++;
+ Layer* root_scroll_layer = layer_tree_host()->root_layer()->children()[0];
+ switch (num_will_begin_frames_) {
+ case 1:
+ // This will not be aborted because of the initial prop changes.
+ EXPECT_EQ(0, num_impl_scrolls_);
+ EXPECT_EQ(0, layer_tree_host()->source_frame_number());
+ EXPECT_VECTOR_EQ(root_scroll_layer->scroll_offset(), initial_scroll_);
+ EXPECT_EQ(1.f, layer_tree_host()->page_scale_factor());
+ break;
+ case 2:
+ // This commit will be aborted, and another commit will be
+ // initiated from the redraw.
+ EXPECT_EQ(1, num_impl_scrolls_);
+ EXPECT_EQ(1, layer_tree_host()->source_frame_number());
+ EXPECT_VECTOR_EQ(root_scroll_layer->scroll_offset(),
+ initial_scroll_ + impl_scroll_);
+ EXPECT_EQ(impl_scale_, layer_tree_host()->page_scale_factor());
+ PostSetNeedsRedrawToMainThread();
+ break;
+ case 3:
+ // This commit will not be aborted because of the scroll change.
+ EXPECT_EQ(2, num_impl_scrolls_);
+ EXPECT_EQ(1, layer_tree_host()->source_frame_number());
+ EXPECT_VECTOR_EQ(root_scroll_layer->scroll_offset(),
+ initial_scroll_ + impl_scroll_ + impl_scroll_);
+ EXPECT_EQ(impl_scale_ * impl_scale_,
+ layer_tree_host()->page_scale_factor());
+ root_scroll_layer->SetScrollOffset(root_scroll_layer->scroll_offset() +
+ second_main_scroll_);
+ break;
+ case 4:
+ // This commit will also be aborted.
+ EXPECT_EQ(3, num_impl_scrolls_);
+ EXPECT_EQ(2, layer_tree_host()->source_frame_number());
+ EXPECT_VECTOR_EQ(root_scroll_layer->scroll_offset(),
+ initial_scroll_ + impl_scroll_ + impl_scroll_ +
+ impl_scroll_ + second_main_scroll_);
+ // End the test by drawing to verify this commit is also aborted.
+ PostSetNeedsRedrawToMainThread();
+ break;
+ }
+ }
+
+ virtual void DidBeginFrame() OVERRIDE { num_did_begin_frames_++; }
+
+ virtual void WillCommit() OVERRIDE { num_will_commits_++; }
+
+ virtual void DidCommit() OVERRIDE { num_did_commits_++; }
+
+ virtual void BeginCommitOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ num_impl_commits_++;
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ LayerImpl* root_scroll_layer =
+ impl->active_tree()->root_layer()->children()[0];
+
+ if (impl->active_tree()->source_frame_number() == 0 &&
+ impl->SourceAnimationFrameNumber() == 1) {
+ // First draw
+ EXPECT_VECTOR_EQ(root_scroll_layer->ScrollDelta(), gfx::Vector2d());
+ root_scroll_layer->ScrollBy(impl_scroll_);
+ EXPECT_VECTOR_EQ(root_scroll_layer->ScrollDelta(), impl_scroll_);
+ EXPECT_VECTOR_EQ(root_scroll_layer->scroll_offset(), initial_scroll_);
+
+ EXPECT_EQ(1.f, impl->active_tree()->page_scale_delta());
+ EXPECT_EQ(1.f, impl->active_tree()->total_page_scale_factor());
+ impl->active_tree()->SetPageScaleDelta(impl_scale_);
+ EXPECT_EQ(impl_scale_, impl->active_tree()->page_scale_delta());
+ EXPECT_EQ(impl_scale_, impl->active_tree()->total_page_scale_factor());
+
+ // To simplify the testing flow, don't redraw here, just commit.
+ impl->SetNeedsCommit();
+ } else if (impl->active_tree()->source_frame_number() == 0 &&
+ impl->SourceAnimationFrameNumber() == 2) {
+ // Test a second draw after an aborted commit.
+ // The scroll/scale values should be baked into the offset/scale factor
+ // since the main thread consumed but aborted the begin frame.
+ EXPECT_VECTOR_EQ(root_scroll_layer->ScrollDelta(), gfx::Vector2d());
+ root_scroll_layer->ScrollBy(impl_scroll_);
+ EXPECT_VECTOR_EQ(root_scroll_layer->ScrollDelta(), impl_scroll_);
+ EXPECT_VECTOR_EQ(root_scroll_layer->scroll_offset(),
+ initial_scroll_ + impl_scroll_);
+
+ EXPECT_EQ(1.f, impl->active_tree()->page_scale_delta());
+ EXPECT_EQ(impl_scale_, impl->active_tree()->total_page_scale_factor());
+ impl->active_tree()->SetPageScaleDelta(impl_scale_);
+ EXPECT_EQ(impl_scale_, impl->active_tree()->page_scale_delta());
+ EXPECT_EQ(impl_scale_ * impl_scale_,
+ impl->active_tree()->total_page_scale_factor());
+
+ impl->SetNeedsCommit();
+ } else if (impl->active_tree()->source_frame_number() == 1 &&
+ impl->SourceAnimationFrameNumber() == 3) {
+ // Third draw after the second full commit.
+ EXPECT_EQ(root_scroll_layer->ScrollDelta(), gfx::Vector2d());
+ root_scroll_layer->ScrollBy(impl_scroll_);
+ impl->SetNeedsCommit();
+ EXPECT_VECTOR_EQ(root_scroll_layer->ScrollDelta(), impl_scroll_);
+ EXPECT_VECTOR_EQ(
+ root_scroll_layer->scroll_offset(),
+ initial_scroll_ + impl_scroll_ + impl_scroll_ + second_main_scroll_);
+ } else if (impl->active_tree()->source_frame_number() == 1 &&
+ impl->SourceAnimationFrameNumber() == 4) {
+ // Final draw after the second aborted commit.
+ EXPECT_VECTOR_EQ(root_scroll_layer->ScrollDelta(), gfx::Vector2d());
+ EXPECT_VECTOR_EQ(root_scroll_layer->scroll_offset(),
+ initial_scroll_ + impl_scroll_ + impl_scroll_ +
+ impl_scroll_ + second_main_scroll_);
+ EndTest();
+ }
+ }
+
+ virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta,
+ float scale) OVERRIDE {
+ num_impl_scrolls_++;
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ EXPECT_EQ(3, num_impl_scrolls_);
+ // Verify that the embedder sees aborted commits as real commits.
+ EXPECT_EQ(4, num_will_begin_frames_);
+ EXPECT_EQ(4, num_did_begin_frames_);
+ EXPECT_EQ(4, num_will_commits_);
+ EXPECT_EQ(4, num_did_commits_);
+ // ...but the compositor thread only sees two real ones.
+ EXPECT_EQ(2, num_impl_commits_);
+ }
+
+ private:
+ gfx::Vector2d initial_scroll_;
+ gfx::Vector2d impl_scroll_;
+ gfx::Vector2d second_main_scroll_;
+ float impl_scale_;
+ int num_will_begin_frames_;
+ int num_did_begin_frames_;
+ int num_will_commits_;
+ int num_did_commits_;
+ int num_impl_commits_;
+ int num_impl_scrolls_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostScrollTestScrollAbortedCommit);
+
+class LayerTreeHostScrollTestFractionalScroll : public LayerTreeHostScrollTest {
+ public:
+ LayerTreeHostScrollTestFractionalScroll() : scroll_amount_(1.75, 0) {}
+
+ virtual void BeginTest() OVERRIDE {
+ layer_tree_host()->root_layer()->SetScrollable(true);
+ layer_tree_host()->root_layer()
+ ->SetMaxScrollOffset(gfx::Vector2d(100, 100));
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ LayerImpl* root = impl->active_tree()->root_layer();
+
+ // Check that a fractional scroll delta is correctly accumulated over
+ // multiple commits.
+ switch (impl->active_tree()->source_frame_number()) {
+ case 0:
+ EXPECT_VECTOR_EQ(root->scroll_offset(), gfx::Vector2d(0, 0));
+ EXPECT_VECTOR_EQ(root->ScrollDelta(), gfx::Vector2d(0, 0));
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 1:
+ EXPECT_VECTOR_EQ(root->scroll_offset(),
+ gfx::ToFlooredVector2d(scroll_amount_));
+ EXPECT_VECTOR_EQ(root->ScrollDelta(),
+ gfx::Vector2dF(fmod(scroll_amount_.x(), 1.0f), 0.0f));
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 2:
+ EXPECT_VECTOR_EQ(
+ root->scroll_offset(),
+ gfx::ToFlooredVector2d(scroll_amount_ + scroll_amount_));
+ EXPECT_VECTOR_EQ(
+ root->ScrollDelta(),
+ gfx::Vector2dF(fmod(2.0f * scroll_amount_.x(), 1.0f), 0.0f));
+ EndTest();
+ break;
+ }
+ root->ScrollBy(scroll_amount_);
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ private:
+ gfx::Vector2dF scroll_amount_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostScrollTestFractionalScroll);
+
+class LayerTreeHostScrollTestCaseWithChild : public LayerTreeHostScrollTest {
+ public:
+ LayerTreeHostScrollTestCaseWithChild()
+ : initial_offset_(10, 20),
+ javascript_scroll_(40, 5),
+ scroll_amount_(2, -1),
+ num_scrolls_(0) {}
+
+ virtual void SetupTree() OVERRIDE {
+ layer_tree_host()->SetDeviceScaleFactor(device_scale_factor_);
+
+ scoped_refptr<Layer> root_layer = Layer::Create();
+ root_layer->SetBounds(gfx::Size(10, 10));
+
+ root_scroll_layer_ = ContentLayer::Create(&fake_content_layer_client_);
+ root_scroll_layer_->SetBounds(gfx::Size(110, 110));
+
+ root_scroll_layer_->SetPosition(gfx::Point());
+ root_scroll_layer_->SetAnchorPoint(gfx::PointF());
+
+ root_scroll_layer_->SetIsDrawable(true);
+ root_scroll_layer_->SetScrollable(true);
+ root_scroll_layer_->SetMaxScrollOffset(gfx::Vector2d(100, 100));
+ root_layer->AddChild(root_scroll_layer_);
+
+ child_layer_ = ContentLayer::Create(&fake_content_layer_client_);
+ child_layer_->set_did_scroll_callback(
+ base::Bind(&LayerTreeHostScrollTestCaseWithChild::DidScroll,
+ base::Unretained(this)));
+ child_layer_->SetBounds(gfx::Size(110, 110));
+
+ if (scroll_child_layer_) {
+ // Scrolls on the child layer will happen at 5, 5. If they are treated
+ // like device pixels, and device scale factor is 2, then they will
+ // be considered at 2.5, 2.5 in logical pixels, and will miss this layer.
+ child_layer_->SetPosition(gfx::Point(5, 5));
+ } else {
+ // Adjust the child layer horizontally so that scrolls will never hit it.
+ child_layer_->SetPosition(gfx::Point(60, 5));
+ }
+ child_layer_->SetAnchorPoint(gfx::PointF());
+
+ child_layer_->SetIsDrawable(true);
+ child_layer_->SetScrollable(true);
+ child_layer_->SetMaxScrollOffset(gfx::Vector2d(100, 100));
+ root_scroll_layer_->AddChild(child_layer_);
+
+ if (scroll_child_layer_) {
+ expected_scroll_layer_ = child_layer_;
+ expected_no_scroll_layer_ = root_scroll_layer_;
+ } else {
+ expected_scroll_layer_ = root_scroll_layer_;
+ expected_no_scroll_layer_ = child_layer_;
+ }
+
+ expected_scroll_layer_->SetScrollOffset(initial_offset_);
+
+ layer_tree_host()->SetRootLayer(root_layer);
+ LayerTreeHostScrollTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE { PostSetNeedsCommitToMainThread(); }
+
+ virtual void WillCommit() OVERRIDE {
+ // Keep the test committing (otherwise the early out for no update
+ // will stall the test).
+ if (layer_tree_host()->source_frame_number() < 2) {
+ layer_tree_host()->SetNeedsCommit();
+ }
+ }
+
+ void DidScroll() {
+ final_scroll_offset_ = expected_scroll_layer_->scroll_offset();
+ }
+
+ virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta,
+ float scale) OVERRIDE {
+ num_scrolls_++;
+ }
+
+ virtual void Layout() OVERRIDE {
+ EXPECT_VECTOR_EQ(gfx::Vector2d(),
+ expected_no_scroll_layer_->scroll_offset());
+
+ switch (layer_tree_host()->source_frame_number()) {
+ case 0:
+ EXPECT_VECTOR_EQ(initial_offset_,
+ expected_scroll_layer_->scroll_offset());
+ break;
+ case 1:
+ EXPECT_VECTOR_EQ(initial_offset_ + scroll_amount_,
+ expected_scroll_layer_->scroll_offset());
+
+ // Pretend like Javascript updated the scroll position itself.
+ expected_scroll_layer_->SetScrollOffset(javascript_scroll_);
+ break;
+ case 2:
+ EXPECT_VECTOR_EQ(javascript_scroll_ + scroll_amount_,
+ expected_scroll_layer_->scroll_offset());
+ break;
+ }
+ }
+
+ virtual void DidActivateTreeOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ LayerImpl* root_impl = impl->active_tree()->root_layer();
+ LayerImpl* root_scroll_layer_impl = root_impl->children()[0];
+ LayerImpl* child_layer_impl = root_scroll_layer_impl->children()[0];
+
+ LayerImpl* expected_scroll_layer_impl = NULL;
+ LayerImpl* expected_no_scroll_layer_impl = NULL;
+ if (scroll_child_layer_) {
+ expected_scroll_layer_impl = child_layer_impl;
+ expected_no_scroll_layer_impl = root_scroll_layer_impl;
+ } else {
+ expected_scroll_layer_impl = root_scroll_layer_impl;
+ expected_no_scroll_layer_impl = child_layer_impl;
+ }
+
+ EXPECT_VECTOR_EQ(gfx::Vector2d(), root_impl->ScrollDelta());
+ EXPECT_VECTOR_EQ(gfx::Vector2d(),
+ expected_no_scroll_layer_impl->ScrollDelta());
+
+ // Ensure device scale factor is affecting the layers.
+ gfx::Size expected_content_bounds = gfx::ToCeiledSize(
+ gfx::ScaleSize(root_scroll_layer_impl->bounds(), device_scale_factor_));
+ EXPECT_SIZE_EQ(expected_content_bounds,
+ root_scroll_layer_->content_bounds());
+
+ expected_content_bounds = gfx::ToCeiledSize(
+ gfx::ScaleSize(child_layer_impl->bounds(), device_scale_factor_));
+ EXPECT_SIZE_EQ(expected_content_bounds, child_layer_->content_bounds());
+
+ switch (impl->active_tree()->source_frame_number()) {
+ case 0: {
+ // Gesture scroll on impl thread.
+ InputHandler::ScrollStatus status = impl->ScrollBegin(
+ gfx::ToCeiledPoint(expected_scroll_layer_impl->position() -
+ gfx::Vector2dF(0.5f, 0.5f)),
+ InputHandler::Gesture);
+ EXPECT_EQ(InputHandler::ScrollStarted, status);
+ impl->ScrollBy(gfx::Point(), scroll_amount_);
+ impl->ScrollEnd();
+
+ // Check the scroll is applied as a delta.
+ EXPECT_VECTOR_EQ(initial_offset_,
+ expected_scroll_layer_impl->scroll_offset());
+ EXPECT_VECTOR_EQ(scroll_amount_,
+ expected_scroll_layer_impl->ScrollDelta());
+ break;
+ }
+ case 1: {
+ // Wheel scroll on impl thread.
+ InputHandler::ScrollStatus status = impl->ScrollBegin(
+ gfx::ToCeiledPoint(expected_scroll_layer_impl->position() +
+ gfx::Vector2dF(0.5f, 0.5f)),
+ InputHandler::Wheel);
+ EXPECT_EQ(InputHandler::ScrollStarted, status);
+ impl->ScrollBy(gfx::Point(), scroll_amount_);
+ impl->ScrollEnd();
+
+ // Check the scroll is applied as a delta.
+ EXPECT_VECTOR_EQ(javascript_scroll_,
+ expected_scroll_layer_impl->scroll_offset());
+ EXPECT_VECTOR_EQ(scroll_amount_,
+ expected_scroll_layer_impl->ScrollDelta());
+ break;
+ }
+ case 2:
+
+ EXPECT_VECTOR_EQ(javascript_scroll_ + scroll_amount_,
+ expected_scroll_layer_impl->scroll_offset());
+ EXPECT_VECTOR_EQ(gfx::Vector2d(),
+ expected_scroll_layer_impl->ScrollDelta());
+
+ EndTest();
+ break;
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ if (scroll_child_layer_) {
+ EXPECT_EQ(0, num_scrolls_);
+ EXPECT_VECTOR_EQ(javascript_scroll_ + scroll_amount_,
+ final_scroll_offset_);
+ } else {
+ EXPECT_EQ(2, num_scrolls_);
+ EXPECT_VECTOR_EQ(gfx::Vector2d(), final_scroll_offset_);
+ }
+ }
+
+ protected:
+ float device_scale_factor_;
+ bool scroll_child_layer_;
+
+ gfx::Vector2d initial_offset_;
+ gfx::Vector2d javascript_scroll_;
+ gfx::Vector2d scroll_amount_;
+ int num_scrolls_;
+ gfx::Vector2d final_scroll_offset_;
+
+ FakeContentLayerClient fake_content_layer_client_;
+
+ scoped_refptr<Layer> root_scroll_layer_;
+ scoped_refptr<Layer> child_layer_;
+ scoped_refptr<Layer> expected_scroll_layer_;
+ scoped_refptr<Layer> expected_no_scroll_layer_;
+};
+
+TEST_F(LayerTreeHostScrollTestCaseWithChild,
+ DeviceScaleFactor1_ScrollChild_DirectRenderer_MainThreadPaint) {
+ device_scale_factor_ = 1.f;
+ scroll_child_layer_ = true;
+ RunTest(true, false, false);
+}
+
+TEST_F(LayerTreeHostScrollTestCaseWithChild,
+ DeviceScaleFactor1_ScrollChild_DirectRenderer_ImplSidePaint) {
+ device_scale_factor_ = 1.f;
+ scroll_child_layer_ = true;
+ RunTest(true, false, true);
+}
+
+TEST_F(LayerTreeHostScrollTestCaseWithChild,
+ DeviceScaleFactor1_ScrollChild_DelegatingRenderer_MainThreadPaint) {
+ device_scale_factor_ = 1.f;
+ scroll_child_layer_ = true;
+ RunTest(true, true, false);
+}
+
+TEST_F(LayerTreeHostScrollTestCaseWithChild,
+ DeviceScaleFactor1_ScrollChild_DelegatingRenderer_ImplSidePaint) {
+ device_scale_factor_ = 1.f;
+ scroll_child_layer_ = true;
+ RunTest(true, true, true);
+}
+
+TEST_F(LayerTreeHostScrollTestCaseWithChild,
+ DeviceScaleFactor15_ScrollChild_DirectRenderer) {
+ device_scale_factor_ = 1.5f;
+ scroll_child_layer_ = true;
+ RunTest(true, false, true);
+}
+
+TEST_F(LayerTreeHostScrollTestCaseWithChild,
+ DeviceScaleFactor15_ScrollChild_DelegatingRenderer) {
+ device_scale_factor_ = 1.5f;
+ scroll_child_layer_ = true;
+ RunTest(true, true, true);
+}
+
+TEST_F(LayerTreeHostScrollTestCaseWithChild,
+ DeviceScaleFactor2_ScrollChild_DirectRenderer) {
+ device_scale_factor_ = 2.f;
+ scroll_child_layer_ = true;
+ RunTest(true, false, true);
+}
+
+TEST_F(LayerTreeHostScrollTestCaseWithChild,
+ DeviceScaleFactor2_ScrollChild_DelegatingRenderer) {
+ device_scale_factor_ = 2.f;
+ scroll_child_layer_ = true;
+ RunTest(true, true, true);
+}
+
+TEST_F(LayerTreeHostScrollTestCaseWithChild,
+ DeviceScaleFactor1_ScrollRootScrollLayer_DirectRenderer) {
+ device_scale_factor_ = 1.f;
+ scroll_child_layer_ = false;
+ RunTest(true, false, true);
+}
+
+TEST_F(LayerTreeHostScrollTestCaseWithChild,
+ DeviceScaleFactor1_ScrollRootScrollLayer_DelegatingRenderer) {
+ device_scale_factor_ = 1.f;
+ scroll_child_layer_ = false;
+ RunTest(true, true, true);
+}
+
+TEST_F(LayerTreeHostScrollTestCaseWithChild,
+ DeviceScaleFactor15_ScrollRootScrollLayer_DirectRenderer) {
+ device_scale_factor_ = 1.5f;
+ scroll_child_layer_ = false;
+ RunTest(true, false, true);
+}
+
+TEST_F(LayerTreeHostScrollTestCaseWithChild,
+ DeviceScaleFactor15_ScrollRootScrollLayer_DelegatingRenderer) {
+ device_scale_factor_ = 1.5f;
+ scroll_child_layer_ = false;
+ RunTest(true, true, true);
+}
+
+TEST_F(LayerTreeHostScrollTestCaseWithChild,
+ DeviceScaleFactor2_ScrollRootScrollLayer_DirectRenderer_MainSidePaint) {
+ device_scale_factor_ = 2.f;
+ scroll_child_layer_ = false;
+ RunTest(true, false, false);
+}
+
+TEST_F(LayerTreeHostScrollTestCaseWithChild,
+ DeviceScaleFactor2_ScrollRootScrollLayer_DirectRenderer_ImplSidePaint) {
+ device_scale_factor_ = 2.f;
+ scroll_child_layer_ = false;
+ RunTest(true, false, true);
+}
+
+TEST_F(LayerTreeHostScrollTestCaseWithChild,
+ DeviceScaleFactor2_ScrollRootScrollLayer_DelegatingRenderer) {
+ device_scale_factor_ = 2.f;
+ scroll_child_layer_ = false;
+ RunTest(true, true, true);
+}
+
+class ImplSidePaintingScrollTest : public LayerTreeHostScrollTest {
+ public:
+ virtual void InitializeSettings(LayerTreeSettings* settings) OVERRIDE {
+ settings->impl_side_painting = true;
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ if (impl->pending_tree())
+ impl->SetNeedsRedraw();
+ }
+};
+
+class ImplSidePaintingScrollTestSimple : public ImplSidePaintingScrollTest {
+ public:
+ ImplSidePaintingScrollTestSimple()
+ : initial_scroll_(10, 20),
+ main_thread_scroll_(40, 5),
+ impl_thread_scroll1_(2, -1),
+ impl_thread_scroll2_(-3, 10),
+ num_scrolls_(0),
+ can_activate_(true) {}
+
+ virtual void BeginTest() OVERRIDE {
+ layer_tree_host()->root_layer()->SetScrollable(true);
+ layer_tree_host()->root_layer()
+ ->SetMaxScrollOffset(gfx::Vector2d(100, 100));
+ layer_tree_host()->root_layer()->SetScrollOffset(initial_scroll_);
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void Layout() OVERRIDE {
+ Layer* root = layer_tree_host()->root_layer();
+ if (!layer_tree_host()->source_frame_number()) {
+ EXPECT_VECTOR_EQ(root->scroll_offset(), initial_scroll_);
+ } else {
+ EXPECT_VECTOR_EQ(root->scroll_offset(),
+ initial_scroll_ + impl_thread_scroll1_);
+
+ // Pretend like Javascript updated the scroll position itself with a
+ // change of main_thread_scroll.
+ root->SetScrollOffset(initial_scroll_ + main_thread_scroll_ +
+ impl_thread_scroll1_);
+ }
+ }
+
+ virtual bool CanActivatePendingTree(LayerTreeHostImpl* impl) OVERRIDE {
+ return can_activate_;
+ }
+
+ virtual bool CanActivatePendingTreeIfNeeded(LayerTreeHostImpl* impl)
+ OVERRIDE {
+ return can_activate_;
+ }
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ // We force a second draw here of the first commit before activating
+ // the second commit.
+ if (impl->active_tree()->source_frame_number() == 0)
+ impl->SetNeedsRedraw();
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ ImplSidePaintingScrollTest::DrawLayersOnThread(impl);
+
+ LayerImpl* root = impl->active_tree()->root_layer();
+ LayerImpl* pending_root =
+ impl->active_tree()->FindPendingTreeLayerById(root->id());
+
+ switch (impl->active_tree()->source_frame_number()) {
+ case 0:
+ if (!impl->pending_tree()) {
+ can_activate_ = false;
+ EXPECT_VECTOR_EQ(root->ScrollDelta(), gfx::Vector2d());
+ root->ScrollBy(impl_thread_scroll1_);
+
+ EXPECT_VECTOR_EQ(root->scroll_offset(), initial_scroll_);
+ EXPECT_VECTOR_EQ(root->ScrollDelta(), impl_thread_scroll1_);
+ EXPECT_VECTOR_EQ(root->sent_scroll_delta(), gfx::Vector2d());
+ PostSetNeedsCommitToMainThread();
+
+ // CommitCompleteOnThread will trigger this function again
+ // and cause us to take the else clause.
+ } else {
+ can_activate_ = true;
+ ASSERT_TRUE(pending_root);
+ EXPECT_EQ(impl->pending_tree()->source_frame_number(), 1);
+
+ root->ScrollBy(impl_thread_scroll2_);
+ EXPECT_VECTOR_EQ(root->scroll_offset(), initial_scroll_);
+ EXPECT_VECTOR_EQ(root->ScrollDelta(),
+ impl_thread_scroll1_ + impl_thread_scroll2_);
+ EXPECT_VECTOR_EQ(root->sent_scroll_delta(), impl_thread_scroll1_);
+
+ EXPECT_VECTOR_EQ(
+ pending_root->scroll_offset(),
+ initial_scroll_ + main_thread_scroll_ + impl_thread_scroll1_);
+ EXPECT_VECTOR_EQ(pending_root->ScrollDelta(), impl_thread_scroll2_);
+ EXPECT_VECTOR_EQ(pending_root->sent_scroll_delta(), gfx::Vector2d());
+ }
+ break;
+ case 1:
+ EXPECT_FALSE(impl->pending_tree());
+ EXPECT_VECTOR_EQ(
+ root->scroll_offset(),
+ initial_scroll_ + main_thread_scroll_ + impl_thread_scroll1_);
+ EXPECT_VECTOR_EQ(root->ScrollDelta(), impl_thread_scroll2_);
+ EXPECT_VECTOR_EQ(root->sent_scroll_delta(), gfx::Vector2d());
+ EndTest();
+ break;
+ }
+ }
+
+ virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta,
+ float scale) OVERRIDE {
+ num_scrolls_++;
+ }
+
+ virtual void AfterTest() OVERRIDE { EXPECT_EQ(1, num_scrolls_); }
+
+ private:
+ gfx::Vector2d initial_scroll_;
+ gfx::Vector2d main_thread_scroll_;
+ gfx::Vector2d impl_thread_scroll1_;
+ gfx::Vector2d impl_thread_scroll2_;
+ int num_scrolls_;
+ bool can_activate_;
+};
+
+MULTI_THREAD_TEST_F(ImplSidePaintingScrollTestSimple);
+
+class LayerTreeHostScrollTestScrollZeroMaxScrollOffset
+ : public LayerTreeHostScrollTest {
+ public:
+ LayerTreeHostScrollTestScrollZeroMaxScrollOffset() {}
+
+ virtual void BeginTest() OVERRIDE { PostSetNeedsCommitToMainThread(); }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ LayerImpl* root = impl->active_tree()->root_layer();
+ root->SetScrollable(true);
+
+ root->SetMaxScrollOffset(gfx::Vector2d(100, 100));
+ EXPECT_EQ(InputHandler::ScrollStarted,
+ root->TryScroll(gfx::PointF(0.0f, 1.0f), InputHandler::Gesture));
+
+ root->SetMaxScrollOffset(gfx::Vector2d(0, 0));
+ EXPECT_EQ(InputHandler::ScrollIgnored,
+ root->TryScroll(gfx::PointF(0.0f, 1.0f), InputHandler::Gesture));
+
+ root->SetMaxScrollOffset(gfx::Vector2d(-100, -100));
+ EXPECT_EQ(InputHandler::ScrollIgnored,
+ root->TryScroll(gfx::PointF(0.0f, 1.0f), InputHandler::Gesture));
+
+ EndTest();
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+ LayerTreeHostScrollTestScrollZeroMaxScrollOffset);
+
+class ThreadCheckingInputHandlerClient : public InputHandlerClient {
+ public:
+ ThreadCheckingInputHandlerClient(base::SingleThreadTaskRunner* runner,
+ bool* received_stop_flinging)
+ : task_runner_(runner), received_stop_flinging_(received_stop_flinging) {}
+
+ virtual void WillShutdown() OVERRIDE {
+ if (!received_stop_flinging_)
+ ADD_FAILURE() << "WillShutdown() called before fling stopped";
+ }
+
+ virtual void Animate(base::TimeTicks time) OVERRIDE {
+ if (!task_runner_->BelongsToCurrentThread())
+ ADD_FAILURE() << "Animate called on wrong thread";
+ }
+
+ virtual void MainThreadHasStoppedFlinging() OVERRIDE {
+ if (!task_runner_->BelongsToCurrentThread())
+ ADD_FAILURE() << "MainThreadHasStoppedFlinging called on wrong thread";
+ *received_stop_flinging_ = true;
+ }
+
+ virtual void DidOverscroll(const DidOverscrollParams& params) OVERRIDE {
+ if (!task_runner_->BelongsToCurrentThread())
+ ADD_FAILURE() << "DidOverscroll called on wrong thread";
+ }
+
+ private:
+ base::SingleThreadTaskRunner* task_runner_;
+ bool* received_stop_flinging_;
+};
+
+void BindInputHandlerOnCompositorThread(
+ const base::WeakPtr<InputHandler>& input_handler,
+ ThreadCheckingInputHandlerClient* client) {
+ input_handler->BindToClient(client);
+}
+
+TEST(LayerTreeHostFlingTest, DidStopFlingingThread) {
+ base::Thread impl_thread("cc");
+ ASSERT_TRUE(impl_thread.Start());
+
+ bool received_stop_flinging = false;
+ LayerTreeSettings settings;
+
+ ThreadCheckingInputHandlerClient input_handler_client(
+ impl_thread.message_loop_proxy().get(), &received_stop_flinging);
+ FakeLayerTreeHostClient client(FakeLayerTreeHostClient::DIRECT_3D);
+
+ ASSERT_TRUE(impl_thread.message_loop_proxy().get());
+ scoped_ptr<LayerTreeHost> layer_tree_host = LayerTreeHost::Create(
+ &client, settings, impl_thread.message_loop_proxy());
+
+ impl_thread.message_loop_proxy()
+ ->PostTask(FROM_HERE,
+ base::Bind(&BindInputHandlerOnCompositorThread,
+ layer_tree_host->GetInputHandler(),
+ base::Unretained(&input_handler_client)));
+
+ layer_tree_host->DidStopFlinging();
+ layer_tree_host.reset();
+ impl_thread.Stop();
+ EXPECT_TRUE(received_stop_flinging);
+}
+
+class LayerTreeHostScrollTestLayerStructureChange
+ : public LayerTreeHostScrollTest {
+ public:
+ LayerTreeHostScrollTestLayerStructureChange()
+ : scroll_destroy_whole_tree_(false) {}
+
+ virtual void SetupTree() OVERRIDE {
+ scoped_refptr<Layer> root_layer = Layer::Create();
+ root_layer->SetBounds(gfx::Size(10, 10));
+
+ Layer* root_scroll_layer =
+ CreateScrollLayer(root_layer.get(), &root_scroll_layer_client_);
+ CreateScrollLayer(root_layer.get(), &sibling_scroll_layer_client_);
+ CreateScrollLayer(root_scroll_layer, &child_scroll_layer_client_);
+
+ layer_tree_host()->SetRootLayer(root_layer);
+ LayerTreeHostScrollTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+ LayerImpl* root = impl->active_tree()->root_layer();
+ switch (impl->active_tree()->source_frame_number()) {
+ case 0:
+ root->child_at(0)->SetScrollDelta(gfx::Vector2dF(5, 5));
+ root->child_at(0)->child_at(0)->SetScrollDelta(gfx::Vector2dF(5, 5));
+ root->child_at(1)->SetScrollDelta(gfx::Vector2dF(5, 5));
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 1:
+ EndTest();
+ break;
+ }
+ }
+
+ virtual void AfterTest() OVERRIDE {}
+
+ virtual void DidScroll(Layer* layer) {
+ if (scroll_destroy_whole_tree_) {
+ layer_tree_host()->SetRootLayer(NULL);
+ EndTest();
+ return;
+ }
+ layer->RemoveFromParent();
+ }
+
+ protected:
+ class FakeLayerScrollClient {
+ public:
+ void DidScroll() {
+ owner_->DidScroll(layer_);
+ }
+ LayerTreeHostScrollTestLayerStructureChange* owner_;
+ Layer* layer_;
+ };
+
+ Layer* CreateScrollLayer(Layer* parent, FakeLayerScrollClient* client) {
+ scoped_refptr<Layer> scroll_layer =
+ ContentLayer::Create(&fake_content_layer_client_);
+ scroll_layer->SetBounds(gfx::Size(110, 110));
+ scroll_layer->SetPosition(gfx::Point(0, 0));
+ scroll_layer->SetAnchorPoint(gfx::PointF());
+ scroll_layer->SetIsDrawable(true);
+ scroll_layer->SetScrollable(true);
+ scroll_layer->SetMaxScrollOffset(gfx::Vector2d(100, 100));
+ scroll_layer->set_did_scroll_callback(base::Bind(
+ &FakeLayerScrollClient::DidScroll, base::Unretained(client)));
+ client->owner_ = this;
+ client->layer_ = scroll_layer.get();
+ parent->AddChild(scroll_layer);
+ return scroll_layer.get();
+ }
+
+ FakeLayerScrollClient root_scroll_layer_client_;
+ FakeLayerScrollClient sibling_scroll_layer_client_;
+ FakeLayerScrollClient child_scroll_layer_client_;
+
+ FakeContentLayerClient fake_content_layer_client_;
+
+ bool scroll_destroy_whole_tree_;
+};
+
+TEST_F(LayerTreeHostScrollTestLayerStructureChange, ScrollDestroyLayer) {
+ RunTest(true, false, false);
+}
+
+TEST_F(LayerTreeHostScrollTestLayerStructureChange, ScrollDestroyWholeTree) {
+ scroll_destroy_whole_tree_ = true;
+ RunTest(true, false, false);
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/trees/layer_tree_host_unittest_video.cc b/chromium/cc/trees/layer_tree_host_unittest_video.cc
new file mode 100644
index 00000000000..5413a602d4b
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_host_unittest_video.cc
@@ -0,0 +1,98 @@
+// 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 "cc/trees/layer_tree_host.h"
+
+#include "base/basictypes.h"
+#include "cc/layers/render_surface_impl.h"
+#include "cc/layers/video_layer.h"
+#include "cc/layers/video_layer_impl.h"
+#include "cc/test/fake_video_frame_provider.h"
+#include "cc/test/layer_tree_test.h"
+#include "cc/trees/damage_tracker.h"
+#include "cc/trees/layer_tree_impl.h"
+
+namespace cc {
+namespace {
+
+// These tests deal with compositing video.
+class LayerTreeHostVideoTest : public LayerTreeTest {};
+
+class LayerTreeHostVideoTestSetNeedsDisplay
+ : public LayerTreeHostVideoTest {
+ public:
+ virtual void SetupTree() OVERRIDE {
+ scoped_refptr<Layer> root = Layer::Create();
+ root->SetBounds(gfx::Size(10, 10));
+ root->SetAnchorPoint(gfx::PointF());
+ root->SetIsDrawable(true);
+
+ scoped_refptr<VideoLayer> video = VideoLayer::Create(
+ &video_frame_provider_);
+ video->SetPosition(gfx::PointF(3.f, 3.f));
+ video->SetBounds(gfx::Size(4, 4));
+ video->SetAnchorPoint(gfx::PointF());
+ video->SetIsDrawable(true);
+ root->AddChild(video);
+
+ layer_tree_host()->SetRootLayer(root);
+ layer_tree_host()->SetDeviceScaleFactor(2.f);
+ LayerTreeHostVideoTest::SetupTree();
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ num_draws_ = 0;
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual bool PrepareToDrawOnThread(LayerTreeHostImpl* host_impl,
+ LayerTreeHostImpl::FrameData* frame,
+ bool result) OVERRIDE {
+ LayerImpl* root_layer = host_impl->active_tree()->root_layer();
+ RenderSurfaceImpl* root_surface = root_layer->render_surface();
+ gfx::RectF damage_rect =
+ root_surface->damage_tracker()->current_damage_rect();
+
+ switch (num_draws_) {
+ case 0:
+ // First frame the whole viewport is damaged.
+ EXPECT_EQ(gfx::RectF(0.f, 0.f, 20.f, 20.f).ToString(),
+ damage_rect.ToString());
+ break;
+ case 1:
+ // Second frame the video layer is damaged.
+ EXPECT_EQ(gfx::RectF(6.f, 6.f, 8.f, 8.f).ToString(),
+ damage_rect.ToString());
+ EndTest();
+ break;
+ }
+
+ EXPECT_TRUE(result);
+ return result;
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ VideoLayerImpl* video = static_cast<VideoLayerImpl*>(
+ host_impl->active_tree()->root_layer()->children()[0]);
+
+ if (num_draws_ == 0)
+ video->SetNeedsRedraw();
+
+ ++num_draws_;
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ EXPECT_EQ(2, num_draws_);
+ }
+
+ private:
+ int num_draws_;
+
+ FakeVideoFrameProvider video_frame_provider_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostVideoTestSetNeedsDisplay);
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/trees/layer_tree_impl.cc b/chromium/cc/trees/layer_tree_impl.cc
new file mode 100644
index 00000000000..70ad6de88fa
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_impl.cc
@@ -0,0 +1,640 @@
+// Copyright 2011 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 "cc/trees/layer_tree_impl.h"
+
+#include "base/debug/trace_event.h"
+#include "cc/animation/keyframed_animation_curve.h"
+#include "cc/animation/scrollbar_animation_controller.h"
+#include "cc/debug/traced_value.h"
+#include "cc/layers/heads_up_display_layer_impl.h"
+#include "cc/layers/render_surface_impl.h"
+#include "cc/layers/scrollbar_layer_impl.h"
+#include "cc/trees/layer_tree_host_common.h"
+#include "cc/trees/layer_tree_host_impl.h"
+#include "ui/gfx/size_conversions.h"
+#include "ui/gfx/vector2d_conversions.h"
+
+namespace cc {
+
+LayerTreeImpl::LayerTreeImpl(LayerTreeHostImpl* layer_tree_host_impl)
+ : layer_tree_host_impl_(layer_tree_host_impl),
+ source_frame_number_(-1),
+ hud_layer_(0),
+ root_scroll_layer_(NULL),
+ currently_scrolling_layer_(NULL),
+ root_layer_scroll_offset_delegate_(NULL),
+ background_color_(0),
+ has_transparent_background_(false),
+ page_scale_factor_(1),
+ page_scale_delta_(1),
+ sent_page_scale_delta_(1),
+ min_page_scale_factor_(0),
+ max_page_scale_factor_(0),
+ scrolling_layer_id_from_previous_tree_(0),
+ contents_textures_purged_(false),
+ viewport_size_invalid_(false),
+ needs_update_draw_properties_(true),
+ needs_full_tree_sync_(true) {
+}
+
+LayerTreeImpl::~LayerTreeImpl() {
+ // Need to explicitly clear the tree prior to destroying this so that
+ // the LayerTreeImpl pointer is still valid in the LayerImpl dtor.
+ root_layer_.reset();
+}
+
+static LayerImpl* FindRootScrollLayerRecursive(LayerImpl* layer) {
+ if (!layer)
+ return NULL;
+
+ if (layer->scrollable())
+ return layer;
+
+ for (size_t i = 0; i < layer->children().size(); ++i) {
+ LayerImpl* found = FindRootScrollLayerRecursive(layer->children()[i]);
+ if (found)
+ return found;
+ }
+
+ return NULL;
+}
+
+void LayerTreeImpl::SetRootLayer(scoped_ptr<LayerImpl> layer) {
+ if (root_scroll_layer_)
+ root_scroll_layer_->SetScrollOffsetDelegate(NULL);
+ root_layer_ = layer.Pass();
+ currently_scrolling_layer_ = NULL;
+ root_scroll_layer_ = NULL;
+
+ layer_tree_host_impl_->OnCanDrawStateChangedForTree();
+}
+
+void LayerTreeImpl::FindRootScrollLayer() {
+ root_scroll_layer_ = FindRootScrollLayerRecursive(root_layer_.get());
+
+ if (root_scroll_layer_) {
+ UpdateMaxScrollOffset();
+ root_scroll_layer_->SetScrollOffsetDelegate(
+ root_layer_scroll_offset_delegate_);
+ }
+
+ if (scrolling_layer_id_from_previous_tree_) {
+ currently_scrolling_layer_ = LayerTreeHostCommon::FindLayerInSubtree(
+ root_layer_.get(),
+ scrolling_layer_id_from_previous_tree_);
+ }
+
+ scrolling_layer_id_from_previous_tree_ = 0;
+}
+
+scoped_ptr<LayerImpl> LayerTreeImpl::DetachLayerTree() {
+ // Clear all data structures that have direct references to the layer tree.
+ scrolling_layer_id_from_previous_tree_ =
+ currently_scrolling_layer_ ? currently_scrolling_layer_->id() : 0;
+ if (root_scroll_layer_)
+ root_scroll_layer_->SetScrollOffsetDelegate(NULL);
+ root_scroll_layer_ = NULL;
+ currently_scrolling_layer_ = NULL;
+
+ render_surface_layer_list_.clear();
+ set_needs_update_draw_properties();
+ return root_layer_.Pass();
+}
+
+void LayerTreeImpl::PushPropertiesTo(LayerTreeImpl* target_tree) {
+ // The request queue should have been processed and does not require a push.
+ DCHECK_EQ(ui_resource_request_queue_.size(), 0u);
+
+ target_tree->SetLatencyInfo(latency_info_);
+ latency_info_.Clear();
+ target_tree->SetPageScaleFactorAndLimits(
+ page_scale_factor(), min_page_scale_factor(), max_page_scale_factor());
+ target_tree->SetPageScaleDelta(
+ target_tree->page_scale_delta() / target_tree->sent_page_scale_delta());
+ target_tree->set_sent_page_scale_delta(1);
+
+ // This should match the property synchronization in
+ // LayerTreeHost::finishCommitOnImplThread().
+ target_tree->set_source_frame_number(source_frame_number());
+ target_tree->set_background_color(background_color());
+ target_tree->set_has_transparent_background(has_transparent_background());
+
+ if (ContentsTexturesPurged())
+ target_tree->SetContentsTexturesPurged();
+ else
+ target_tree->ResetContentsTexturesPurged();
+
+ if (ViewportSizeInvalid())
+ target_tree->SetViewportSizeInvalid();
+ else
+ target_tree->ResetViewportSizeInvalid();
+
+ if (hud_layer())
+ target_tree->set_hud_layer(static_cast<HeadsUpDisplayLayerImpl*>(
+ LayerTreeHostCommon::FindLayerInSubtree(
+ target_tree->root_layer(), hud_layer()->id())));
+ else
+ target_tree->set_hud_layer(NULL);
+}
+
+LayerImpl* LayerTreeImpl::RootScrollLayer() const {
+ return root_scroll_layer_;
+}
+
+LayerImpl* LayerTreeImpl::RootContainerLayer() const {
+ return root_scroll_layer_ ? root_scroll_layer_->parent() : NULL;
+}
+
+LayerImpl* LayerTreeImpl::CurrentlyScrollingLayer() const {
+ DCHECK(IsActiveTree());
+ return currently_scrolling_layer_;
+}
+
+void LayerTreeImpl::SetCurrentlyScrollingLayer(LayerImpl* layer) {
+ if (currently_scrolling_layer_ == layer)
+ return;
+
+ if (currently_scrolling_layer_ &&
+ currently_scrolling_layer_->scrollbar_animation_controller())
+ currently_scrolling_layer_->scrollbar_animation_controller()->
+ DidScrollGestureEnd(CurrentPhysicalTimeTicks());
+ currently_scrolling_layer_ = layer;
+ if (layer && layer->scrollbar_animation_controller())
+ layer->scrollbar_animation_controller()->DidScrollGestureBegin();
+}
+
+void LayerTreeImpl::ClearCurrentlyScrollingLayer() {
+ SetCurrentlyScrollingLayer(NULL);
+ scrolling_layer_id_from_previous_tree_ = 0;
+}
+
+void LayerTreeImpl::SetPageScaleFactorAndLimits(float page_scale_factor,
+ float min_page_scale_factor, float max_page_scale_factor) {
+ if (!page_scale_factor)
+ return;
+
+ min_page_scale_factor_ = min_page_scale_factor;
+ max_page_scale_factor_ = max_page_scale_factor;
+ page_scale_factor_ = page_scale_factor;
+}
+
+void LayerTreeImpl::SetPageScaleDelta(float delta) {
+ // Clamp to the current min/max limits.
+ float total = page_scale_factor_ * delta;
+ if (min_page_scale_factor_ && total < min_page_scale_factor_)
+ delta = min_page_scale_factor_ / page_scale_factor_;
+ else if (max_page_scale_factor_ && total > max_page_scale_factor_)
+ delta = max_page_scale_factor_ / page_scale_factor_;
+
+ if (delta == page_scale_delta_)
+ return;
+
+ page_scale_delta_ = delta;
+
+ if (IsActiveTree()) {
+ LayerTreeImpl* pending_tree = layer_tree_host_impl_->pending_tree();
+ if (pending_tree) {
+ DCHECK_EQ(1, pending_tree->sent_page_scale_delta());
+ pending_tree->SetPageScaleDelta(
+ page_scale_delta_ / sent_page_scale_delta_);
+ }
+ }
+
+ UpdateMaxScrollOffset();
+ set_needs_update_draw_properties();
+}
+
+gfx::SizeF LayerTreeImpl::ScrollableViewportSize() const {
+ return gfx::ScaleSize(layer_tree_host_impl_->VisibleViewportSize(),
+ 1.0f / total_page_scale_factor());
+}
+
+void LayerTreeImpl::UpdateMaxScrollOffset() {
+ LayerImpl* root_scroll = RootScrollLayer();
+ if (!root_scroll || !root_scroll->children().size())
+ return;
+
+ gfx::Vector2dF max_scroll = gfx::Rect(ScrollableSize()).bottom_right() -
+ gfx::RectF(ScrollableViewportSize()).bottom_right();
+
+ // The viewport may be larger than the contents in some cases, such as
+ // having a vertical scrollbar but no horizontal overflow.
+ max_scroll.SetToMax(gfx::Vector2dF());
+
+ root_scroll_layer_->SetMaxScrollOffset(gfx::ToFlooredVector2d(max_scroll));
+}
+
+static void ApplySentScrollDeltasOn(LayerImpl* layer) {
+ layer->ApplySentScrollDeltas();
+}
+
+void LayerTreeImpl::ApplySentScrollAndScaleDeltas() {
+ DCHECK(IsActiveTree());
+
+ page_scale_factor_ *= sent_page_scale_delta_;
+ page_scale_delta_ /= sent_page_scale_delta_;
+ sent_page_scale_delta_ = 1.f;
+
+ if (!root_layer())
+ return;
+
+ LayerTreeHostCommon::CallFunctionForSubtree(
+ root_layer(), base::Bind(&ApplySentScrollDeltasOn));
+}
+
+void LayerTreeImpl::UpdateSolidColorScrollbars() {
+ DCHECK(settings().solid_color_scrollbars);
+
+ LayerImpl* root_scroll = RootScrollLayer();
+ DCHECK(root_scroll);
+ DCHECK(IsActiveTree());
+
+ gfx::RectF scrollable_viewport(
+ gfx::PointAtOffsetFromOrigin(root_scroll->TotalScrollOffset()),
+ ScrollableViewportSize());
+ float vertical_adjust = 0.0f;
+ if (RootContainerLayer())
+ vertical_adjust = layer_tree_host_impl_->VisibleViewportSize().height() -
+ RootContainerLayer()->bounds().height();
+ if (ScrollbarLayerImpl* horiz = root_scroll->horizontal_scrollbar_layer()) {
+ horiz->SetVerticalAdjust(vertical_adjust);
+ horiz->SetVisibleToTotalLengthRatio(
+ scrollable_viewport.width() / ScrollableSize().width());
+ }
+ if (ScrollbarLayerImpl* vertical = root_scroll->vertical_scrollbar_layer()) {
+ vertical->SetVerticalAdjust(vertical_adjust);
+ vertical->SetVisibleToTotalLengthRatio(
+ scrollable_viewport.height() / ScrollableSize().height());
+ }
+}
+
+void LayerTreeImpl::UpdateDrawProperties() {
+ if (IsActiveTree() && RootScrollLayer() && RootContainerLayer())
+ UpdateRootScrollLayerSizeDelta();
+
+ if (settings().solid_color_scrollbars &&
+ IsActiveTree() &&
+ RootScrollLayer()) {
+ UpdateSolidColorScrollbars();
+ }
+
+ needs_update_draw_properties_ = false;
+ render_surface_layer_list_.clear();
+
+ // For max_texture_size.
+ if (!layer_tree_host_impl_->renderer())
+ return;
+
+ if (!root_layer())
+ return;
+
+ {
+ TRACE_EVENT2("cc",
+ "LayerTreeImpl::UpdateDrawProperties",
+ "IsActive",
+ IsActiveTree(),
+ "SourceFrameNumber",
+ source_frame_number_);
+ LayerTreeHostCommon::CalcDrawPropsImplInputs inputs(
+ root_layer(),
+ layer_tree_host_impl_->DeviceViewport().size(),
+ layer_tree_host_impl_->DeviceTransform(),
+ device_scale_factor(),
+ total_page_scale_factor(),
+ root_scroll_layer_ ? root_scroll_layer_->parent() : NULL,
+ MaxTextureSize(),
+ settings().can_use_lcd_text,
+ settings().layer_transforms_should_scale_layer_contents,
+ &render_surface_layer_list_);
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+ }
+
+ DCHECK(!needs_update_draw_properties_) <<
+ "CalcDrawProperties should not set_needs_update_draw_properties()";
+}
+
+const LayerImplList& LayerTreeImpl::RenderSurfaceLayerList() const {
+ // If this assert triggers, then the list is dirty.
+ DCHECK(!needs_update_draw_properties_);
+ return render_surface_layer_list_;
+}
+
+gfx::Size LayerTreeImpl::ScrollableSize() const {
+ if (!root_scroll_layer_ || root_scroll_layer_->children().empty())
+ return gfx::Size();
+ return root_scroll_layer_->children()[0]->bounds();
+}
+
+LayerImpl* LayerTreeImpl::LayerById(int id) {
+ LayerIdMap::iterator iter = layer_id_map_.find(id);
+ return iter != layer_id_map_.end() ? iter->second : NULL;
+}
+
+void LayerTreeImpl::RegisterLayer(LayerImpl* layer) {
+ DCHECK(!LayerById(layer->id()));
+ layer_id_map_[layer->id()] = layer;
+}
+
+void LayerTreeImpl::UnregisterLayer(LayerImpl* layer) {
+ DCHECK(LayerById(layer->id()));
+ layer_id_map_.erase(layer->id());
+}
+
+void LayerTreeImpl::PushPersistedState(LayerTreeImpl* pending_tree) {
+ pending_tree->SetCurrentlyScrollingLayer(
+ LayerTreeHostCommon::FindLayerInSubtree(pending_tree->root_layer(),
+ currently_scrolling_layer_ ? currently_scrolling_layer_->id() : 0));
+ pending_tree->SetLatencyInfo(latency_info_);
+ latency_info_.Clear();
+}
+
+static void DidBecomeActiveRecursive(LayerImpl* layer) {
+ layer->DidBecomeActive();
+ for (size_t i = 0; i < layer->children().size(); ++i)
+ DidBecomeActiveRecursive(layer->children()[i]);
+}
+
+void LayerTreeImpl::DidBecomeActive() {
+ if (!root_layer())
+ return;
+
+ DidBecomeActiveRecursive(root_layer());
+ FindRootScrollLayer();
+}
+
+bool LayerTreeImpl::ContentsTexturesPurged() const {
+ return contents_textures_purged_;
+}
+
+void LayerTreeImpl::SetContentsTexturesPurged() {
+ if (contents_textures_purged_)
+ return;
+ contents_textures_purged_ = true;
+ layer_tree_host_impl_->OnCanDrawStateChangedForTree();
+}
+
+void LayerTreeImpl::ResetContentsTexturesPurged() {
+ if (!contents_textures_purged_)
+ return;
+ contents_textures_purged_ = false;
+ layer_tree_host_impl_->OnCanDrawStateChangedForTree();
+}
+
+bool LayerTreeImpl::ViewportSizeInvalid() const {
+ return viewport_size_invalid_;
+}
+
+void LayerTreeImpl::SetViewportSizeInvalid() {
+ viewport_size_invalid_ = true;
+ layer_tree_host_impl_->OnCanDrawStateChangedForTree();
+}
+
+void LayerTreeImpl::ResetViewportSizeInvalid() {
+ viewport_size_invalid_ = false;
+ layer_tree_host_impl_->OnCanDrawStateChangedForTree();
+}
+
+Proxy* LayerTreeImpl::proxy() const {
+ return layer_tree_host_impl_->proxy();
+}
+
+const LayerTreeSettings& LayerTreeImpl::settings() const {
+ return layer_tree_host_impl_->settings();
+}
+
+const RendererCapabilities& LayerTreeImpl::GetRendererCapabilities() const {
+ return layer_tree_host_impl_->GetRendererCapabilities();
+}
+
+OutputSurface* LayerTreeImpl::output_surface() const {
+ return layer_tree_host_impl_->output_surface();
+}
+
+ResourceProvider* LayerTreeImpl::resource_provider() const {
+ return layer_tree_host_impl_->resource_provider();
+}
+
+TileManager* LayerTreeImpl::tile_manager() const {
+ return layer_tree_host_impl_->tile_manager();
+}
+
+FrameRateCounter* LayerTreeImpl::frame_rate_counter() const {
+ return layer_tree_host_impl_->fps_counter();
+}
+
+PaintTimeCounter* LayerTreeImpl::paint_time_counter() const {
+ return layer_tree_host_impl_->paint_time_counter();
+}
+
+MemoryHistory* LayerTreeImpl::memory_history() const {
+ return layer_tree_host_impl_->memory_history();
+}
+
+bool LayerTreeImpl::IsActiveTree() const {
+ return layer_tree_host_impl_->active_tree() == this;
+}
+
+bool LayerTreeImpl::IsPendingTree() const {
+ return layer_tree_host_impl_->pending_tree() == this;
+}
+
+bool LayerTreeImpl::IsRecycleTree() const {
+ return layer_tree_host_impl_->recycle_tree() == this;
+}
+
+LayerImpl* LayerTreeImpl::FindActiveTreeLayerById(int id) {
+ LayerTreeImpl* tree = layer_tree_host_impl_->active_tree();
+ if (!tree)
+ return NULL;
+ return tree->LayerById(id);
+}
+
+LayerImpl* LayerTreeImpl::FindPendingTreeLayerById(int id) {
+ LayerTreeImpl* tree = layer_tree_host_impl_->pending_tree();
+ if (!tree)
+ return NULL;
+ return tree->LayerById(id);
+}
+
+int LayerTreeImpl::MaxTextureSize() const {
+ return layer_tree_host_impl_->GetRendererCapabilities().max_texture_size;
+}
+
+bool LayerTreeImpl::PinchGestureActive() const {
+ return layer_tree_host_impl_->pinch_gesture_active();
+}
+
+base::TimeTicks LayerTreeImpl::CurrentFrameTimeTicks() const {
+ return layer_tree_host_impl_->CurrentFrameTimeTicks();
+}
+
+base::Time LayerTreeImpl::CurrentFrameTime() const {
+ return layer_tree_host_impl_->CurrentFrameTime();
+}
+
+base::TimeTicks LayerTreeImpl::CurrentPhysicalTimeTicks() const {
+ return layer_tree_host_impl_->CurrentPhysicalTimeTicks();
+}
+
+void LayerTreeImpl::SetNeedsCommit() {
+ layer_tree_host_impl_->SetNeedsCommit();
+}
+
+void LayerTreeImpl::SetNeedsRedraw() {
+ layer_tree_host_impl_->SetNeedsRedraw();
+}
+
+const LayerTreeDebugState& LayerTreeImpl::debug_state() const {
+ return layer_tree_host_impl_->debug_state();
+}
+
+float LayerTreeImpl::device_scale_factor() const {
+ return layer_tree_host_impl_->device_scale_factor();
+}
+
+gfx::Size LayerTreeImpl::device_viewport_size() const {
+ return layer_tree_host_impl_->device_viewport_size();
+}
+
+DebugRectHistory* LayerTreeImpl::debug_rect_history() const {
+ return layer_tree_host_impl_->debug_rect_history();
+}
+
+AnimationRegistrar* LayerTreeImpl::animationRegistrar() const {
+ return layer_tree_host_impl_->animation_registrar();
+}
+
+scoped_ptr<base::Value> LayerTreeImpl::AsValue() const {
+ scoped_ptr<base::DictionaryValue> state(new base::DictionaryValue());
+ TracedValue::MakeDictIntoImplicitSnapshot(
+ state.get(), "cc::LayerTreeImpl", this);
+
+ state->Set("root_layer", root_layer_->AsValue().release());
+
+ scoped_ptr<base::ListValue> render_surface_layer_list(new base::ListValue());
+ typedef LayerIterator<LayerImpl,
+ LayerImplList,
+ RenderSurfaceImpl,
+ LayerIteratorActions::BackToFront> LayerIteratorType;
+ LayerIteratorType end = LayerIteratorType::End(&render_surface_layer_list_);
+ for (LayerIteratorType it = LayerIteratorType::Begin(
+ &render_surface_layer_list_); it != end; ++it) {
+ if (!it.represents_itself())
+ continue;
+ render_surface_layer_list->Append(TracedValue::CreateIDRef(*it).release());
+ }
+
+ state->Set("render_surface_layer_list",
+ render_surface_layer_list.release());
+ return state.PassAs<base::Value>();
+}
+
+void LayerTreeImpl::SetRootLayerScrollOffsetDelegate(
+ LayerScrollOffsetDelegate* root_layer_scroll_offset_delegate) {
+ root_layer_scroll_offset_delegate_ = root_layer_scroll_offset_delegate;
+ if (root_scroll_layer_) {
+ root_scroll_layer_->SetScrollOffsetDelegate(
+ root_layer_scroll_offset_delegate_);
+ }
+}
+
+void LayerTreeImpl::UpdateRootScrollLayerSizeDelta() {
+ LayerImpl* root_scroll = RootScrollLayer();
+ LayerImpl* root_container = RootContainerLayer();
+ DCHECK(root_scroll);
+ DCHECK(root_container);
+ DCHECK(IsActiveTree());
+
+ gfx::Vector2dF scrollable_viewport_size =
+ gfx::RectF(ScrollableViewportSize()).bottom_right() - gfx::PointF();
+
+ gfx::Vector2dF original_viewport_size =
+ gfx::RectF(root_container->bounds()).bottom_right() -
+ gfx::PointF();
+ original_viewport_size.Scale(1 / page_scale_factor());
+
+ root_scroll->SetFixedContainerSizeDelta(
+ scrollable_viewport_size - original_viewport_size);
+}
+
+void LayerTreeImpl::SetLatencyInfo(const ui::LatencyInfo& latency_info) {
+ latency_info_.MergeWith(latency_info);
+}
+
+const ui::LatencyInfo& LayerTreeImpl::GetLatencyInfo() {
+ return latency_info_;
+}
+
+void LayerTreeImpl::ClearLatencyInfo() {
+ latency_info_.Clear();
+}
+
+void LayerTreeImpl::WillModifyTilePriorities() {
+ layer_tree_host_impl_->SetNeedsManageTiles();
+}
+
+void LayerTreeImpl::set_ui_resource_request_queue(
+ const UIResourceRequestQueue& queue) {
+ ui_resource_request_queue_ = queue;
+}
+
+ResourceProvider::ResourceId LayerTreeImpl::ResourceIdForUIResource(
+ UIResourceId uid) const {
+ return layer_tree_host_impl_->ResourceIdForUIResource(uid);
+}
+
+void LayerTreeImpl::ProcessUIResourceRequestQueue() {
+ while (ui_resource_request_queue_.size() > 0) {
+ UIResourceRequest req = ui_resource_request_queue_.front();
+ ui_resource_request_queue_.pop_front();
+
+ switch (req.type) {
+ case UIResourceRequest::UIResourceCreate:
+ layer_tree_host_impl_->CreateUIResource(req.id, req.bitmap);
+ break;
+ case UIResourceRequest::UIResourceDelete:
+ layer_tree_host_impl_->DeleteUIResource(req.id);
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+}
+
+void LayerTreeImpl::AddLayerWithCopyOutputRequest(LayerImpl* layer) {
+ // Only the active tree needs to know about layers with copy requests, as
+ // they are aborted if not serviced during draw.
+ DCHECK(IsActiveTree());
+
+ DCHECK(std::find(layers_with_copy_output_request_.begin(),
+ layers_with_copy_output_request_.end(),
+ layer) == layers_with_copy_output_request_.end());
+ layers_with_copy_output_request_.push_back(layer);
+}
+
+void LayerTreeImpl::RemoveLayerWithCopyOutputRequest(LayerImpl* layer) {
+ // Only the active tree needs to know about layers with copy requests, as
+ // they are aborted if not serviced during draw.
+ DCHECK(IsActiveTree());
+
+ std::vector<LayerImpl*>::iterator it = std::find(
+ layers_with_copy_output_request_.begin(),
+ layers_with_copy_output_request_.end(),
+ layer);
+ DCHECK(it != layers_with_copy_output_request_.end());
+ layers_with_copy_output_request_.erase(it);
+}
+
+const std::vector<LayerImpl*> LayerTreeImpl::LayersWithCopyOutputRequest()
+ const {
+ // Only the active tree needs to know about layers with copy requests, as
+ // they are aborted if not serviced during draw.
+ DCHECK(IsActiveTree());
+
+ return layers_with_copy_output_request_;
+}
+
+} // namespace cc
diff --git a/chromium/cc/trees/layer_tree_impl.h b/chromium/cc/trees/layer_tree_impl.h
new file mode 100644
index 00000000000..7e66da8b04e
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_impl.h
@@ -0,0 +1,261 @@
+// 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.
+
+#ifndef CC_TREES_LAYER_TREE_IMPL_H_
+#define CC_TREES_LAYER_TREE_IMPL_H_
+
+#include <list>
+#include <string>
+#include <vector>
+
+#include "base/containers/hash_tables.h"
+#include "base/values.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/resources/ui_resource_client.h"
+#include "ui/base/latency_info.h"
+
+#if defined(COMPILER_GCC)
+namespace BASE_HASH_NAMESPACE {
+template<>
+struct hash<cc::LayerImpl*> {
+ size_t operator()(cc::LayerImpl* ptr) const {
+ return hash<size_t>()(reinterpret_cast<size_t>(ptr));
+ }
+};
+} // namespace BASE_HASH_NAMESPACE
+#endif // COMPILER
+
+namespace cc {
+
+class DebugRectHistory;
+class FrameRateCounter;
+class HeadsUpDisplayLayerImpl;
+class LayerTreeDebugState;
+class LayerTreeHostImpl;
+class LayerTreeImpl;
+class LayerTreeSettings;
+class MemoryHistory;
+class OutputSurface;
+class PaintTimeCounter;
+class Proxy;
+class ResourceProvider;
+class TileManager;
+struct RendererCapabilities;
+struct UIResourceRequest;
+
+typedef std::list<UIResourceRequest> UIResourceRequestQueue;
+
+class CC_EXPORT LayerTreeImpl {
+ public:
+ static scoped_ptr<LayerTreeImpl> create(
+ LayerTreeHostImpl* layer_tree_host_impl) {
+ return make_scoped_ptr(new LayerTreeImpl(layer_tree_host_impl));
+ }
+ virtual ~LayerTreeImpl();
+
+ // Methods called by the layer tree that pass-through or access LTHI.
+ // ---------------------------------------------------------------------------
+ const LayerTreeSettings& settings() const;
+ const RendererCapabilities& GetRendererCapabilities() const;
+ OutputSurface* output_surface() const;
+ ResourceProvider* resource_provider() const;
+ TileManager* tile_manager() const;
+ FrameRateCounter* frame_rate_counter() const;
+ PaintTimeCounter* paint_time_counter() const;
+ MemoryHistory* memory_history() const;
+ bool IsActiveTree() const;
+ bool IsPendingTree() const;
+ bool IsRecycleTree() const;
+ LayerImpl* FindActiveTreeLayerById(int id);
+ LayerImpl* FindPendingTreeLayerById(int id);
+ int MaxTextureSize() const;
+ bool PinchGestureActive() const;
+ base::TimeTicks CurrentFrameTimeTicks() const;
+ base::Time CurrentFrameTime() const;
+ base::TimeTicks CurrentPhysicalTimeTicks() const;
+ void SetNeedsCommit();
+
+ // Tree specific methods exposed to layer-impl tree.
+ // ---------------------------------------------------------------------------
+ void SetNeedsRedraw();
+
+ // TODO(nduca): These are implemented in cc files temporarily, but will become
+ // trivial accessors in a followup patch.
+ const LayerTreeDebugState& debug_state() const;
+ float device_scale_factor() const;
+ gfx::Size device_viewport_size() const;
+ DebugRectHistory* debug_rect_history() const;
+ scoped_ptr<base::Value> AsValue() const;
+
+ // Other public methods
+ // ---------------------------------------------------------------------------
+ LayerImpl* root_layer() const { return root_layer_.get(); }
+ void SetRootLayer(scoped_ptr<LayerImpl>);
+ scoped_ptr<LayerImpl> DetachLayerTree();
+
+ void PushPropertiesTo(LayerTreeImpl* tree_impl);
+
+ int source_frame_number() const { return source_frame_number_; }
+ void set_source_frame_number(int frame_number) {
+ source_frame_number_ = frame_number;
+ }
+
+ HeadsUpDisplayLayerImpl* hud_layer() { return hud_layer_; }
+ void set_hud_layer(HeadsUpDisplayLayerImpl* layer_impl) {
+ hud_layer_ = layer_impl;
+ }
+
+ LayerImpl* RootScrollLayer() const;
+ LayerImpl* RootContainerLayer() const;
+ LayerImpl* CurrentlyScrollingLayer() const;
+ void SetCurrentlyScrollingLayer(LayerImpl* layer);
+ void ClearCurrentlyScrollingLayer();
+
+ void FindRootScrollLayer();
+ void UpdateMaxScrollOffset();
+ void ApplySentScrollAndScaleDeltas();
+
+ SkColor background_color() const { return background_color_; }
+ void set_background_color(SkColor color) { background_color_ = color; }
+
+ bool has_transparent_background() const {
+ return has_transparent_background_;
+ }
+ void set_has_transparent_background(bool transparent) {
+ has_transparent_background_ = transparent;
+ }
+
+ void SetPageScaleFactorAndLimits(float page_scale_factor,
+ float min_page_scale_factor, float max_page_scale_factor);
+ void SetPageScaleDelta(float delta);
+ float total_page_scale_factor() const {
+ return page_scale_factor_ * page_scale_delta_;
+ }
+ float page_scale_factor() const { return page_scale_factor_; }
+ float min_page_scale_factor() const { return min_page_scale_factor_; }
+ float max_page_scale_factor() const { return max_page_scale_factor_; }
+ float page_scale_delta() const { return page_scale_delta_; }
+ void set_sent_page_scale_delta(float delta) {
+ sent_page_scale_delta_ = delta;
+ }
+ float sent_page_scale_delta() const { return sent_page_scale_delta_; }
+
+ // Updates draw properties and render surface layer list, as well as tile
+ // priorities.
+ void UpdateDrawProperties();
+
+ void set_needs_update_draw_properties() {
+ needs_update_draw_properties_ = true;
+ }
+ bool needs_update_draw_properties() const {
+ return needs_update_draw_properties_;
+ }
+
+ void set_needs_full_tree_sync(bool needs) { needs_full_tree_sync_ = needs; }
+ bool needs_full_tree_sync() const { return needs_full_tree_sync_; }
+
+ void set_ui_resource_request_queue(const UIResourceRequestQueue& queue);
+
+ const LayerImplList& RenderSurfaceLayerList() const;
+
+ // These return the size of the root scrollable area and the size of
+ // the user-visible scrolling viewport, in CSS layout coordinates.
+ gfx::Size ScrollableSize() const;
+ gfx::SizeF ScrollableViewportSize() const;
+
+ LayerImpl* LayerById(int id);
+
+ // These should be called by LayerImpl's ctor/dtor.
+ void RegisterLayer(LayerImpl* layer);
+ void UnregisterLayer(LayerImpl* layer);
+
+ AnimationRegistrar* animationRegistrar() const;
+
+ void PushPersistedState(LayerTreeImpl* pending_tree);
+
+ void DidBecomeActive();
+
+ bool ContentsTexturesPurged() const;
+ void SetContentsTexturesPurged();
+ void ResetContentsTexturesPurged();
+
+ // Set on the active tree when the viewport size recently changed
+ // and the active tree's size is now out of date.
+ bool ViewportSizeInvalid() const;
+ void SetViewportSizeInvalid();
+ void ResetViewportSizeInvalid();
+
+ // Useful for debug assertions, probably shouldn't be used for anything else.
+ Proxy* proxy() const;
+
+ void SetRootLayerScrollOffsetDelegate(
+ LayerScrollOffsetDelegate* root_layer_scroll_offset_delegate);
+
+ void SetLatencyInfo(const ui::LatencyInfo& latency_info);
+ const ui::LatencyInfo& GetLatencyInfo();
+ void ClearLatencyInfo();
+
+ void WillModifyTilePriorities();
+
+ ResourceProvider::ResourceId ResourceIdForUIResource(UIResourceId uid) const;
+ void ProcessUIResourceRequestQueue();
+
+ void AddLayerWithCopyOutputRequest(LayerImpl* layer);
+ void RemoveLayerWithCopyOutputRequest(LayerImpl* layer);
+ const std::vector<LayerImpl*> LayersWithCopyOutputRequest() const;
+
+ protected:
+ explicit LayerTreeImpl(LayerTreeHostImpl* layer_tree_host_impl);
+
+ void UpdateSolidColorScrollbars();
+
+ void UpdateRootScrollLayerSizeDelta();
+
+ LayerTreeHostImpl* layer_tree_host_impl_;
+ int source_frame_number_;
+ scoped_ptr<LayerImpl> root_layer_;
+ HeadsUpDisplayLayerImpl* hud_layer_;
+ LayerImpl* root_scroll_layer_;
+ LayerImpl* currently_scrolling_layer_;
+ LayerScrollOffsetDelegate* root_layer_scroll_offset_delegate_;
+ SkColor background_color_;
+ bool has_transparent_background_;
+
+ float page_scale_factor_;
+ float page_scale_delta_;
+ float sent_page_scale_delta_;
+ float min_page_scale_factor_;
+ float max_page_scale_factor_;
+
+ typedef base::hash_map<int, LayerImpl*> LayerIdMap;
+ LayerIdMap layer_id_map_;
+
+ std::vector<LayerImpl*> layers_with_copy_output_request_;
+
+ // Persisted state for non-impl-side-painting.
+ int scrolling_layer_id_from_previous_tree_;
+
+ // List of visible layers for the most recently prepared frame. Used for
+ // rendering and input event hit testing.
+ LayerImplList render_surface_layer_list_;
+
+ bool contents_textures_purged_;
+ bool viewport_size_invalid_;
+ bool needs_update_draw_properties_;
+
+ // In impl-side painting mode, this is true when the tree may contain
+ // structural differences relative to the active tree.
+ bool needs_full_tree_sync_;
+
+ ui::LatencyInfo latency_info_;
+
+ UIResourceRequestQueue ui_resource_request_queue_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LayerTreeImpl);
+};
+
+} // namespace cc
+
+#endif // CC_TREES_LAYER_TREE_IMPL_H_
diff --git a/chromium/cc/trees/layer_tree_settings.cc b/chromium/cc/trees/layer_tree_settings.cc
new file mode 100644
index 00000000000..62fd261ef82
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_settings.cc
@@ -0,0 +1,68 @@
+// Copyright 2011 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 "cc/trees/layer_tree_settings.h"
+
+#include <limits>
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+
+namespace cc {
+
+LayerTreeSettings::LayerTreeSettings()
+ : impl_side_painting(false),
+ allow_antialiasing(true),
+ throttle_frame_production(true),
+ begin_frame_scheduling_enabled(false),
+ using_synchronous_renderer_compositor(false),
+ per_tile_painting_enabled(false),
+ partial_swap_enabled(false),
+ cache_render_pass_contents(true),
+ accelerated_animation_enabled(true),
+ background_color_instead_of_checkerboard(false),
+ show_overdraw_in_tracing(false),
+ can_use_lcd_text(true),
+ should_clear_root_render_pass(true),
+ use_linear_fade_scrollbar_animator(false),
+ scrollbar_linear_fade_delay_ms(300),
+ scrollbar_linear_fade_length_ms(300),
+ solid_color_scrollbars(false),
+ solid_color_scrollbar_color(SK_ColorWHITE),
+ solid_color_scrollbar_thickness_dip(-1),
+ calculate_top_controls_position(false),
+ use_memory_management(true),
+ timeout_and_draw_when_animation_checkerboards(true),
+ layer_transforms_should_scale_layer_contents(false),
+ minimum_contents_scale(0.0625f),
+ low_res_contents_scale_factor(0.125f),
+ top_controls_height(0.f),
+ top_controls_show_threshold(0.5f),
+ top_controls_hide_threshold(0.5f),
+ refresh_rate(60.0),
+ max_partial_texture_updates(std::numeric_limits<size_t>::max()),
+ num_raster_threads(1),
+ default_tile_size(gfx::Size(256, 256)),
+ max_untiled_layer_size(gfx::Size(512, 512)),
+ minimum_occlusion_tracking_size(gfx::Size(160, 160)),
+ use_pinch_zoom_scrollbars(false),
+ use_pinch_virtual_viewport(false),
+ // At 256x256 tiles, 128 tiles cover an area of 2048x4096 pixels.
+ max_tiles_for_interest_area(128),
+ max_unused_resource_memory_percentage(100),
+ highp_threshold_min(0),
+ force_direct_layer_drawing(false),
+ strict_layer_property_change_checking(false),
+ use_map_image(false),
+ compositor_name("ChromiumCompositor"),
+ ignore_root_layer_flings(false) {
+ // TODO(danakj): Renable surface caching when we can do it more realiably.
+ // crbug.com/170713
+ cache_render_pass_contents = false;
+}
+
+LayerTreeSettings::~LayerTreeSettings() {}
+
+} // namespace cc
diff --git a/chromium/cc/trees/layer_tree_settings.h b/chromium/cc/trees/layer_tree_settings.h
new file mode 100644
index 00000000000..d3a71cf8ed7
--- /dev/null
+++ b/chromium/cc/trees/layer_tree_settings.h
@@ -0,0 +1,73 @@
+// Copyright 2011 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.
+
+#ifndef CC_TREES_LAYER_TREE_SETTINGS_H_
+#define CC_TREES_LAYER_TREE_SETTINGS_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "cc/base/cc_export.h"
+#include "cc/debug/layer_tree_debug_state.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/gfx/size.h"
+
+namespace cc {
+
+class CC_EXPORT LayerTreeSettings {
+ public:
+ LayerTreeSettings();
+ ~LayerTreeSettings();
+
+ bool impl_side_painting;
+ bool allow_antialiasing;
+ bool throttle_frame_production;
+ bool begin_frame_scheduling_enabled;
+ bool using_synchronous_renderer_compositor;
+ bool per_tile_painting_enabled;
+ bool partial_swap_enabled;
+ bool cache_render_pass_contents;
+ bool accelerated_animation_enabled;
+ bool background_color_instead_of_checkerboard;
+ bool show_overdraw_in_tracing;
+ bool can_use_lcd_text;
+ bool should_clear_root_render_pass;
+ bool use_linear_fade_scrollbar_animator;
+ int scrollbar_linear_fade_delay_ms;
+ int scrollbar_linear_fade_length_ms;
+ bool solid_color_scrollbars;
+ SkColor solid_color_scrollbar_color;
+ int solid_color_scrollbar_thickness_dip;
+ bool calculate_top_controls_position;
+ bool use_memory_management;
+ bool timeout_and_draw_when_animation_checkerboards;
+ bool layer_transforms_should_scale_layer_contents;
+ float minimum_contents_scale;
+ float low_res_contents_scale_factor;
+ float top_controls_height;
+ float top_controls_show_threshold;
+ float top_controls_hide_threshold;
+ double refresh_rate;
+ size_t max_partial_texture_updates;
+ size_t num_raster_threads;
+ gfx::Size default_tile_size;
+ gfx::Size max_untiled_layer_size;
+ gfx::Size minimum_occlusion_tracking_size;
+ bool use_pinch_zoom_scrollbars;
+ bool use_pinch_virtual_viewport;
+ size_t max_tiles_for_interest_area;
+ size_t max_unused_resource_memory_percentage;
+ int highp_threshold_min;
+ bool force_direct_layer_drawing; // With Skia GPU backend.
+ bool strict_layer_property_change_checking;
+ bool use_map_image;
+ std::string compositor_name;
+ bool ignore_root_layer_flings;
+
+ LayerTreeDebugState initial_debug_state;
+};
+
+} // namespace cc
+
+#endif // CC_TREES_LAYER_TREE_SETTINGS_H_
diff --git a/chromium/cc/trees/occlusion_tracker.cc b/chromium/cc/trees/occlusion_tracker.cc
new file mode 100644
index 00000000000..9fc26da5667
--- /dev/null
+++ b/chromium/cc/trees/occlusion_tracker.cc
@@ -0,0 +1,746 @@
+// 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 "cc/trees/occlusion_tracker.h"
+
+#include <algorithm>
+
+#include "cc/base/math_util.h"
+#include "cc/debug/overdraw_metrics.h"
+#include "cc/layers/layer.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/layers/render_surface.h"
+#include "cc/layers/render_surface_impl.h"
+#include "ui/gfx/quad_f.h"
+#include "ui/gfx/rect_conversions.h"
+
+namespace cc {
+
+template <typename LayerType, typename RenderSurfaceType>
+OcclusionTrackerBase<LayerType, RenderSurfaceType>::OcclusionTrackerBase(
+ gfx::Rect screen_space_clip_rect, bool record_metrics_for_frame)
+ : screen_space_clip_rect_(screen_space_clip_rect),
+ overdraw_metrics_(OverdrawMetrics::Create(record_metrics_for_frame)),
+ prevent_occlusion_(false),
+ occluding_screen_space_rects_(NULL),
+ non_occluding_screen_space_rects_(NULL) {}
+
+template <typename LayerType, typename RenderSurfaceType>
+OcclusionTrackerBase<LayerType, RenderSurfaceType>::~OcclusionTrackerBase() {}
+
+template <typename LayerType, typename RenderSurfaceType>
+void OcclusionTrackerBase<LayerType, RenderSurfaceType>::EnterLayer(
+ const LayerIteratorPosition<LayerType>& layer_iterator,
+ bool prevent_occlusion) {
+ LayerType* render_target = layer_iterator.target_render_surface_layer;
+
+ if (layer_iterator.represents_itself)
+ EnterRenderTarget(render_target);
+ else if (layer_iterator.represents_target_render_surface)
+ FinishedRenderTarget(render_target);
+
+ prevent_occlusion_ = prevent_occlusion;
+}
+
+template <typename LayerType, typename RenderSurfaceType>
+void OcclusionTrackerBase<LayerType, RenderSurfaceType>::LeaveLayer(
+ const LayerIteratorPosition<LayerType>& layer_iterator) {
+ LayerType* render_target = layer_iterator.target_render_surface_layer;
+
+ if (layer_iterator.represents_itself)
+ MarkOccludedBehindLayer(layer_iterator.current_layer);
+ // TODO(danakj): This should be done when entering the contributing surface,
+ // but in a way that the surface's own occlusion won't occlude itself.
+ else if (layer_iterator.represents_contributing_render_surface)
+ LeaveToRenderTarget(render_target);
+
+ prevent_occlusion_ = false;
+}
+
+template <typename RenderSurfaceType>
+static gfx::Rect ScreenSpaceClipRectInTargetSurface(
+ const RenderSurfaceType* target_surface, gfx::Rect screen_space_clip_rect) {
+ gfx::Transform inverse_screen_space_transform(
+ gfx::Transform::kSkipInitialization);
+ if (!target_surface->screen_space_transform().GetInverse(
+ &inverse_screen_space_transform))
+ return target_surface->content_rect();
+
+ return gfx::ToEnclosingRect(MathUtil::ProjectClippedRect(
+ inverse_screen_space_transform, screen_space_clip_rect));
+}
+
+template <typename RenderSurfaceType>
+static Region TransformSurfaceOpaqueRegion(const Region& region,
+ bool have_clip_rect,
+ gfx::Rect clip_rect_in_new_target,
+ const gfx::Transform& transform) {
+ if (region.IsEmpty())
+ return Region();
+
+ // Verify that rects within the |surface| will remain rects in its target
+ // surface after applying |transform|. If this is true, then apply |transform|
+ // to each rect within |region| in order to transform the entire Region.
+
+ // TODO(danakj): Find a rect interior to each transformed quad.
+ if (!transform.Preserves2dAxisAlignment())
+ return Region();
+
+ // TODO(danakj): If the Region is too complex, degrade gracefully here by
+ // skipping rects in it.
+ Region transformed_region;
+ for (Region::Iterator rects(region); rects.has_rect(); rects.next()) {
+ bool clipped;
+ gfx::QuadF transformed_quad =
+ MathUtil::MapQuad(transform, gfx::QuadF(rects.rect()), &clipped);
+ gfx::Rect transformed_rect =
+ gfx::ToEnclosedRect(transformed_quad.BoundingBox());
+ DCHECK(!clipped); // We only map if the transform preserves axis alignment.
+ if (have_clip_rect)
+ transformed_rect.Intersect(clip_rect_in_new_target);
+ transformed_region.Union(transformed_rect);
+ }
+ return transformed_region;
+}
+
+static inline bool LayerOpacityKnown(const Layer* layer) {
+ return !layer->draw_opacity_is_animating();
+}
+static inline bool LayerOpacityKnown(const LayerImpl* layer) {
+ return true;
+}
+static inline bool LayerTransformsToTargetKnown(const Layer* layer) {
+ return !layer->draw_transform_is_animating();
+}
+static inline bool LayerTransformsToTargetKnown(const LayerImpl* layer) {
+ return true;
+}
+
+static inline bool SurfaceOpacityKnown(const RenderSurface* rs) {
+ return !rs->draw_opacity_is_animating();
+}
+static inline bool SurfaceOpacityKnown(const RenderSurfaceImpl* rs) {
+ return true;
+}
+static inline bool SurfaceTransformsToTargetKnown(const RenderSurface* rs) {
+ return !rs->target_surface_transforms_are_animating();
+}
+static inline bool SurfaceTransformsToTargetKnown(const RenderSurfaceImpl* rs) {
+ return true;
+}
+static inline bool SurfaceTransformsToScreenKnown(const RenderSurface* rs) {
+ return !rs->screen_space_transforms_are_animating();
+}
+static inline bool SurfaceTransformsToScreenKnown(const RenderSurfaceImpl* rs) {
+ return true;
+}
+
+static inline bool LayerIsInUnsorted3dRenderingContext(const Layer* layer) {
+ return layer->parent() && layer->parent()->preserves_3d();
+}
+static inline bool LayerIsInUnsorted3dRenderingContext(const LayerImpl* layer) {
+ return false;
+}
+
+template <typename LayerType>
+static inline bool LayerIsHidden(const LayerType* layer) {
+ return layer->hide_layer_and_subtree() ||
+ (layer->parent() && LayerIsHidden(layer->parent()));
+}
+
+template <typename LayerType, typename RenderSurfaceType>
+void OcclusionTrackerBase<LayerType, RenderSurfaceType>::EnterRenderTarget(
+ const LayerType* new_target) {
+ if (!stack_.empty() && stack_.back().target == new_target)
+ return;
+
+ const LayerType* old_target = NULL;
+ const RenderSurfaceType* old_ancestor_that_moves_pixels = NULL;
+ if (!stack_.empty()) {
+ old_target = stack_.back().target;
+ old_ancestor_that_moves_pixels =
+ old_target->render_surface()->nearest_ancestor_that_moves_pixels();
+ }
+ const RenderSurfaceType* new_ancestor_that_moves_pixels =
+ new_target->render_surface()->nearest_ancestor_that_moves_pixels();
+
+ stack_.push_back(StackObject(new_target));
+
+ // We copy the screen occlusion into the new RenderSurface subtree, but we
+ // never copy in the occlusion from inside the target, since we are looking
+ // at a new RenderSurface target.
+
+ // If we are entering a subtree that is going to move pixels around, then the
+ // occlusion we've computed so far won't apply to the pixels we're drawing
+ // here in the same way. We discard the occlusion thus far to be safe, and
+ // ensure we don't cull any pixels that are moved such that they become
+ // visible.
+ bool entering_subtree_that_moves_pixels =
+ new_ancestor_that_moves_pixels &&
+ new_ancestor_that_moves_pixels != old_ancestor_that_moves_pixels;
+
+ bool have_transform_from_screen_to_new_target = false;
+ gfx::Transform inverse_new_target_screen_space_transform(
+ // Note carefully, not used if screen space transform is uninvertible.
+ gfx::Transform::kSkipInitialization);
+ if (SurfaceTransformsToScreenKnown(new_target->render_surface())) {
+ have_transform_from_screen_to_new_target =
+ new_target->render_surface()->screen_space_transform().GetInverse(
+ &inverse_new_target_screen_space_transform);
+ }
+
+ bool entering_root_target = new_target->parent() == NULL;
+
+ bool copy_outside_occlusion_forward =
+ stack_.size() > 1 &&
+ !entering_subtree_that_moves_pixels &&
+ have_transform_from_screen_to_new_target &&
+ !entering_root_target;
+ if (!copy_outside_occlusion_forward)
+ return;
+
+ int last_index = stack_.size() - 1;
+ gfx::Transform old_target_to_new_target_transform(
+ inverse_new_target_screen_space_transform,
+ old_target->render_surface()->screen_space_transform());
+ stack_[last_index].occlusion_from_outside_target =
+ TransformSurfaceOpaqueRegion<RenderSurfaceType>(
+ stack_[last_index - 1].occlusion_from_outside_target,
+ false,
+ gfx::Rect(),
+ old_target_to_new_target_transform);
+ stack_[last_index].occlusion_from_outside_target.Union(
+ TransformSurfaceOpaqueRegion<RenderSurfaceType>(
+ stack_[last_index - 1].occlusion_from_inside_target,
+ false,
+ gfx::Rect(),
+ old_target_to_new_target_transform));
+}
+
+template <typename LayerType, typename RenderSurfaceType>
+void OcclusionTrackerBase<LayerType, RenderSurfaceType>::FinishedRenderTarget(
+ const LayerType* finished_target) {
+ // Make sure we know about the target surface.
+ EnterRenderTarget(finished_target);
+
+ RenderSurfaceType* surface = finished_target->render_surface();
+
+ // Readbacks always happen on render targets so we only need to check
+ // for readbacks here.
+ bool target_is_only_for_copy_request =
+ finished_target->HasCopyRequest() && LayerIsHidden(finished_target);
+
+ // If the occlusion within the surface can not be applied to things outside of
+ // the surface's subtree, then clear the occlusion here so it won't be used.
+ // TODO(senorblanco): Make this smarter for SkImageFilter case: once
+ // SkImageFilters can report affectsOpacity(), call that.
+ if (finished_target->mask_layer() ||
+ !SurfaceOpacityKnown(surface) ||
+ surface->draw_opacity() < 1 ||
+ target_is_only_for_copy_request ||
+ finished_target->filters().HasFilterThatAffectsOpacity() ||
+ finished_target->filter()) {
+ stack_.back().occlusion_from_outside_target.Clear();
+ stack_.back().occlusion_from_inside_target.Clear();
+ } else if (!SurfaceTransformsToTargetKnown(surface)) {
+ stack_.back().occlusion_from_inside_target.Clear();
+ stack_.back().occlusion_from_outside_target.Clear();
+ }
+}
+
+template <typename LayerType>
+static void ReduceOcclusionBelowSurface(LayerType* contributing_layer,
+ gfx::Rect surface_rect,
+ const gfx::Transform& surface_transform,
+ LayerType* render_target,
+ Region* occlusion_from_inside_target) {
+ if (surface_rect.IsEmpty())
+ return;
+
+ gfx::Rect affected_area_in_target = gfx::ToEnclosingRect(
+ MathUtil::MapClippedRect(surface_transform, gfx::RectF(surface_rect)));
+ if (contributing_layer->render_surface()->is_clipped()) {
+ affected_area_in_target.Intersect(
+ contributing_layer->render_surface()->clip_rect());
+ }
+ if (affected_area_in_target.IsEmpty())
+ return;
+
+ int outset_top, outset_right, outset_bottom, outset_left;
+ contributing_layer->background_filters().GetOutsets(
+ &outset_top, &outset_right, &outset_bottom, &outset_left);
+
+ // The filter can move pixels from outside of the clip, so allow affected_area
+ // to expand outside the clip.
+ affected_area_in_target.Inset(
+ -outset_left, -outset_top, -outset_right, -outset_bottom);
+
+ gfx::Rect FilterOutsetsInTarget(-outset_left,
+ -outset_top,
+ outset_left + outset_right,
+ outset_top + outset_bottom);
+
+ Region affected_occlusion = IntersectRegions(*occlusion_from_inside_target,
+ affected_area_in_target);
+ Region::Iterator affected_occlusion_rects(affected_occlusion);
+
+ occlusion_from_inside_target->Subtract(affected_area_in_target);
+ for (; affected_occlusion_rects.has_rect(); affected_occlusion_rects.next()) {
+ gfx::Rect occlusion_rect = affected_occlusion_rects.rect();
+
+ // Shrink the rect by expanding the non-opaque pixels outside the rect.
+
+ // The left outset of the filters moves pixels on the right side of
+ // the occlusion_rect into it, shrinking its right edge.
+ int shrink_left =
+ occlusion_rect.x() == affected_area_in_target.x() ? 0 : outset_right;
+ int shrink_top =
+ occlusion_rect.y() == affected_area_in_target.y() ? 0 : outset_bottom;
+ int shrink_right =
+ occlusion_rect.right() == affected_area_in_target.right() ?
+ 0 : outset_left;
+ int shrink_bottom =
+ occlusion_rect.bottom() == affected_area_in_target.bottom() ?
+ 0 : outset_top;
+
+ occlusion_rect.Inset(shrink_left, shrink_top, shrink_right, shrink_bottom);
+
+ occlusion_from_inside_target->Union(occlusion_rect);
+ }
+}
+
+template <typename LayerType, typename RenderSurfaceType>
+void OcclusionTrackerBase<LayerType, RenderSurfaceType>::LeaveToRenderTarget(
+ const LayerType* new_target) {
+ int last_index = stack_.size() - 1;
+ bool surface_will_be_at_top_after_pop =
+ stack_.size() > 1 && stack_[last_index - 1].target == new_target;
+
+ // We merge the screen occlusion from the current RenderSurfaceImpl subtree
+ // out to its parent target RenderSurfaceImpl. The target occlusion can be
+ // merged out as well but needs to be transformed to the new target.
+
+ const LayerType* old_target = stack_[last_index].target;
+ const RenderSurfaceType* old_surface = old_target->render_surface();
+
+ Region old_occlusion_from_inside_target_in_new_target =
+ TransformSurfaceOpaqueRegion<RenderSurfaceType>(
+ stack_[last_index].occlusion_from_inside_target,
+ old_surface->is_clipped(),
+ old_surface->clip_rect(),
+ old_surface->draw_transform());
+ if (old_target->has_replica() && !old_target->replica_has_mask()) {
+ old_occlusion_from_inside_target_in_new_target.Union(
+ TransformSurfaceOpaqueRegion<RenderSurfaceType>(
+ stack_[last_index].occlusion_from_inside_target,
+ old_surface->is_clipped(),
+ old_surface->clip_rect(),
+ old_surface->replica_draw_transform()));
+ }
+
+ Region old_occlusion_from_outside_target_in_new_target =
+ TransformSurfaceOpaqueRegion<RenderSurfaceType>(
+ stack_[last_index].occlusion_from_outside_target,
+ false,
+ gfx::Rect(),
+ old_surface->draw_transform());
+
+ gfx::Rect unoccluded_surface_rect;
+ gfx::Rect unoccluded_replica_rect;
+ if (old_target->background_filters().HasFilterThatMovesPixels()) {
+ unoccluded_surface_rect = UnoccludedContributingSurfaceContentRect(
+ old_target, false, old_surface->content_rect(), NULL);
+ if (old_target->has_replica()) {
+ unoccluded_replica_rect = UnoccludedContributingSurfaceContentRect(
+ old_target, true, old_surface->content_rect(), NULL);
+ }
+ }
+
+ if (surface_will_be_at_top_after_pop) {
+ // Merge the top of the stack down.
+ stack_[last_index - 1].occlusion_from_inside_target.Union(
+ old_occlusion_from_inside_target_in_new_target);
+ // TODO(danakj): Strictly this should subtract the inside target occlusion
+ // before union.
+ if (new_target->parent()) {
+ stack_[last_index - 1].occlusion_from_outside_target.Union(
+ old_occlusion_from_outside_target_in_new_target);
+ }
+ stack_.pop_back();
+ } else {
+ // Replace the top of the stack with the new pushed surface.
+ stack_.back().target = new_target;
+ stack_.back().occlusion_from_inside_target =
+ old_occlusion_from_inside_target_in_new_target;
+ if (new_target->parent()) {
+ stack_.back().occlusion_from_outside_target =
+ old_occlusion_from_outside_target_in_new_target;
+ } else {
+ stack_.back().occlusion_from_outside_target.Clear();
+ }
+ }
+
+ if (!old_target->background_filters().HasFilterThatMovesPixels())
+ return;
+
+ ReduceOcclusionBelowSurface(old_target,
+ unoccluded_surface_rect,
+ old_surface->draw_transform(),
+ new_target,
+ &stack_.back().occlusion_from_inside_target);
+ ReduceOcclusionBelowSurface(old_target,
+ unoccluded_surface_rect,
+ old_surface->draw_transform(),
+ new_target,
+ &stack_.back().occlusion_from_outside_target);
+
+ if (!old_target->has_replica())
+ return;
+ ReduceOcclusionBelowSurface(old_target,
+ unoccluded_replica_rect,
+ old_surface->replica_draw_transform(),
+ new_target,
+ &stack_.back().occlusion_from_inside_target);
+ ReduceOcclusionBelowSurface(old_target,
+ unoccluded_replica_rect,
+ old_surface->replica_draw_transform(),
+ new_target,
+ &stack_.back().occlusion_from_outside_target);
+}
+
+template <typename LayerType, typename RenderSurfaceType>
+void OcclusionTrackerBase<LayerType, RenderSurfaceType>::
+ MarkOccludedBehindLayer(const LayerType* layer) {
+ DCHECK(!stack_.empty());
+ DCHECK_EQ(layer->render_target(), stack_.back().target);
+ if (stack_.empty())
+ return;
+
+ if (!LayerOpacityKnown(layer) || layer->draw_opacity() < 1)
+ return;
+
+ if (LayerIsInUnsorted3dRenderingContext(layer))
+ return;
+
+ if (!LayerTransformsToTargetKnown(layer))
+ return;
+
+ Region opaque_contents = layer->VisibleContentOpaqueRegion();
+ if (opaque_contents.IsEmpty())
+ return;
+
+ DCHECK(layer->visible_content_rect().Contains(opaque_contents.bounds()));
+
+ // TODO(danakj): Find a rect interior to each transformed quad.
+ if (!layer->draw_transform().Preserves2dAxisAlignment())
+ return;
+
+ gfx::Rect clip_rect_in_target = ScreenSpaceClipRectInTargetSurface(
+ layer->render_target()->render_surface(), screen_space_clip_rect_);
+ if (layer->is_clipped()) {
+ clip_rect_in_target.Intersect(layer->clip_rect());
+ } else {
+ clip_rect_in_target.Intersect(
+ layer->render_target()->render_surface()->content_rect());
+ }
+
+ for (Region::Iterator opaque_content_rects(opaque_contents);
+ opaque_content_rects.has_rect();
+ opaque_content_rects.next()) {
+ bool clipped;
+ gfx::QuadF transformed_quad = MathUtil::MapQuad(
+ layer->draw_transform(),
+ gfx::QuadF(opaque_content_rects.rect()),
+ &clipped);
+ gfx::Rect transformed_rect =
+ gfx::ToEnclosedRect(transformed_quad.BoundingBox());
+ DCHECK(!clipped); // We only map if the transform preserves axis alignment.
+ transformed_rect.Intersect(clip_rect_in_target);
+ if (transformed_rect.width() < minimum_tracking_size_.width() &&
+ transformed_rect.height() < minimum_tracking_size_.height())
+ continue;
+ stack_.back().occlusion_from_inside_target.Union(transformed_rect);
+
+ if (!occluding_screen_space_rects_)
+ continue;
+
+ // Save the occluding area in screen space for debug visualization.
+ gfx::QuadF screen_space_quad = MathUtil::MapQuad(
+ layer->render_target()->render_surface()->screen_space_transform(),
+ gfx::QuadF(transformed_rect), &clipped);
+ // TODO(danakj): Store the quad in the debug info instead of the bounding
+ // box.
+ gfx::Rect screen_space_rect =
+ gfx::ToEnclosedRect(screen_space_quad.BoundingBox());
+ occluding_screen_space_rects_->push_back(screen_space_rect);
+ }
+
+ if (!non_occluding_screen_space_rects_)
+ return;
+
+ Region non_opaque_contents =
+ SubtractRegions(gfx::Rect(layer->content_bounds()), opaque_contents);
+ for (Region::Iterator non_opaque_content_rects(non_opaque_contents);
+ non_opaque_content_rects.has_rect();
+ non_opaque_content_rects.next()) {
+ // We've already checked for clipping in the MapQuad call above, these calls
+ // should not clip anything further.
+ gfx::Rect transformed_rect = gfx::ToEnclosedRect(
+ MathUtil::MapClippedRect(layer->draw_transform(),
+ gfx::RectF(non_opaque_content_rects.rect())));
+ transformed_rect.Intersect(clip_rect_in_target);
+ if (transformed_rect.IsEmpty())
+ continue;
+
+ bool clipped;
+ gfx::QuadF screen_space_quad = MathUtil::MapQuad(
+ layer->render_target()->render_surface()->screen_space_transform(),
+ gfx::QuadF(transformed_rect),
+ &clipped);
+ // TODO(danakj): Store the quad in the debug info instead of the bounding
+ // box.
+ gfx::Rect screen_space_rect =
+ gfx::ToEnclosedRect(screen_space_quad.BoundingBox());
+ non_occluding_screen_space_rects_->push_back(screen_space_rect);
+ }
+}
+
+template <typename LayerType, typename RenderSurfaceType>
+bool OcclusionTrackerBase<LayerType, RenderSurfaceType>::Occluded(
+ const LayerType* render_target,
+ gfx::Rect content_rect,
+ const gfx::Transform& draw_transform,
+ bool impl_draw_transform_is_unknown,
+ bool is_clipped,
+ gfx::Rect clip_rect_in_target,
+ bool* has_occlusion_from_outside_target_surface) const {
+ if (has_occlusion_from_outside_target_surface)
+ *has_occlusion_from_outside_target_surface = false;
+ if (prevent_occlusion_)
+ return false;
+
+ DCHECK(!stack_.empty());
+ if (stack_.empty())
+ return false;
+ if (content_rect.IsEmpty())
+ return true;
+ if (impl_draw_transform_is_unknown)
+ return false;
+
+ // For tests with no render target.
+ if (!render_target)
+ return false;
+
+ DCHECK_EQ(render_target->render_target(), render_target);
+ DCHECK(render_target->render_surface());
+ DCHECK_EQ(render_target, stack_.back().target);
+
+ gfx::Transform inverse_draw_transform(gfx::Transform::kSkipInitialization);
+ if (!draw_transform.GetInverse(&inverse_draw_transform))
+ return false;
+
+ // Take the ToEnclosingRect at each step, as we want to contain any unoccluded
+ // partial pixels in the resulting Rect.
+ Region unoccluded_region_in_target_surface = gfx::ToEnclosingRect(
+ MathUtil::MapClippedRect(draw_transform, gfx::RectF(content_rect)));
+ // Layers can't clip across surfaces, so count this as internal occlusion.
+ if (is_clipped)
+ unoccluded_region_in_target_surface.Intersect(clip_rect_in_target);
+ unoccluded_region_in_target_surface.Subtract(
+ stack_.back().occlusion_from_inside_target);
+ gfx::RectF unoccluded_rect_in_target_surface_without_outside_occlusion =
+ unoccluded_region_in_target_surface.bounds();
+ unoccluded_region_in_target_surface.Subtract(
+ stack_.back().occlusion_from_outside_target);
+
+ // Treat other clipping as occlusion from outside the surface.
+ // TODO(danakj): Clip to visibleContentRect?
+ unoccluded_region_in_target_surface.Intersect(
+ render_target->render_surface()->content_rect());
+ unoccluded_region_in_target_surface.Intersect(
+ ScreenSpaceClipRectInTargetSurface(render_target->render_surface(),
+ screen_space_clip_rect_));
+
+ gfx::RectF unoccluded_rect_in_target_surface =
+ unoccluded_region_in_target_surface.bounds();
+
+ if (has_occlusion_from_outside_target_surface) {
+ // Check if the unoccluded rect shrank when applying outside occlusion.
+ *has_occlusion_from_outside_target_surface = !gfx::SubtractRects(
+ unoccluded_rect_in_target_surface_without_outside_occlusion,
+ unoccluded_rect_in_target_surface).IsEmpty();
+ }
+
+ return unoccluded_rect_in_target_surface.IsEmpty();
+}
+
+template <typename LayerType, typename RenderSurfaceType>
+gfx::Rect OcclusionTrackerBase<LayerType, RenderSurfaceType>::
+ UnoccludedContentRect(
+ const LayerType* render_target,
+ gfx::Rect content_rect,
+ const gfx::Transform& draw_transform,
+ bool impl_draw_transform_is_unknown,
+ bool is_clipped,
+ gfx::Rect clip_rect_in_target,
+ bool* has_occlusion_from_outside_target_surface) const {
+ if (has_occlusion_from_outside_target_surface)
+ *has_occlusion_from_outside_target_surface = false;
+ if (prevent_occlusion_)
+ return content_rect;
+
+ DCHECK(!stack_.empty());
+ if (stack_.empty())
+ return content_rect;
+ if (content_rect.IsEmpty())
+ return content_rect;
+ if (impl_draw_transform_is_unknown)
+ return content_rect;
+
+ // For tests with no render target.
+ if (!render_target)
+ return content_rect;
+
+ DCHECK_EQ(render_target->render_target(), render_target);
+ DCHECK(render_target->render_surface());
+ DCHECK_EQ(render_target, stack_.back().target);
+
+ gfx::Transform inverse_draw_transform(gfx::Transform::kSkipInitialization);
+ if (!draw_transform.GetInverse(&inverse_draw_transform))
+ return content_rect;
+
+ // Take the ToEnclosingRect at each step, as we want to contain any unoccluded
+ // partial pixels in the resulting Rect.
+ Region unoccluded_region_in_target_surface = gfx::ToEnclosingRect(
+ MathUtil::MapClippedRect(draw_transform, gfx::RectF(content_rect)));
+ // Layers can't clip across surfaces, so count this as internal occlusion.
+ if (is_clipped)
+ unoccluded_region_in_target_surface.Intersect(clip_rect_in_target);
+ unoccluded_region_in_target_surface.Subtract(
+ stack_.back().occlusion_from_inside_target);
+ gfx::RectF unoccluded_rect_in_target_surface_without_outside_occlusion =
+ unoccluded_region_in_target_surface.bounds();
+ unoccluded_region_in_target_surface.Subtract(
+ stack_.back().occlusion_from_outside_target);
+
+ // Treat other clipping as occlusion from outside the surface.
+ // TODO(danakj): Clip to visibleContentRect?
+ unoccluded_region_in_target_surface.Intersect(
+ render_target->render_surface()->content_rect());
+ unoccluded_region_in_target_surface.Intersect(
+ ScreenSpaceClipRectInTargetSurface(render_target->render_surface(),
+ screen_space_clip_rect_));
+
+ gfx::RectF unoccluded_rect_in_target_surface =
+ unoccluded_region_in_target_surface.bounds();
+ gfx::Rect unoccluded_rect = gfx::ToEnclosingRect(
+ MathUtil::ProjectClippedRect(inverse_draw_transform,
+ unoccluded_rect_in_target_surface));
+ unoccluded_rect.Intersect(content_rect);
+
+ if (has_occlusion_from_outside_target_surface) {
+ // Check if the unoccluded rect shrank when applying outside occlusion.
+ *has_occlusion_from_outside_target_surface = !gfx::SubtractRects(
+ unoccluded_rect_in_target_surface_without_outside_occlusion,
+ unoccluded_rect_in_target_surface).IsEmpty();
+ }
+
+ return unoccluded_rect;
+}
+
+template <typename LayerType, typename RenderSurfaceType>
+gfx::Rect OcclusionTrackerBase<LayerType, RenderSurfaceType>::
+ UnoccludedContributingSurfaceContentRect(
+ const LayerType* layer,
+ bool for_replica,
+ gfx::Rect content_rect,
+ bool* has_occlusion_from_outside_target_surface) const {
+ DCHECK(!stack_.empty());
+ // The layer is a contributing render_target so it should have a surface.
+ DCHECK(layer->render_surface());
+ // The layer is a contributing render_target so its target should be itself.
+ DCHECK_EQ(layer->render_target(), layer);
+ // The layer should not be the root, else what is is contributing to?
+ DCHECK(layer->parent());
+ // This should be called while the layer is still considered the current
+ // target in the occlusion tracker.
+ DCHECK_EQ(layer, stack_.back().target);
+
+ if (has_occlusion_from_outside_target_surface)
+ *has_occlusion_from_outside_target_surface = false;
+ if (prevent_occlusion_)
+ return content_rect;
+
+ if (content_rect.IsEmpty())
+ return content_rect;
+
+ const RenderSurfaceType* surface = layer->render_surface();
+ const LayerType* contributing_surface_render_target =
+ layer->parent()->render_target();
+
+ if (!SurfaceTransformsToTargetKnown(surface))
+ return content_rect;
+
+ gfx::Transform draw_transform =
+ for_replica ? surface->replica_draw_transform()
+ : surface->draw_transform();
+ gfx::Transform inverse_draw_transform(gfx::Transform::kSkipInitialization);
+ if (!draw_transform.GetInverse(&inverse_draw_transform))
+ return content_rect;
+
+ // A contributing surface doesn't get occluded by things inside its own
+ // surface, so only things outside the surface can occlude it. That occlusion
+ // is found just below the top of the stack (if it exists).
+ bool has_occlusion = stack_.size() > 1;
+
+ // Take the ToEnclosingRect at each step, as we want to contain any unoccluded
+ // partial pixels in the resulting Rect.
+ Region unoccluded_region_in_target_surface = gfx::ToEnclosingRect(
+ MathUtil::MapClippedRect(draw_transform, gfx::RectF(content_rect)));
+ // Layers can't clip across surfaces, so count this as internal occlusion.
+ if (surface->is_clipped())
+ unoccluded_region_in_target_surface.Intersect(surface->clip_rect());
+ if (has_occlusion) {
+ const StackObject& second_last = stack_[stack_.size() - 2];
+ unoccluded_region_in_target_surface.Subtract(
+ second_last.occlusion_from_inside_target);
+ }
+ gfx::RectF unoccluded_rect_in_target_surface_without_outside_occlusion =
+ unoccluded_region_in_target_surface.bounds();
+ if (has_occlusion) {
+ const StackObject& second_last = stack_[stack_.size() - 2];
+ unoccluded_region_in_target_surface.Subtract(
+ second_last.occlusion_from_outside_target);
+ }
+
+ // Treat other clipping as occlusion from outside the target surface.
+ unoccluded_region_in_target_surface.Intersect(
+ contributing_surface_render_target->render_surface()->content_rect());
+ unoccluded_region_in_target_surface.Intersect(
+ ScreenSpaceClipRectInTargetSurface(
+ contributing_surface_render_target->render_surface(),
+ screen_space_clip_rect_));
+
+ gfx::RectF unoccluded_rect_in_target_surface =
+ unoccluded_region_in_target_surface.bounds();
+ gfx::Rect unoccluded_rect = gfx::ToEnclosingRect(
+ MathUtil::ProjectClippedRect(inverse_draw_transform,
+ unoccluded_rect_in_target_surface));
+ unoccluded_rect.Intersect(content_rect);
+
+ if (has_occlusion_from_outside_target_surface) {
+ // Check if the unoccluded rect shrank when applying outside occlusion.
+ *has_occlusion_from_outside_target_surface = !gfx::SubtractRects(
+ unoccluded_rect_in_target_surface_without_outside_occlusion,
+ unoccluded_rect_in_target_surface).IsEmpty();
+ }
+
+ return unoccluded_rect;
+}
+
+// Instantiate (and export) templates here for the linker.
+template class OcclusionTrackerBase<Layer, RenderSurface>;
+template class OcclusionTrackerBase<LayerImpl, RenderSurfaceImpl>;
+
+} // namespace cc
diff --git a/chromium/cc/trees/occlusion_tracker.h b/chromium/cc/trees/occlusion_tracker.h
new file mode 100644
index 00000000000..132b3fa3160
--- /dev/null
+++ b/chromium/cc/trees/occlusion_tracker.h
@@ -0,0 +1,176 @@
+// 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.
+
+#ifndef CC_TREES_OCCLUSION_TRACKER_H_
+#define CC_TREES_OCCLUSION_TRACKER_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "cc/base/cc_export.h"
+#include "cc/base/region.h"
+#include "cc/layers/layer_iterator.h"
+#include "ui/gfx/rect.h"
+
+namespace cc {
+class OverdrawMetrics;
+class LayerImpl;
+class RenderSurfaceImpl;
+class Layer;
+class RenderSurface;
+
+// This class is used to track occlusion of layers while traversing them in a
+// front-to-back order. As each layer is visited, one of the methods in this
+// class is called to notify it about the current target surface. Then,
+// occlusion in the content space of the current layer may be queried, via
+// methods such as Occluded() and UnoccludedContentRect(). If the current layer
+// owns a RenderSurfaceImpl, then occlusion on that RenderSurfaceImpl may also
+// be queried via surfaceOccluded() and surfaceUnoccludedContentRect(). Finally,
+// once finished with the layer, occlusion behind the layer should be marked by
+// calling MarkOccludedBehindLayer().
+template <typename LayerType, typename RenderSurfaceType>
+class CC_EXPORT OcclusionTrackerBase {
+ public:
+ OcclusionTrackerBase(gfx::Rect screen_space_clip_rect,
+ bool record_metrics_for_frame);
+ ~OcclusionTrackerBase();
+
+ // Called at the beginning of each step in the LayerIterator's front-to-back
+ // traversal. If |prevent_occlusion| is true, the layer will be considered
+ // unoccluded.
+ void EnterLayer(const LayerIteratorPosition<LayerType>& layer_iterator,
+ bool prevent_occlusion);
+ // Called at the end of each step in the LayerIterator's front-to-back
+ // traversal.
+ void LeaveLayer(const LayerIteratorPosition<LayerType>& layer_iterator);
+
+ // Returns true if the given rect in content space for a layer is fully
+ // occluded in either screen space or the layer's target surface.
+ // |render_target| is the contributing layer's render target, and
+ // |draw_transform|, |transformsToTargetKnown| and |clippedRectInTarget| are
+ // relative to that.
+ bool Occluded(const LayerType* render_target,
+ gfx::Rect content_rect,
+ const gfx::Transform& draw_transform,
+ bool impl_draw_transform_is_unknown,
+ bool is_clipped,
+ gfx::Rect clip_rect_in_target,
+ bool* has_occlusion_from_outside_target_surface) const;
+
+ // Gives an unoccluded sub-rect of |content_rect| in the content space of a
+ // layer. Used when considering occlusion for a layer that paints/draws
+ // something. |render_target| is the contributing layer's render target, and
+ // |draw_transform|, |transformsToTargetKnown| and |clippedRectInTarget| are
+ // relative to that.
+ gfx::Rect UnoccludedContentRect(
+ const LayerType* render_target,
+ gfx::Rect content_rect,
+ const gfx::Transform& draw_transform,
+ bool impl_draw_transform_is_unknown,
+ bool is_clipped,
+ gfx::Rect clip_rect_in_target,
+ bool* has_occlusion_from_outside_target_surface) const;
+
+ // Gives an unoccluded sub-rect of |content_rect| in the content space of the
+ // render_target owned by the layer. Used when considering occlusion for a
+ // contributing surface that is rendering into another target.
+ gfx::Rect UnoccludedContributingSurfaceContentRect(
+ const LayerType* layer,
+ bool for_replica,
+ gfx::Rect content_rect,
+ bool* has_occlusion_from_outside_target_surface) const;
+
+ // Report operations for recording overdraw metrics.
+ OverdrawMetrics* overdraw_metrics() const {
+ return overdraw_metrics_.get();
+ }
+
+ // Gives the region of the screen that is not occluded by something opaque.
+ Region ComputeVisibleRegionInScreen() const {
+ DCHECK(!stack_.back().target->parent());
+ return SubtractRegions(screen_space_clip_rect_,
+ stack_.back().occlusion_from_inside_target);
+ }
+
+ void set_minimum_tracking_size(gfx::Size size) {
+ minimum_tracking_size_ = size;
+ }
+
+ // The following is used for visualization purposes.
+ void set_occluding_screen_space_rects_container(
+ std::vector<gfx::Rect>* rects) {
+ occluding_screen_space_rects_ = rects;
+ }
+ void set_non_occluding_screen_space_rects_container(
+ std::vector<gfx::Rect>* rects) {
+ non_occluding_screen_space_rects_ = rects;
+ }
+
+ protected:
+ struct StackObject {
+ StackObject() : target(0) {}
+ explicit StackObject(const LayerType* target) : target(target) {}
+ const LayerType* target;
+ Region occlusion_from_outside_target;
+ Region occlusion_from_inside_target;
+ };
+
+ // The stack holds occluded regions for subtrees in the
+ // RenderSurfaceImpl-Layer tree, so that when we leave a subtree we may apply
+ // a mask to it, but not to the parts outside the subtree.
+ // - The first time we see a new subtree under a target, we add that target to
+ // the top of the stack. This can happen as a layer representing itself, or as
+ // a target surface.
+ // - When we visit a target surface, we apply its mask to its subtree, which
+ // is at the top of the stack.
+ // - When we visit a layer representing itself, we add its occlusion to the
+ // current subtree, which is at the top of the stack.
+ // - When we visit a layer representing a contributing surface, the current
+ // target will never be the top of the stack since we just came from the
+ // contributing surface.
+ // We merge the occlusion at the top of the stack with the new current
+ // subtree. This new target is pushed onto the stack if not already there.
+ std::vector<StackObject> stack_;
+
+ private:
+ // Called when visiting a layer representing itself. If the target was not
+ // already current, then this indicates we have entered a new surface subtree.
+ void EnterRenderTarget(const LayerType* new_target);
+
+ // Called when visiting a layer representing a target surface. This indicates
+ // we have visited all the layers within the surface, and we may perform any
+ // surface-wide operations.
+ void FinishedRenderTarget(const LayerType* finished_target);
+
+ // Called when visiting a layer representing a contributing surface. This
+ // indicates that we are leaving our current surface, and entering the new
+ // one. We then perform any operations required for merging results from the
+ // child subtree into its parent.
+ void LeaveToRenderTarget(const LayerType* new_target);
+
+ // Add the layer's occlusion to the tracked state.
+ void MarkOccludedBehindLayer(const LayerType* layer);
+
+ gfx::Rect screen_space_clip_rect_;
+ scoped_ptr<class OverdrawMetrics> overdraw_metrics_;
+ gfx::Size minimum_tracking_size_;
+ bool prevent_occlusion_;
+
+ // This is used for visualizing the occlusion tracking process.
+ std::vector<gfx::Rect>* occluding_screen_space_rects_;
+ std::vector<gfx::Rect>* non_occluding_screen_space_rects_;
+
+ DISALLOW_COPY_AND_ASSIGN(OcclusionTrackerBase);
+};
+
+typedef OcclusionTrackerBase<Layer, RenderSurface> OcclusionTracker;
+typedef OcclusionTrackerBase<LayerImpl, RenderSurfaceImpl> OcclusionTrackerImpl;
+#if !defined(COMPILER_MSVC)
+extern template class OcclusionTrackerBase<Layer, RenderSurface>;
+extern template class OcclusionTrackerBase<LayerImpl, RenderSurfaceImpl>;
+#endif
+
+} // namespace cc
+
+#endif // CC_TREES_OCCLUSION_TRACKER_H_
diff --git a/chromium/cc/trees/occlusion_tracker_unittest.cc b/chromium/cc/trees/occlusion_tracker_unittest.cc
new file mode 100644
index 00000000000..469d65a4cb2
--- /dev/null
+++ b/chromium/cc/trees/occlusion_tracker_unittest.cc
@@ -0,0 +1,4933 @@
+// 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 "cc/trees/occlusion_tracker.h"
+
+#include "cc/animation/layer_animation_controller.h"
+#include "cc/base/math_util.h"
+#include "cc/debug/overdraw_metrics.h"
+#include "cc/layers/layer.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/output/copy_output_request.h"
+#include "cc/output/copy_output_result.h"
+#include "cc/output/filter_operation.h"
+#include "cc/output/filter_operations.h"
+#include "cc/test/animation_test_common.h"
+#include "cc/test/fake_impl_proxy.h"
+#include "cc/test/fake_layer_tree_host.h"
+#include "cc/test/fake_layer_tree_host_impl.h"
+#include "cc/test/geometry_test_utils.h"
+#include "cc/test/occlusion_tracker_test_common.h"
+#include "cc/trees/layer_tree_host_common.h"
+#include "cc/trees/single_thread_proxy.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+namespace {
+
+class TestContentLayer : public Layer {
+ public:
+ TestContentLayer() : Layer(), override_opaque_contents_rect_(false) {}
+
+ virtual bool DrawsContent() const OVERRIDE { return true; }
+ virtual Region VisibleContentOpaqueRegion() const OVERRIDE {
+ if (override_opaque_contents_rect_)
+ return gfx::IntersectRects(opaque_contents_rect_, visible_content_rect());
+ return Layer::VisibleContentOpaqueRegion();
+ }
+ void SetOpaqueContentsRect(gfx::Rect opaque_contents_rect) {
+ override_opaque_contents_rect_ = true;
+ opaque_contents_rect_ = opaque_contents_rect;
+ }
+
+ private:
+ virtual ~TestContentLayer() {}
+
+ bool override_opaque_contents_rect_;
+ gfx::Rect opaque_contents_rect_;
+};
+
+class TestContentLayerImpl : public LayerImpl {
+ public:
+ TestContentLayerImpl(LayerTreeImpl* tree_impl, int id)
+ : LayerImpl(tree_impl, id), override_opaque_contents_rect_(false) {
+ SetDrawsContent(true);
+ }
+
+ virtual Region VisibleContentOpaqueRegion() const OVERRIDE {
+ if (override_opaque_contents_rect_)
+ return gfx::IntersectRects(opaque_contents_rect_, visible_content_rect());
+ return LayerImpl::VisibleContentOpaqueRegion();
+ }
+ void SetOpaqueContentsRect(gfx::Rect opaque_contents_rect) {
+ override_opaque_contents_rect_ = true;
+ opaque_contents_rect_ = opaque_contents_rect;
+ }
+
+ private:
+ bool override_opaque_contents_rect_;
+ gfx::Rect opaque_contents_rect_;
+};
+
+static inline bool LayerImplDrawTransformIsUnknown(const Layer* layer) {
+ return layer->draw_transform_is_animating();
+}
+static inline bool LayerImplDrawTransformIsUnknown(const LayerImpl* layer) {
+ return false;
+}
+
+template <typename LayerType, typename RenderSurfaceType>
+class TestOcclusionTrackerWithClip
+ : public TestOcclusionTrackerBase<LayerType, RenderSurfaceType> {
+ public:
+ TestOcclusionTrackerWithClip(gfx::Rect viewport_rect,
+ bool record_metrics_for_frame)
+ : TestOcclusionTrackerBase<LayerType, RenderSurfaceType>(
+ viewport_rect,
+ record_metrics_for_frame) {}
+ explicit TestOcclusionTrackerWithClip(gfx::Rect viewport_rect)
+ : TestOcclusionTrackerBase<LayerType, RenderSurfaceType>(viewport_rect,
+ false) {}
+
+ bool OccludedLayer(const LayerType* layer, gfx::Rect content_rect) {
+ bool temp;
+ return OccludedLayer(layer, content_rect, &temp);
+ }
+
+ bool OccludedLayer(const LayerType* layer,
+ gfx::Rect content_rect,
+ bool* has_occlusion_from_outside_target_surface) const {
+ return this->Occluded(layer->render_target(),
+ content_rect,
+ layer->draw_transform(),
+ LayerImplDrawTransformIsUnknown(layer),
+ layer->is_clipped(),
+ layer->clip_rect(),
+ has_occlusion_from_outside_target_surface);
+ }
+ // Gives an unoccluded sub-rect of |content_rect| in the content space of the
+ // layer. Simple wrapper around UnoccludedContentRect.
+ gfx::Rect UnoccludedLayerContentRect(const LayerType* layer,
+ gfx::Rect content_rect) const {
+ bool temp;
+ return UnoccludedLayerContentRect(layer, content_rect, &temp);
+ }
+
+ gfx::Rect UnoccludedLayerContentRect(
+ const LayerType* layer,
+ gfx::Rect content_rect,
+ bool* has_occlusion_from_outside_target_surface) const {
+ return this->UnoccludedContentRect(
+ layer->render_target(),
+ content_rect,
+ layer->draw_transform(),
+ LayerImplDrawTransformIsUnknown(layer),
+ layer->is_clipped(),
+ layer->clip_rect(),
+ has_occlusion_from_outside_target_surface);
+ }
+};
+
+struct OcclusionTrackerTestMainThreadTypes {
+ typedef Layer LayerType;
+ typedef FakeLayerTreeHost HostType;
+ typedef RenderSurface RenderSurfaceType;
+ typedef TestContentLayer ContentLayerType;
+ typedef scoped_refptr<Layer> LayerPtrType;
+ typedef scoped_refptr<ContentLayerType> ContentLayerPtrType;
+ typedef LayerIterator<Layer,
+ RenderSurfaceLayerList,
+ RenderSurface,
+ LayerIteratorActions::FrontToBack> TestLayerIterator;
+ typedef OcclusionTracker OcclusionTrackerType;
+
+ static LayerPtrType CreateLayer(HostType* host) { return Layer::Create(); }
+ static ContentLayerPtrType CreateContentLayer(HostType* host) {
+ return make_scoped_refptr(new ContentLayerType());
+ }
+
+ static LayerPtrType PassLayerPtr(ContentLayerPtrType* layer) {
+ LayerPtrType ref(*layer);
+ *layer = NULL;
+ return ref;
+ }
+
+ static LayerPtrType PassLayerPtr(LayerPtrType* layer) {
+ LayerPtrType ref(*layer);
+ *layer = NULL;
+ return ref;
+ }
+
+ static void DestroyLayer(LayerPtrType* layer) { *layer = NULL; }
+};
+
+struct OcclusionTrackerTestImplThreadTypes {
+ typedef LayerImpl LayerType;
+ typedef LayerTreeImpl HostType;
+ typedef RenderSurfaceImpl RenderSurfaceType;
+ typedef TestContentLayerImpl ContentLayerType;
+ typedef scoped_ptr<LayerImpl> LayerPtrType;
+ typedef scoped_ptr<ContentLayerType> ContentLayerPtrType;
+ typedef LayerIterator<LayerImpl,
+ LayerImplList,
+ RenderSurfaceImpl,
+ LayerIteratorActions::FrontToBack> TestLayerIterator;
+ typedef OcclusionTrackerImpl OcclusionTrackerType;
+
+ static LayerPtrType CreateLayer(HostType* host) {
+ return LayerImpl::Create(host, next_layer_impl_id++);
+ }
+ static ContentLayerPtrType CreateContentLayer(HostType* host) {
+ return make_scoped_ptr(new ContentLayerType(host, next_layer_impl_id++));
+ }
+ static int next_layer_impl_id;
+
+ static LayerPtrType PassLayerPtr(LayerPtrType* layer) {
+ return layer->Pass();
+ }
+
+ static LayerPtrType PassLayerPtr(ContentLayerPtrType* layer) {
+ return layer->PassAs<LayerType>();
+ }
+
+ static void DestroyLayer(LayerPtrType* layer) { layer->reset(); }
+};
+
+int OcclusionTrackerTestImplThreadTypes::next_layer_impl_id = 1;
+
+template <typename Types> class OcclusionTrackerTest : public testing::Test {
+ protected:
+ explicit OcclusionTrackerTest(bool opaque_layers)
+ : opaque_layers_(opaque_layers), host_(FakeLayerTreeHost::Create()) {}
+
+ virtual void RunMyTest() = 0;
+
+ virtual void TearDown() {
+ Types::DestroyLayer(&root_);
+ render_surface_layer_list_.reset();
+ render_surface_layer_list_impl_.clear();
+ replica_layers_.clear();
+ mask_layers_.clear();
+ }
+
+ typename Types::HostType* GetHost();
+
+ typename Types::ContentLayerType* CreateRoot(const gfx::Transform& transform,
+ gfx::PointF position,
+ gfx::Size bounds) {
+ typename Types::ContentLayerPtrType layer(
+ Types::CreateContentLayer(GetHost()));
+ typename Types::ContentLayerType* layer_ptr = layer.get();
+ SetProperties(layer_ptr, transform, position, bounds);
+
+ DCHECK(!root_.get());
+ root_ = Types::PassLayerPtr(&layer);
+
+ SetRootLayerOnMainThread(layer_ptr);
+
+ return layer_ptr;
+ }
+
+ typename Types::LayerType* CreateLayer(typename Types::LayerType* parent,
+ const gfx::Transform& transform,
+ gfx::PointF position,
+ gfx::Size bounds) {
+ typename Types::LayerPtrType layer(Types::CreateLayer(GetHost()));
+ typename Types::LayerType* layer_ptr = layer.get();
+ SetProperties(layer_ptr, transform, position, bounds);
+ parent->AddChild(Types::PassLayerPtr(&layer));
+ return layer_ptr;
+ }
+
+ typename Types::LayerType* CreateSurface(typename Types::LayerType* parent,
+ const gfx::Transform& transform,
+ gfx::PointF position,
+ gfx::Size bounds) {
+ typename Types::LayerType* layer =
+ CreateLayer(parent, transform, position, bounds);
+ layer->SetForceRenderSurface(true);
+ return layer;
+ }
+
+ typename Types::ContentLayerType* CreateDrawingLayer(
+ typename Types::LayerType* parent,
+ const gfx::Transform& transform,
+ gfx::PointF position,
+ gfx::Size bounds,
+ bool opaque) {
+ typename Types::ContentLayerPtrType layer(
+ Types::CreateContentLayer(GetHost()));
+ typename Types::ContentLayerType* layer_ptr = layer.get();
+ SetProperties(layer_ptr, transform, position, bounds);
+
+ if (opaque_layers_) {
+ layer_ptr->SetContentsOpaque(opaque);
+ } else {
+ layer_ptr->SetContentsOpaque(false);
+ if (opaque)
+ layer_ptr->SetOpaqueContentsRect(gfx::Rect(bounds));
+ else
+ layer_ptr->SetOpaqueContentsRect(gfx::Rect());
+ }
+
+ parent->AddChild(Types::PassLayerPtr(&layer));
+ return layer_ptr;
+ }
+
+ typename Types::LayerType* CreateReplicaLayer(
+ typename Types::LayerType* owning_layer,
+ const gfx::Transform& transform,
+ gfx::PointF position,
+ gfx::Size bounds) {
+ typename Types::ContentLayerPtrType layer(
+ Types::CreateContentLayer(GetHost()));
+ typename Types::ContentLayerType* layer_ptr = layer.get();
+ SetProperties(layer_ptr, transform, position, bounds);
+ SetReplica(owning_layer, Types::PassLayerPtr(&layer));
+ return layer_ptr;
+ }
+
+ typename Types::LayerType* CreateMaskLayer(
+ typename Types::LayerType* owning_layer,
+ gfx::Size bounds) {
+ typename Types::ContentLayerPtrType layer(
+ Types::CreateContentLayer(GetHost()));
+ typename Types::ContentLayerType* layer_ptr = layer.get();
+ SetProperties(layer_ptr, identity_matrix, gfx::PointF(), bounds);
+ SetMask(owning_layer, Types::PassLayerPtr(&layer));
+ return layer_ptr;
+ }
+
+ typename Types::ContentLayerType* CreateDrawingSurface(
+ typename Types::LayerType* parent,
+ const gfx::Transform& transform,
+ gfx::PointF position,
+ gfx::Size bounds,
+ bool opaque) {
+ typename Types::ContentLayerType* layer =
+ CreateDrawingLayer(parent, transform, position, bounds, opaque);
+ layer->SetForceRenderSurface(true);
+ return layer;
+ }
+
+
+ void CopyOutputCallback(scoped_ptr<CopyOutputResult> result) {}
+
+ void AddCopyRequest(Layer* layer) {
+ layer->RequestCopyOfOutput(
+ CopyOutputRequest::CreateBitmapRequest(base::Bind(
+ &OcclusionTrackerTest<Types>::CopyOutputCallback,
+ base::Unretained(this))));
+ }
+
+ void AddCopyRequest(LayerImpl* layer) {
+ ScopedPtrVector<CopyOutputRequest> requests;
+ requests.push_back(
+ CopyOutputRequest::CreateBitmapRequest(base::Bind(
+ &OcclusionTrackerTest<Types>::CopyOutputCallback,
+ base::Unretained(this))));
+ layer->PassCopyRequests(&requests);
+ }
+
+ void CalcDrawEtc(TestContentLayerImpl* root) {
+ DCHECK(root == root_.get());
+ DCHECK(!root->render_surface());
+
+ LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
+ root, root->bounds(), &render_surface_layer_list_impl_);
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ layer_iterator_ = layer_iterator_begin_ =
+ Types::TestLayerIterator::Begin(&render_surface_layer_list_impl_);
+ }
+
+ void CalcDrawEtc(TestContentLayer* root) {
+ DCHECK(root == root_.get());
+ DCHECK(!root->render_surface());
+
+ render_surface_layer_list_.reset(new RenderSurfaceLayerList);
+ LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs(
+ root, root->bounds(), render_surface_layer_list_.get());
+ inputs.can_adjust_raster_scales = true;
+ LayerTreeHostCommon::CalculateDrawProperties(&inputs);
+
+ layer_iterator_ = layer_iterator_begin_ =
+ Types::TestLayerIterator::Begin(render_surface_layer_list_.get());
+ }
+
+ void EnterLayer(typename Types::LayerType* layer,
+ typename Types::OcclusionTrackerType* occlusion,
+ bool prevent_occlusion) {
+ ASSERT_EQ(layer, *layer_iterator_);
+ ASSERT_TRUE(layer_iterator_.represents_itself());
+ occlusion->EnterLayer(layer_iterator_, prevent_occlusion);
+ }
+
+ void LeaveLayer(typename Types::LayerType* layer,
+ typename Types::OcclusionTrackerType* occlusion) {
+ ASSERT_EQ(layer, *layer_iterator_);
+ ASSERT_TRUE(layer_iterator_.represents_itself());
+ occlusion->LeaveLayer(layer_iterator_);
+ ++layer_iterator_;
+ }
+
+ void VisitLayer(typename Types::LayerType* layer,
+ typename Types::OcclusionTrackerType* occlusion) {
+ EnterLayer(layer, occlusion, false);
+ LeaveLayer(layer, occlusion);
+ }
+
+ void EnterContributingSurface(
+ typename Types::LayerType* layer,
+ typename Types::OcclusionTrackerType* occlusion,
+ bool prevent_occlusion) {
+ ASSERT_EQ(layer, *layer_iterator_);
+ ASSERT_TRUE(layer_iterator_.represents_target_render_surface());
+ occlusion->EnterLayer(layer_iterator_, false);
+ occlusion->LeaveLayer(layer_iterator_);
+ ++layer_iterator_;
+ ASSERT_TRUE(layer_iterator_.represents_contributing_render_surface());
+ occlusion->EnterLayer(layer_iterator_, prevent_occlusion);
+ }
+
+ void LeaveContributingSurface(
+ typename Types::LayerType* layer,
+ typename Types::OcclusionTrackerType* occlusion) {
+ ASSERT_EQ(layer, *layer_iterator_);
+ ASSERT_TRUE(layer_iterator_.represents_contributing_render_surface());
+ occlusion->LeaveLayer(layer_iterator_);
+ ++layer_iterator_;
+ }
+
+ void VisitContributingSurface(
+ typename Types::LayerType* layer,
+ typename Types::OcclusionTrackerType* occlusion) {
+ EnterContributingSurface(layer, occlusion, false);
+ LeaveContributingSurface(layer, occlusion);
+ }
+
+ void ResetLayerIterator() { layer_iterator_ = layer_iterator_begin_; }
+
+ const gfx::Transform identity_matrix;
+
+ private:
+ void SetRootLayerOnMainThread(Layer* root) {
+ host_->SetRootLayer(scoped_refptr<Layer>(root));
+ }
+
+ void SetRootLayerOnMainThread(LayerImpl* root) {}
+
+ void SetBaseProperties(typename Types::LayerType* layer,
+ const gfx::Transform& transform,
+ gfx::PointF position,
+ gfx::Size bounds) {
+ layer->SetTransform(transform);
+ layer->SetSublayerTransform(gfx::Transform());
+ layer->SetAnchorPoint(gfx::PointF());
+ layer->SetPosition(position);
+ layer->SetBounds(bounds);
+ }
+
+ void SetProperties(Layer* layer,
+ const gfx::Transform& transform,
+ gfx::PointF position,
+ gfx::Size bounds) {
+ SetBaseProperties(layer, transform, position, bounds);
+ }
+
+ void SetProperties(LayerImpl* layer,
+ const gfx::Transform& transform,
+ gfx::PointF position,
+ gfx::Size bounds) {
+ SetBaseProperties(layer, transform, position, bounds);
+
+ layer->SetContentBounds(layer->bounds());
+ }
+
+ void SetReplica(Layer* owning_layer, scoped_refptr<Layer> layer) {
+ owning_layer->SetReplicaLayer(layer.get());
+ replica_layers_.push_back(layer);
+ }
+
+ void SetReplica(LayerImpl* owning_layer, scoped_ptr<LayerImpl> layer) {
+ owning_layer->SetReplicaLayer(layer.Pass());
+ }
+
+ void SetMask(Layer* owning_layer, scoped_refptr<Layer> layer) {
+ owning_layer->SetMaskLayer(layer.get());
+ mask_layers_.push_back(layer);
+ }
+
+ void SetMask(LayerImpl* owning_layer, scoped_ptr<LayerImpl> layer) {
+ owning_layer->SetMaskLayer(layer.Pass());
+ }
+
+ bool opaque_layers_;
+ scoped_ptr<FakeLayerTreeHost> host_;
+ // These hold ownership of the layers for the duration of the test.
+ typename Types::LayerPtrType root_;
+ scoped_ptr<RenderSurfaceLayerList> render_surface_layer_list_;
+ LayerImplList render_surface_layer_list_impl_;
+ typename Types::TestLayerIterator layer_iterator_begin_;
+ typename Types::TestLayerIterator layer_iterator_;
+ typename Types::LayerType* last_layer_visited_;
+ LayerList replica_layers_;
+ LayerList mask_layers_;
+};
+
+template <>
+FakeLayerTreeHost*
+OcclusionTrackerTest<OcclusionTrackerTestMainThreadTypes>::GetHost() {
+ return host_.get();
+}
+
+template <>
+LayerTreeImpl*
+OcclusionTrackerTest<OcclusionTrackerTestImplThreadTypes>::GetHost() {
+ return host_->host_impl()->active_tree();
+}
+
+#define RUN_TEST_MAIN_THREAD_OPAQUE_LAYERS(ClassName) \
+ class ClassName##MainThreadOpaqueLayers \
+ : public ClassName<OcclusionTrackerTestMainThreadTypes> { \
+ public: /* NOLINT(whitespace/indent) */ \
+ ClassName##MainThreadOpaqueLayers() \
+ : ClassName<OcclusionTrackerTestMainThreadTypes>(true) {} \
+ }; \
+ TEST_F(ClassName##MainThreadOpaqueLayers, RunTest) { RunMyTest(); }
+#define RUN_TEST_MAIN_THREAD_OPAQUE_PAINTS(ClassName) \
+ class ClassName##MainThreadOpaquePaints \
+ : public ClassName<OcclusionTrackerTestMainThreadTypes> { \
+ public: /* NOLINT(whitespace/indent) */ \
+ ClassName##MainThreadOpaquePaints() \
+ : ClassName<OcclusionTrackerTestMainThreadTypes>(false) {} \
+ }; \
+ TEST_F(ClassName##MainThreadOpaquePaints, RunTest) { RunMyTest(); }
+
+#define RUN_TEST_IMPL_THREAD_OPAQUE_LAYERS(ClassName) \
+ class ClassName##ImplThreadOpaqueLayers \
+ : public ClassName<OcclusionTrackerTestImplThreadTypes> { \
+ public: /* NOLINT(whitespace/indent) */ \
+ ClassName##ImplThreadOpaqueLayers() \
+ : ClassName<OcclusionTrackerTestImplThreadTypes>(true) {} \
+ }; \
+ TEST_F(ClassName##ImplThreadOpaqueLayers, RunTest) { RunMyTest(); }
+#define RUN_TEST_IMPL_THREAD_OPAQUE_PAINTS(ClassName) \
+ class ClassName##ImplThreadOpaquePaints \
+ : public ClassName<OcclusionTrackerTestImplThreadTypes> { \
+ public: /* NOLINT(whitespace/indent) */ \
+ ClassName##ImplThreadOpaquePaints() \
+ : ClassName<OcclusionTrackerTestImplThreadTypes>(false) {} \
+ }; \
+ TEST_F(ClassName##ImplThreadOpaquePaints, RunTest) { RunMyTest(); }
+
+#define ALL_OCCLUSIONTRACKER_TEST(ClassName) \
+ RUN_TEST_MAIN_THREAD_OPAQUE_LAYERS(ClassName) \
+ RUN_TEST_MAIN_THREAD_OPAQUE_PAINTS(ClassName) \
+ RUN_TEST_IMPL_THREAD_OPAQUE_LAYERS(ClassName) \
+ RUN_TEST_IMPL_THREAD_OPAQUE_PAINTS(ClassName)
+
+#define MAIN_THREAD_TEST(ClassName) \
+ RUN_TEST_MAIN_THREAD_OPAQUE_LAYERS(ClassName)
+
+#define IMPL_THREAD_TEST(ClassName) \
+ RUN_TEST_IMPL_THREAD_OPAQUE_LAYERS(ClassName)
+
+#define MAIN_AND_IMPL_THREAD_TEST(ClassName) \
+ RUN_TEST_MAIN_THREAD_OPAQUE_LAYERS(ClassName) \
+ RUN_TEST_IMPL_THREAD_OPAQUE_LAYERS(ClassName)
+
+template <class Types>
+class OcclusionTrackerTestIdentityTransforms
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestIdentityTransforms(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+
+ void RunMyTest() {
+ typename Types::ContentLayerType* root = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(200, 200));
+ typename Types::ContentLayerType* parent = this->CreateDrawingLayer(
+ root, this->identity_matrix, gfx::PointF(), gfx::Size(100, 100), true);
+ typename Types::ContentLayerType* layer =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(30.f, 30.f),
+ gfx::Size(500, 500),
+ true);
+ parent->SetMasksToBounds(true);
+ this->CalcDrawEtc(root);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000), false);
+
+ this->VisitLayer(layer, &occlusion);
+ this->EnterLayer(parent, &occlusion, false);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(30, 30, 70, 70).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(30, 30, 70, 70)));
+ EXPECT_FALSE(occlusion.OccludedLayer(parent, gfx::Rect(29, 30, 70, 70)));
+ EXPECT_FALSE(occlusion.OccludedLayer(parent, gfx::Rect(30, 29, 70, 70)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(31, 30, 70, 70)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(30, 31, 70, 70)));
+
+ EXPECT_TRUE(occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(30, 30, 70, 70)).IsEmpty());
+ EXPECT_RECT_EQ(gfx::Rect(29, 30, 1, 70),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(29, 30, 70, 70)));
+ EXPECT_RECT_EQ(gfx::Rect(29, 29, 70, 70),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(29, 29, 70, 70)));
+ EXPECT_RECT_EQ(gfx::Rect(30, 29, 70, 1),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(30, 29, 70, 70)));
+ EXPECT_RECT_EQ(gfx::Rect(31, 29, 69, 1),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(31, 29, 70, 70)));
+ EXPECT_RECT_EQ(gfx::Rect(),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(31, 30, 70, 70)));
+ EXPECT_RECT_EQ(gfx::Rect(),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(31, 31, 70, 70)));
+ EXPECT_RECT_EQ(gfx::Rect(),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(30, 31, 70, 70)));
+ EXPECT_RECT_EQ(gfx::Rect(29, 31, 1, 69),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(29, 31, 70, 70)));
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestIdentityTransforms);
+
+template <class Types>
+class OcclusionTrackerTestQuadsMismatchLayer
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestQuadsMismatchLayer(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ gfx::Transform layer_transform;
+ layer_transform.Translate(10.0, 10.0);
+
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::Point(0, 0), gfx::Size(100, 100));
+ typename Types::ContentLayerType* layer1 = this->CreateDrawingLayer(
+ parent, layer_transform, gfx::PointF(), gfx::Size(90, 90), true);
+ typename Types::ContentLayerType* layer2 = this->CreateDrawingLayer(
+ layer1, layer_transform, gfx::PointF(), gfx::Size(50, 50), true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->VisitLayer(layer2, &occlusion);
+ this->EnterLayer(layer1, &occlusion, false);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(20, 20, 50, 50).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ // This checks cases where the quads don't match their "containing"
+ // layers, e.g. in terms of transforms or clip rect. This is typical for
+ // DelegatedRendererLayer.
+
+ gfx::Transform quad_transform;
+ quad_transform.Translate(30.0, 30.0);
+ gfx::Rect clip_rect_in_target(0, 0, 100, 100);
+
+ EXPECT_TRUE(occlusion.UnoccludedContentRect(parent,
+ gfx::Rect(0, 0, 10, 10),
+ quad_transform,
+ false,
+ true,
+ clip_rect_in_target,
+ NULL).IsEmpty());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 10, 10),
+ occlusion.UnoccludedContentRect(parent,
+ gfx::Rect(0, 0, 10, 10),
+ quad_transform,
+ true,
+ true,
+ clip_rect_in_target,
+ NULL));
+ EXPECT_RECT_EQ(gfx::Rect(40, 40, 10, 10),
+ occlusion.UnoccludedContentRect(parent,
+ gfx::Rect(40, 40, 10, 10),
+ quad_transform,
+ false,
+ true,
+ clip_rect_in_target,
+ NULL));
+ EXPECT_RECT_EQ(gfx::Rect(40, 30, 5, 10),
+ occlusion.UnoccludedContentRect(parent,
+ gfx::Rect(35, 30, 10, 10),
+ quad_transform,
+ false,
+ true,
+ clip_rect_in_target,
+ NULL));
+ EXPECT_RECT_EQ(gfx::Rect(40, 40, 5, 5),
+ occlusion.UnoccludedContentRect(parent,
+ gfx::Rect(40, 40, 10, 10),
+ quad_transform,
+ false,
+ true,
+ gfx::Rect(0, 0, 75, 75),
+ NULL));
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestQuadsMismatchLayer);
+
+template <class Types>
+class OcclusionTrackerTestRotatedChild : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestRotatedChild(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ gfx::Transform layer_transform;
+ layer_transform.Translate(250.0, 250.0);
+ layer_transform.Rotate(90.0);
+ layer_transform.Translate(-250.0, -250.0);
+
+ typename Types::ContentLayerType* root = this->CreateRoot(
+ this->identity_matrix, gfx::Point(0, 0), gfx::Size(200, 200));
+ typename Types::ContentLayerType* parent = this->CreateDrawingLayer(
+ root, this->identity_matrix, gfx::PointF(), gfx::Size(100, 100), true);
+ typename Types::ContentLayerType* layer =
+ this->CreateDrawingLayer(parent,
+ layer_transform,
+ gfx::PointF(30.f, 30.f),
+ gfx::Size(500, 500),
+ true);
+ parent->SetMasksToBounds(true);
+ this->CalcDrawEtc(root);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->VisitLayer(layer, &occlusion);
+ this->EnterLayer(parent, &occlusion, false);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(30, 30, 70, 70).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(30, 30, 70, 70)));
+ EXPECT_FALSE(occlusion.OccludedLayer(parent, gfx::Rect(29, 30, 70, 70)));
+ EXPECT_FALSE(occlusion.OccludedLayer(parent, gfx::Rect(30, 29, 70, 70)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(31, 30, 70, 70)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(30, 31, 70, 70)));
+
+ EXPECT_TRUE(occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(30, 30, 70, 70)).IsEmpty());
+ EXPECT_RECT_EQ(gfx::Rect(29, 30, 1, 70),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(29, 30, 70, 70)));
+ EXPECT_RECT_EQ(gfx::Rect(29, 29, 70, 70),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(29, 29, 70, 70)));
+ EXPECT_RECT_EQ(gfx::Rect(30, 29, 70, 1),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(30, 29, 70, 70)));
+ EXPECT_RECT_EQ(gfx::Rect(31, 29, 69, 1),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(31, 29, 70, 70)));
+ EXPECT_RECT_EQ(gfx::Rect(),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(31, 30, 70, 70)));
+ EXPECT_RECT_EQ(gfx::Rect(),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(31, 31, 70, 70)));
+ EXPECT_RECT_EQ(gfx::Rect(),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(30, 31, 70, 70)));
+ EXPECT_RECT_EQ(gfx::Rect(29, 31, 1, 69),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(29, 31, 70, 70)));
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestRotatedChild);
+
+template <class Types>
+class OcclusionTrackerTestTranslatedChild : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestTranslatedChild(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ gfx::Transform layer_transform;
+ layer_transform.Translate(20.0, 20.0);
+
+ typename Types::ContentLayerType* root = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(200, 200));
+ typename Types::ContentLayerType* parent = this->CreateDrawingLayer(
+ root, this->identity_matrix, gfx::PointF(), gfx::Size(100, 100), true);
+ typename Types::ContentLayerType* layer =
+ this->CreateDrawingLayer(parent,
+ layer_transform,
+ gfx::PointF(30.f, 30.f),
+ gfx::Size(500, 500),
+ true);
+ parent->SetMasksToBounds(true);
+ this->CalcDrawEtc(root);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->VisitLayer(layer, &occlusion);
+ this->EnterLayer(parent, &occlusion, false);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(50, 50, 50, 50).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(50, 50, 50, 50)));
+ EXPECT_FALSE(occlusion.OccludedLayer(parent, gfx::Rect(49, 50, 50, 50)));
+ EXPECT_FALSE(occlusion.OccludedLayer(parent, gfx::Rect(50, 49, 50, 50)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(51, 50, 50, 50)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(50, 51, 50, 50)));
+
+ EXPECT_TRUE(occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(50, 50, 50, 50)).IsEmpty());
+ EXPECT_RECT_EQ(gfx::Rect(49, 50, 1, 50),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(49, 50, 50, 50)));
+ EXPECT_RECT_EQ(gfx::Rect(49, 49, 50, 50),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(49, 49, 50, 50)));
+ EXPECT_RECT_EQ(gfx::Rect(50, 49, 50, 1),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(50, 49, 50, 50)));
+ EXPECT_RECT_EQ(gfx::Rect(51, 49, 49, 1),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(51, 49, 50, 50)));
+ EXPECT_TRUE(occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(51, 50, 50, 50)).IsEmpty());
+ EXPECT_TRUE(occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(51, 51, 50, 50)).IsEmpty());
+ EXPECT_TRUE(occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(50, 51, 50, 50)).IsEmpty());
+ EXPECT_RECT_EQ(gfx::Rect(49, 51, 1, 49),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(49, 51, 50, 50)));
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestTranslatedChild);
+
+template <class Types>
+class OcclusionTrackerTestChildInRotatedChild
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestChildInRotatedChild(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ gfx::Transform child_transform;
+ child_transform.Translate(250.0, 250.0);
+ child_transform.Rotate(90.0);
+ child_transform.Translate(-250.0, -250.0);
+
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(100, 100));
+ parent->SetMasksToBounds(true);
+ typename Types::LayerType* child = this->CreateSurface(
+ parent, child_transform, gfx::PointF(30.f, 30.f), gfx::Size(500, 500));
+ child->SetMasksToBounds(true);
+ typename Types::ContentLayerType* layer =
+ this->CreateDrawingLayer(child,
+ this->identity_matrix,
+ gfx::PointF(10.f, 10.f),
+ gfx::Size(500, 500),
+ true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->VisitLayer(layer, &occlusion);
+ this->EnterContributingSurface(child, &occlusion, false);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(10, 430, 60, 70).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ this->LeaveContributingSurface(child, &occlusion);
+ this->EnterLayer(parent, &occlusion, false);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(30, 40, 70, 60).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(30, 40, 70, 60)));
+ EXPECT_FALSE(occlusion.OccludedLayer(parent, gfx::Rect(29, 40, 70, 60)));
+ EXPECT_FALSE(occlusion.OccludedLayer(parent, gfx::Rect(30, 39, 70, 60)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(31, 40, 70, 60)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(30, 41, 70, 60)));
+
+ /* Justification for the above occlusion from |layer|:
+ 100
+ +---------------------+
+ | |
+ | 30 | rotate(90)
+ | 30 + ---------------------------------+
+ 100 | | 10 | | ==>
+ | |10+---------------------------------+
+ | | | | | |
+ | | | | | |
+ | | | | | |
+ +----|--|-------------+ | |
+ | | | |
+ | | | |
+ | | | |500
+ | | | |
+ | | | |
+ | | | |
+ | | | |
+ +--|-------------------------------+ |
+ | |
+ +---------------------------------+
+ 500
+
+ +---------------------+
+ | |30 Visible region of |layer|: /////
+ | |
+ | +---------------------------------+
+ 100| | |10 |
+ | +---------------------------------+ |
+ | | |///////////////| 420 | |
+ | | |///////////////|60 | |
+ | | |///////////////| | |
+ +--|--|---------------+ | |
+ 20|10| 70 | |
+ | | | |
+ | | | |
+ | | | |
+ | | | |
+ | | | |
+ | | |10|
+ | +------------------------------|--+
+ | 490 |
+ +---------------------------------+
+ 500
+
+ */
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestChildInRotatedChild);
+
+template <class Types>
+class OcclusionTrackerTestScaledRenderSurface
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestScaledRenderSurface(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+
+ void RunMyTest() {
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(200, 200));
+
+ gfx::Transform layer1_matrix;
+ layer1_matrix.Scale(2.0, 2.0);
+ typename Types::ContentLayerType* layer1 = this->CreateDrawingLayer(
+ parent, layer1_matrix, gfx::PointF(), gfx::Size(100, 100), true);
+ layer1->SetForceRenderSurface(true);
+
+ gfx::Transform layer2_matrix;
+ layer2_matrix.Translate(25.0, 25.0);
+ typename Types::ContentLayerType* layer2 = this->CreateDrawingLayer(
+ layer1, layer2_matrix, gfx::PointF(), gfx::Size(50, 50), true);
+ typename Types::ContentLayerType* occluder =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(100.f, 100.f),
+ gfx::Size(500, 500),
+ true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->VisitLayer(occluder, &occlusion);
+ this->EnterLayer(layer2, &occlusion, false);
+
+ EXPECT_EQ(gfx::Rect(100, 100, 100, 100).ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ EXPECT_RECT_EQ(
+ gfx::Rect(0, 0, 25, 25),
+ occlusion.UnoccludedLayerContentRect(layer2, gfx::Rect(0, 0, 25, 25)));
+ EXPECT_RECT_EQ(gfx::Rect(10, 25, 15, 25),
+ occlusion.UnoccludedLayerContentRect(
+ layer2, gfx::Rect(10, 25, 25, 25)));
+ EXPECT_RECT_EQ(gfx::Rect(25, 10, 25, 15),
+ occlusion.UnoccludedLayerContentRect(
+ layer2, gfx::Rect(25, 10, 25, 25)));
+ EXPECT_TRUE(occlusion.UnoccludedLayerContentRect(
+ layer2, gfx::Rect(25, 25, 25, 25)).IsEmpty());
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestScaledRenderSurface);
+
+template <class Types>
+class OcclusionTrackerTestVisitTargetTwoTimes
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestVisitTargetTwoTimes(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ gfx::Transform child_transform;
+ child_transform.Translate(250.0, 250.0);
+ child_transform.Rotate(90.0);
+ child_transform.Translate(-250.0, -250.0);
+
+ typename Types::ContentLayerType* root = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(200, 200));
+ typename Types::ContentLayerType* parent = this->CreateDrawingLayer(
+ root, this->identity_matrix, gfx::PointF(), gfx::Size(100, 100), true);
+ parent->SetMasksToBounds(true);
+ typename Types::LayerType* child = this->CreateSurface(
+ parent, child_transform, gfx::PointF(30.f, 30.f), gfx::Size(500, 500));
+ child->SetMasksToBounds(true);
+ typename Types::ContentLayerType* layer =
+ this->CreateDrawingLayer(child,
+ this->identity_matrix,
+ gfx::PointF(10.f, 10.f),
+ gfx::Size(500, 500),
+ true);
+ // |child2| makes |parent|'s surface get considered by OcclusionTracker
+ // first, instead of |child|'s. This exercises different code in
+ // LeaveToRenderTarget, as the target surface has already been seen.
+ typename Types::ContentLayerType* child2 =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(30.f, 30.f),
+ gfx::Size(60, 20),
+ true);
+ this->CalcDrawEtc(root);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->VisitLayer(child2, &occlusion);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(30, 30, 60, 20).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ this->VisitLayer(layer, &occlusion);
+
+ EXPECT_EQ(gfx::Rect(0, 440, 20, 60).ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(10, 430, 60, 70).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ this->EnterContributingSurface(child, &occlusion, false);
+
+ EXPECT_EQ(gfx::Rect(0, 440, 20, 60).ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(10, 430, 60, 70).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ // Occlusion in |child2| should get merged with the |child| surface we are
+ // leaving now.
+ this->LeaveContributingSurface(child, &occlusion);
+ this->EnterLayer(parent, &occlusion, false);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(UnionRegions(gfx::Rect(30, 30, 60, 10), gfx::Rect(30, 40, 70, 60))
+ .ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ EXPECT_FALSE(occlusion.OccludedLayer(parent, gfx::Rect(30, 30, 70, 70)));
+ EXPECT_RECT_EQ(gfx::Rect(90, 30, 10, 10),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(30, 30, 70, 70)));
+
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(30, 30, 60, 10)));
+ EXPECT_FALSE(occlusion.OccludedLayer(parent, gfx::Rect(29, 30, 60, 10)));
+ EXPECT_FALSE(occlusion.OccludedLayer(parent, gfx::Rect(30, 29, 60, 10)));
+ EXPECT_FALSE(occlusion.OccludedLayer(parent, gfx::Rect(31, 30, 60, 10)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(30, 31, 60, 10)));
+
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(30, 40, 70, 60)));
+ EXPECT_FALSE(occlusion.OccludedLayer(parent, gfx::Rect(29, 40, 70, 60)));
+ EXPECT_FALSE(occlusion.OccludedLayer(parent, gfx::Rect(30, 39, 70, 60)));
+
+ EXPECT_TRUE(occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(30, 30, 60, 10)).IsEmpty());
+ EXPECT_RECT_EQ(gfx::Rect(29, 30, 1, 10),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(29, 30, 60, 10)));
+ EXPECT_RECT_EQ(gfx::Rect(30, 29, 60, 1),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(30, 29, 60, 10)));
+ EXPECT_RECT_EQ(gfx::Rect(90, 30, 1, 10),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(31, 30, 60, 10)));
+ EXPECT_TRUE(occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(30, 31, 60, 10)).IsEmpty());
+
+ EXPECT_TRUE(occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(30, 40, 70, 60)).IsEmpty());
+ EXPECT_RECT_EQ(gfx::Rect(29, 40, 1, 60),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(29, 40, 70, 60)));
+ // This rect is mostly occluded by |child2|.
+ EXPECT_RECT_EQ(gfx::Rect(90, 39, 10, 1),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(30, 39, 70, 60)));
+ // This rect extends past top/right ends of |child2|.
+ EXPECT_RECT_EQ(gfx::Rect(30, 29, 70, 11),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(30, 29, 70, 70)));
+ // This rect extends past left/right ends of |child2|.
+ EXPECT_RECT_EQ(gfx::Rect(20, 39, 80, 60),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(20, 39, 80, 60)));
+ EXPECT_RECT_EQ(gfx::Rect(),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(31, 40, 70, 60)));
+ EXPECT_RECT_EQ(gfx::Rect(),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(30, 41, 70, 60)));
+
+ /* Justification for the above occlusion from |layer|:
+ 100
+ +---------------------+
+ | |
+ | 30 | rotate(90)
+ | 30 + ------------+--------------------+
+ 100 | | 10 | | | ==>
+ | |10+----------|----------------------+
+ | + ------------+ | | |
+ | | | | | |
+ | | | | | |
+ +----|--|-------------+ | |
+ | | | |
+ | | | |
+ | | | |500
+ | | | |
+ | | | |
+ | | | |
+ | | | |
+ +--|-------------------------------+ |
+ | |
+ +---------------------------------+
+ 500
+
+
+ +---------------------+
+ | |30 Visible region of |layer|: /////
+ | 30 60 | |child2|: \\\\\
+ | 30 +------------+--------------------+
+ | |\\\\\\\\\\\\| |10 |
+ | +--|\\\\\\\\\\\\|-----------------+ |
+ | | +------------+//| 420 | |
+ | | |///////////////|60 | |
+ | | |///////////////| | |
+ +--|--|---------------+ | |
+ 20|10| 70 | |
+ | | | |
+ | | | |
+ | | | |
+ | | | |
+ | | | |
+ | | |10|
+ | +------------------------------|--+
+ | 490 |
+ +---------------------------------+
+ 500
+ */
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestVisitTargetTwoTimes);
+
+template <class Types>
+class OcclusionTrackerTestSurfaceRotatedOffAxis
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestSurfaceRotatedOffAxis(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ gfx::Transform child_transform;
+ child_transform.Translate(250.0, 250.0);
+ child_transform.Rotate(95.0);
+ child_transform.Translate(-250.0, -250.0);
+
+ gfx::Transform layer_transform;
+ layer_transform.Translate(10.0, 10.0);
+
+ typename Types::ContentLayerType* root = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(1000, 1000));
+ typename Types::ContentLayerType* parent = this->CreateDrawingLayer(
+ root, this->identity_matrix, gfx::PointF(), gfx::Size(100, 100), true);
+ typename Types::LayerType* child = this->CreateLayer(
+ parent, child_transform, gfx::PointF(30.f, 30.f), gfx::Size(500, 500));
+ child->SetMasksToBounds(true);
+ typename Types::ContentLayerType* layer = this->CreateDrawingLayer(
+ child, layer_transform, gfx::PointF(), gfx::Size(500, 500), true);
+ this->CalcDrawEtc(root);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ gfx::Rect clipped_layer_in_child = MathUtil::MapClippedRect(
+ layer_transform, layer->visible_content_rect());
+
+ this->VisitLayer(layer, &occlusion);
+ this->EnterContributingSurface(child, &occlusion, false);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(clipped_layer_in_child.ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ this->LeaveContributingSurface(child, &occlusion);
+ this->EnterLayer(parent, &occlusion, false);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ EXPECT_FALSE(occlusion.OccludedLayer(parent, gfx::Rect(75, 55, 1, 1)));
+ EXPECT_RECT_EQ(
+ gfx::Rect(75, 55, 1, 1),
+ occlusion.UnoccludedLayerContentRect(parent, gfx::Rect(75, 55, 1, 1)));
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestSurfaceRotatedOffAxis);
+
+template <class Types>
+class OcclusionTrackerTestSurfaceWithTwoOpaqueChildren
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestSurfaceWithTwoOpaqueChildren(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ gfx::Transform child_transform;
+ child_transform.Translate(250.0, 250.0);
+ child_transform.Rotate(90.0);
+ child_transform.Translate(-250.0, -250.0);
+
+ typename Types::ContentLayerType* root = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(1000, 1000));
+ typename Types::ContentLayerType* parent = this->CreateDrawingLayer(
+ root, this->identity_matrix, gfx::PointF(), gfx::Size(100, 100), true);
+ parent->SetMasksToBounds(true);
+ typename Types::ContentLayerType* child =
+ this->CreateDrawingSurface(parent,
+ child_transform,
+ gfx::PointF(30.f, 30.f),
+ gfx::Size(500, 500),
+ false);
+ child->SetMasksToBounds(true);
+ typename Types::ContentLayerType* layer1 =
+ this->CreateDrawingLayer(child,
+ this->identity_matrix,
+ gfx::PointF(10.f, 10.f),
+ gfx::Size(500, 500),
+ true);
+ typename Types::ContentLayerType* layer2 =
+ this->CreateDrawingLayer(child,
+ this->identity_matrix,
+ gfx::PointF(10.f, 450.f),
+ gfx::Size(500, 60),
+ true);
+ this->CalcDrawEtc(root);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->VisitLayer(layer2, &occlusion);
+ this->VisitLayer(layer1, &occlusion);
+ this->VisitLayer(child, &occlusion);
+ this->EnterContributingSurface(child, &occlusion, false);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(10, 430, 60, 70).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ EXPECT_TRUE(occlusion.OccludedLayer(child, gfx::Rect(10, 430, 60, 70)));
+ EXPECT_FALSE(occlusion.OccludedLayer(child, gfx::Rect(9, 430, 60, 70)));
+ // These rects are occluded except for the part outside the bounds of the
+ // target surface.
+ EXPECT_TRUE(occlusion.OccludedLayer(child, gfx::Rect(10, 429, 60, 70)));
+ EXPECT_TRUE(occlusion.OccludedLayer(child, gfx::Rect(11, 430, 60, 70)));
+ EXPECT_TRUE(occlusion.OccludedLayer(child, gfx::Rect(10, 431, 60, 70)));
+
+ EXPECT_TRUE(occlusion.UnoccludedLayerContentRect(
+ child, gfx::Rect(10, 430, 60, 70)).IsEmpty());
+ EXPECT_RECT_EQ(
+ gfx::Rect(9, 430, 1, 70),
+ occlusion.UnoccludedLayerContentRect(child, gfx::Rect(9, 430, 60, 70)));
+ // These rects are occluded except for the part outside the bounds of the
+ // target surface.
+ EXPECT_RECT_EQ(gfx::Rect(),
+ occlusion.UnoccludedLayerContentRect(
+ child, gfx::Rect(10, 429, 60, 70)));
+ EXPECT_RECT_EQ(gfx::Rect(),
+ occlusion.UnoccludedLayerContentRect(
+ child, gfx::Rect(11, 430, 60, 70)));
+ EXPECT_RECT_EQ(gfx::Rect(),
+ occlusion.UnoccludedLayerContentRect(
+ child, gfx::Rect(10, 431, 60, 70)));
+
+ this->LeaveContributingSurface(child, &occlusion);
+ this->EnterLayer(parent, &occlusion, false);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(30, 40, 70, 60).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(30, 40, 70, 60)));
+ EXPECT_FALSE(occlusion.OccludedLayer(parent, gfx::Rect(29, 40, 70, 60)));
+ EXPECT_FALSE(occlusion.OccludedLayer(parent, gfx::Rect(30, 39, 70, 60)));
+
+ EXPECT_TRUE(occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(30, 40, 70, 60)).IsEmpty());
+ EXPECT_RECT_EQ(gfx::Rect(29, 40, 1, 60),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(29, 40, 70, 60)));
+ EXPECT_RECT_EQ(gfx::Rect(30, 39, 70, 1),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(30, 39, 70, 60)));
+ EXPECT_RECT_EQ(gfx::Rect(),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(31, 40, 70, 60)));
+ EXPECT_RECT_EQ(gfx::Rect(),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(30, 41, 70, 60)));
+
+ /* Justification for the above occlusion from |layer1| and |layer2|:
+
+ +---------------------+
+ | |30 Visible region of |layer1|: /////
+ | | Visible region of |layer2|: \\\\\
+ | +---------------------------------+
+ | | |10 |
+ | +---------------+-----------------+ |
+ | | |\\\\\\\\\\\\|//| 420 | |
+ | | |\\\\\\\\\\\\|//|60 | |
+ | | |\\\\\\\\\\\\|//| | |
+ +--|--|------------|--+ | |
+ 20|10| 70 | | |
+ | | | | |
+ | | | | |
+ | | | | |
+ | | | | |
+ | | | | |
+ | | | |10|
+ | +------------|-----------------|--+
+ | | 490 |
+ +---------------+-----------------+
+ 60 440
+ */
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestSurfaceWithTwoOpaqueChildren);
+
+template <class Types>
+class OcclusionTrackerTestOverlappingSurfaceSiblings
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestOverlappingSurfaceSiblings(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ gfx::Transform child_transform;
+ child_transform.Translate(250.0, 250.0);
+ child_transform.Rotate(90.0);
+ child_transform.Translate(-250.0, -250.0);
+
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(100, 100));
+ parent->SetMasksToBounds(true);
+ typename Types::LayerType* child1 = this->CreateSurface(
+ parent, child_transform, gfx::PointF(30.f, 30.f), gfx::Size(10, 10));
+ typename Types::LayerType* child2 = this->CreateSurface(
+ parent, child_transform, gfx::PointF(20.f, 40.f), gfx::Size(10, 10));
+ typename Types::ContentLayerType* layer1 =
+ this->CreateDrawingLayer(child1,
+ this->identity_matrix,
+ gfx::PointF(-10.f, -10.f),
+ gfx::Size(510, 510),
+ true);
+ typename Types::ContentLayerType* layer2 =
+ this->CreateDrawingLayer(child2,
+ this->identity_matrix,
+ gfx::PointF(-10.f, -10.f),
+ gfx::Size(510, 510),
+ true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->VisitLayer(layer2, &occlusion);
+ this->EnterContributingSurface(child2, &occlusion, false);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(-10, 420, 70, 80).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ EXPECT_TRUE(occlusion.OccludedLayer(child2, gfx::Rect(-10, 420, 70, 80)));
+ EXPECT_TRUE(occlusion.OccludedLayer(child2, gfx::Rect(-11, 420, 70, 80)));
+ EXPECT_TRUE(occlusion.OccludedLayer(child2, gfx::Rect(-10, 419, 70, 80)));
+ EXPECT_TRUE(occlusion.OccludedLayer(child2, gfx::Rect(-10, 420, 71, 80)));
+ EXPECT_TRUE(occlusion.OccludedLayer(child2, gfx::Rect(-10, 420, 70, 81)));
+
+ // There is nothing above child2's surface in the z-order.
+ EXPECT_RECT_EQ(gfx::Rect(-10, 420, 70, 80),
+ occlusion.UnoccludedContributingSurfaceContentRect(
+ child2, false, gfx::Rect(-10, 420, 70, 80), NULL));
+
+ this->LeaveContributingSurface(child2, &occlusion);
+ this->VisitLayer(layer1, &occlusion);
+ this->EnterContributingSurface(child1, &occlusion, false);
+
+ EXPECT_EQ(gfx::Rect(0, 430, 70, 80).ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(-10, 430, 80, 70).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ // child2's contents will occlude child1 below it.
+ EXPECT_RECT_EQ(gfx::Rect(-10, 430, 10, 70),
+ occlusion.UnoccludedContributingSurfaceContentRect(
+ child1, false, gfx::Rect(-10, 430, 80, 70), NULL));
+
+ this->LeaveContributingSurface(child1, &occlusion);
+ this->EnterLayer(parent, &occlusion, false);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(UnionRegions(gfx::Rect(30, 20, 70, 10), gfx::Rect(20, 30, 80, 70))
+ .ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ EXPECT_FALSE(occlusion.OccludedLayer(parent, gfx::Rect(20, 20, 80, 80)));
+
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(30, 20, 70, 80)));
+ EXPECT_FALSE(occlusion.OccludedLayer(parent, gfx::Rect(29, 20, 70, 80)));
+ EXPECT_FALSE(occlusion.OccludedLayer(parent, gfx::Rect(30, 19, 70, 80)));
+
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(20, 30, 80, 70)));
+ EXPECT_FALSE(occlusion.OccludedLayer(parent, gfx::Rect(19, 30, 80, 70)));
+ EXPECT_FALSE(occlusion.OccludedLayer(parent, gfx::Rect(20, 29, 80, 70)));
+
+ /* Justification for the above occlusion:
+ 100
+ +---------------------+
+ | 20 | layer1
+ | 30+ ---------------------------------+
+ 100 | 30| | layer2 |
+ |20+----------------------------------+ |
+ | | | | | |
+ | | | | | |
+ | | | | | |
+ +--|-|----------------+ | |
+ | | | | 510
+ | | | |
+ | | | |
+ | | | |
+ | | | |
+ | | | |
+ | | | |
+ | +--------------------------------|-+
+ | |
+ +----------------------------------+
+ 510
+ */
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestOverlappingSurfaceSiblings);
+
+template <class Types>
+class OcclusionTrackerTestOverlappingSurfaceSiblingsWithTwoTransforms
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestOverlappingSurfaceSiblingsWithTwoTransforms(
+ bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ gfx::Transform child1_transform;
+ child1_transform.Translate(250.0, 250.0);
+ child1_transform.Rotate(-90.0);
+ child1_transform.Translate(-250.0, -250.0);
+
+ gfx::Transform child2_transform;
+ child2_transform.Translate(250.0, 250.0);
+ child2_transform.Rotate(90.0);
+ child2_transform.Translate(-250.0, -250.0);
+
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(100, 100));
+ parent->SetMasksToBounds(true);
+ typename Types::LayerType* child1 = this->CreateSurface(
+ parent, child1_transform, gfx::PointF(30.f, 20.f), gfx::Size(10, 10));
+ typename Types::LayerType* child2 =
+ this->CreateDrawingSurface(parent,
+ child2_transform,
+ gfx::PointF(20.f, 40.f),
+ gfx::Size(10, 10),
+ false);
+ typename Types::ContentLayerType* layer1 =
+ this->CreateDrawingLayer(child1,
+ this->identity_matrix,
+ gfx::PointF(-10.f, -20.f),
+ gfx::Size(510, 510),
+ true);
+ typename Types::ContentLayerType* layer2 =
+ this->CreateDrawingLayer(child2,
+ this->identity_matrix,
+ gfx::PointF(-10.f, -10.f),
+ gfx::Size(510, 510),
+ true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->VisitLayer(layer2, &occlusion);
+ this->EnterLayer(child2, &occlusion, false);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(-10, 420, 70, 80).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ this->LeaveLayer(child2, &occlusion);
+ this->EnterContributingSurface(child2, &occlusion, false);
+
+ // There is nothing above child2's surface in the z-order.
+ EXPECT_RECT_EQ(gfx::Rect(-10, 420, 70, 80),
+ occlusion.UnoccludedContributingSurfaceContentRect(
+ child2, false, gfx::Rect(-10, 420, 70, 80), NULL));
+
+ this->LeaveContributingSurface(child2, &occlusion);
+ this->VisitLayer(layer1, &occlusion);
+ this->EnterContributingSurface(child1, &occlusion, false);
+
+ EXPECT_EQ(gfx::Rect(420, -10, 70, 80).ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(420, -20, 80, 90).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ // child2's contents will occlude child1 below it.
+ EXPECT_RECT_EQ(gfx::Rect(420, -20, 80, 90),
+ occlusion.UnoccludedContributingSurfaceContentRect(
+ child1, false, gfx::Rect(420, -20, 80, 90), NULL));
+ EXPECT_RECT_EQ(gfx::Rect(490, -10, 10, 80),
+ occlusion.UnoccludedContributingSurfaceContentRect(
+ child1, false, gfx::Rect(420, -10, 80, 90), NULL));
+ EXPECT_RECT_EQ(gfx::Rect(420, -20, 70, 10),
+ occlusion.UnoccludedContributingSurfaceContentRect(
+ child1, false, gfx::Rect(420, -20, 70, 90), NULL));
+
+ this->LeaveContributingSurface(child1, &occlusion);
+ this->EnterLayer(parent, &occlusion, false);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(10, 20, 90, 80).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ /* Justification for the above occlusion:
+ 100
+ +---------------------+
+ |20 | layer1
+ 10+----------------------------------+
+ 100 || 30 | layer2 |
+ |20+----------------------------------+
+ || | | | |
+ || | | | |
+ || | | | |
+ +|-|------------------+ | |
+ | | | | 510
+ | | 510 | |
+ | | | |
+ | | | |
+ | | | |
+ | | | |
+ | | 520 | |
+ +----------------------------------+ |
+ | |
+ +----------------------------------+
+ 510
+ */
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(
+ OcclusionTrackerTestOverlappingSurfaceSiblingsWithTwoTransforms);
+
+template <class Types>
+class OcclusionTrackerTestFilters : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestFilters(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ gfx::Transform layer_transform;
+ layer_transform.Translate(250.0, 250.0);
+ layer_transform.Rotate(90.0);
+ layer_transform.Translate(-250.0, -250.0);
+
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(100, 100));
+ parent->SetMasksToBounds(true);
+ typename Types::ContentLayerType* blur_layer =
+ this->CreateDrawingLayer(parent,
+ layer_transform,
+ gfx::PointF(30.f, 30.f),
+ gfx::Size(500, 500),
+ true);
+ typename Types::ContentLayerType* opaque_layer =
+ this->CreateDrawingLayer(parent,
+ layer_transform,
+ gfx::PointF(30.f, 30.f),
+ gfx::Size(500, 500),
+ true);
+ typename Types::ContentLayerType* opacity_layer =
+ this->CreateDrawingLayer(parent,
+ layer_transform,
+ gfx::PointF(30.f, 30.f),
+ gfx::Size(500, 500),
+ true);
+
+ FilterOperations filters;
+ filters.Append(FilterOperation::CreateBlurFilter(10.f));
+ blur_layer->SetFilters(filters);
+
+ filters.Clear();
+ filters.Append(FilterOperation::CreateGrayscaleFilter(0.5f));
+ opaque_layer->SetFilters(filters);
+
+ filters.Clear();
+ filters.Append(FilterOperation::CreateOpacityFilter(0.5f));
+ opacity_layer->SetFilters(filters);
+
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ // Opacity layer won't contribute to occlusion.
+ this->VisitLayer(opacity_layer, &occlusion);
+ this->EnterContributingSurface(opacity_layer, &occlusion, false);
+
+ EXPECT_TRUE(occlusion.occlusion_from_outside_target().IsEmpty());
+ EXPECT_TRUE(occlusion.occlusion_from_inside_target().IsEmpty());
+
+ // And has nothing to contribute to its parent surface.
+ this->LeaveContributingSurface(opacity_layer, &occlusion);
+ EXPECT_TRUE(occlusion.occlusion_from_outside_target().IsEmpty());
+ EXPECT_TRUE(occlusion.occlusion_from_inside_target().IsEmpty());
+
+ // Opaque layer will contribute to occlusion.
+ this->VisitLayer(opaque_layer, &occlusion);
+ this->EnterContributingSurface(opaque_layer, &occlusion, false);
+
+ EXPECT_TRUE(occlusion.occlusion_from_outside_target().IsEmpty());
+ EXPECT_EQ(gfx::Rect(0, 430, 70, 70).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ // And it gets translated to the parent surface.
+ this->LeaveContributingSurface(opaque_layer, &occlusion);
+ EXPECT_TRUE(occlusion.occlusion_from_outside_target().IsEmpty());
+ EXPECT_EQ(gfx::Rect(30, 30, 70, 70).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ // The blur layer needs to throw away any occlusion from outside its
+ // subtree.
+ this->EnterLayer(blur_layer, &occlusion, false);
+ EXPECT_TRUE(occlusion.occlusion_from_outside_target().IsEmpty());
+ EXPECT_TRUE(occlusion.occlusion_from_inside_target().IsEmpty());
+
+ // And it won't contribute to occlusion.
+ this->LeaveLayer(blur_layer, &occlusion);
+ this->EnterContributingSurface(blur_layer, &occlusion, false);
+ EXPECT_TRUE(occlusion.occlusion_from_outside_target().IsEmpty());
+ EXPECT_TRUE(occlusion.occlusion_from_inside_target().IsEmpty());
+
+ // But the opaque layer's occlusion is preserved on the parent.
+ this->LeaveContributingSurface(blur_layer, &occlusion);
+ this->EnterLayer(parent, &occlusion, false);
+ EXPECT_TRUE(occlusion.occlusion_from_outside_target().IsEmpty());
+ EXPECT_EQ(gfx::Rect(30, 30, 70, 70).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestFilters);
+
+template <class Types>
+class OcclusionTrackerTestReplicaDoesOcclude
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestReplicaDoesOcclude(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(100, 200));
+ typename Types::LayerType* surface =
+ this->CreateDrawingSurface(parent,
+ this->identity_matrix,
+ gfx::PointF(0.f, 100.f),
+ gfx::Size(50, 50),
+ true);
+ this->CreateReplicaLayer(
+ surface, this->identity_matrix, gfx::PointF(50.f, 50.f), gfx::Size());
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->VisitLayer(surface, &occlusion);
+
+ EXPECT_EQ(gfx::Rect(0, 0, 50, 50).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ this->VisitContributingSurface(surface, &occlusion);
+ this->EnterLayer(parent, &occlusion, false);
+
+ // The surface and replica should both be occluding the parent.
+ EXPECT_EQ(
+ UnionRegions(gfx::Rect(0, 100, 50, 50),
+ gfx::Rect(50, 150, 50, 50)).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestReplicaDoesOcclude);
+
+template <class Types>
+class OcclusionTrackerTestReplicaWithClipping
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestReplicaWithClipping(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(100, 170));
+ parent->SetMasksToBounds(true);
+ typename Types::LayerType* surface =
+ this->CreateDrawingSurface(parent,
+ this->identity_matrix,
+ gfx::PointF(0.f, 100.f),
+ gfx::Size(50, 50),
+ true);
+ this->CreateReplicaLayer(
+ surface, this->identity_matrix, gfx::PointF(50.f, 50.f), gfx::Size());
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->VisitLayer(surface, &occlusion);
+
+ EXPECT_EQ(gfx::Rect(0, 0, 50, 50).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ this->VisitContributingSurface(surface, &occlusion);
+ this->EnterLayer(parent, &occlusion, false);
+
+ // The surface and replica should both be occluding the parent.
+ EXPECT_EQ(
+ UnionRegions(gfx::Rect(0, 100, 50, 50),
+ gfx::Rect(50, 150, 50, 20)).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestReplicaWithClipping);
+
+template <class Types>
+class OcclusionTrackerTestReplicaWithMask : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestReplicaWithMask(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(100, 200));
+ typename Types::LayerType* surface =
+ this->CreateDrawingSurface(parent,
+ this->identity_matrix,
+ gfx::PointF(0.f, 100.f),
+ gfx::Size(50, 50),
+ true);
+ typename Types::LayerType* replica = this->CreateReplicaLayer(
+ surface, this->identity_matrix, gfx::PointF(50.f, 50.f), gfx::Size());
+ this->CreateMaskLayer(replica, gfx::Size(10, 10));
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->VisitLayer(surface, &occlusion);
+
+ EXPECT_EQ(gfx::Rect(0, 0, 50, 50).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ this->VisitContributingSurface(surface, &occlusion);
+ this->EnterLayer(parent, &occlusion, false);
+
+ // The replica should not be occluding the parent, since it has a mask
+ // applied to it.
+ EXPECT_EQ(gfx::Rect(0, 100, 50, 50).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestReplicaWithMask);
+
+template <class Types>
+class OcclusionTrackerTestLayerClipRectOutsideChild
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestLayerClipRectOutsideChild(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(300, 300));
+ typename Types::ContentLayerType* clip =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(200.f, 100.f),
+ gfx::Size(100, 100),
+ false);
+ clip->SetMasksToBounds(true);
+ typename Types::ContentLayerType* layer =
+ this->CreateDrawingLayer(clip,
+ this->identity_matrix,
+ gfx::PointF(-200.f, -100.f),
+ gfx::Size(200, 200),
+ false);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->EnterLayer(layer, &occlusion, false);
+
+ EXPECT_TRUE(occlusion.OccludedLayer(layer, gfx::Rect(0, 0, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(layer, gfx::Rect(100, 0, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(layer, gfx::Rect(0, 100, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(layer, gfx::Rect(100, 100, 100, 100)));
+ EXPECT_FALSE(occlusion.OccludedLayer(layer, gfx::Rect(200, 100, 100, 100)));
+
+ this->LeaveLayer(layer, &occlusion);
+ this->EnterLayer(clip, &occlusion, false);
+
+ EXPECT_TRUE(occlusion.OccludedLayer(clip, gfx::Rect(-100, 0, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(clip, gfx::Rect(0, -100, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(clip, gfx::Rect(100, 0, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(clip, gfx::Rect(0, 100, 100, 100)));
+ EXPECT_FALSE(occlusion.OccludedLayer(clip, gfx::Rect(0, 0, 100, 100)));
+
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 100),
+ occlusion.UnoccludedLayerContentRect(
+ clip, gfx::Rect(-100, -100, 300, 300)));
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestLayerClipRectOutsideChild);
+
+template <class Types>
+class OcclusionTrackerTestViewportRectOutsideChild
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestViewportRectOutsideChild(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(300, 300));
+ typename Types::ContentLayerType* layer =
+ this->CreateDrawingSurface(parent,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(200, 200),
+ true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(200, 100, 100, 100));
+
+ this->EnterLayer(layer, &occlusion, false);
+
+ EXPECT_TRUE(occlusion.OccludedLayer(layer, gfx::Rect(0, 0, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(layer, gfx::Rect(100, 0, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(layer, gfx::Rect(0, 100, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(layer, gfx::Rect(100, 100, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(layer, gfx::Rect(200, 100, 100, 100)));
+
+ this->LeaveLayer(layer, &occlusion);
+ this->VisitContributingSurface(layer, &occlusion);
+ this->EnterLayer(parent, &occlusion, false);
+
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(0, 0, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(0, 100, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(100, 0, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(0, 100, 100, 100)));
+ EXPECT_FALSE(
+ occlusion.OccludedLayer(parent, gfx::Rect(200, 100, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(200, 0, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(0, 200, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(100, 200, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(200, 200, 100, 100)));
+
+ EXPECT_RECT_EQ(gfx::Rect(200, 100, 100, 100),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(0, 0, 300, 300)));
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestViewportRectOutsideChild);
+
+template <class Types>
+class OcclusionTrackerTestLayerClipRectOverChild
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestLayerClipRectOverChild(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(300, 300));
+ typename Types::ContentLayerType* clip =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(100.f, 100.f),
+ gfx::Size(100, 100),
+ false);
+ clip->SetMasksToBounds(true);
+ typename Types::ContentLayerType* layer =
+ this->CreateDrawingSurface(clip,
+ this->identity_matrix,
+ gfx::PointF(-100.f, -100.f),
+ gfx::Size(200, 200),
+ true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->EnterLayer(layer, &occlusion, false);
+
+ EXPECT_TRUE(occlusion.OccludedLayer(layer, gfx::Rect(0, 0, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(layer, gfx::Rect(0, 100, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(layer, gfx::Rect(100, 0, 100, 100)));
+ EXPECT_FALSE(occlusion.OccludedLayer(layer, gfx::Rect(100, 100, 100, 100)));
+
+ this->LeaveLayer(layer, &occlusion);
+ this->VisitContributingSurface(layer, &occlusion);
+
+ EXPECT_EQ(gfx::Rect(100, 100, 100, 100).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ this->EnterLayer(clip, &occlusion, false);
+
+ EXPECT_TRUE(occlusion.OccludedLayer(clip, gfx::Rect(0, 0, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(clip, gfx::Rect(0, 100, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(clip, gfx::Rect(100, 0, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(clip, gfx::Rect(100, 100, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(clip, gfx::Rect(200, 100, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(clip, gfx::Rect(200, 0, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(clip, gfx::Rect(0, 200, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(clip, gfx::Rect(100, 200, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(clip, gfx::Rect(200, 200, 100, 100)));
+
+ EXPECT_TRUE(occlusion.UnoccludedLayerContentRect(
+ clip, gfx::Rect(0, 0, 300, 300)).IsEmpty());
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestLayerClipRectOverChild);
+
+template <class Types>
+class OcclusionTrackerTestViewportRectOverChild
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestViewportRectOverChild(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(300, 300));
+ typename Types::ContentLayerType* layer =
+ this->CreateDrawingSurface(parent,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(200, 200),
+ true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(100, 100, 100, 100));
+
+ this->EnterLayer(layer, &occlusion, false);
+
+ EXPECT_TRUE(occlusion.OccludedLayer(layer, gfx::Rect(0, 0, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(layer, gfx::Rect(0, 100, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(layer, gfx::Rect(100, 0, 100, 100)));
+ EXPECT_FALSE(occlusion.OccludedLayer(layer, gfx::Rect(100, 100, 100, 100)));
+
+ this->LeaveLayer(layer, &occlusion);
+ this->VisitContributingSurface(layer, &occlusion);
+ this->EnterLayer(parent, &occlusion, false);
+
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(0, 0, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(0, 100, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(100, 0, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(100, 100, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(200, 100, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(200, 0, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(0, 200, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(100, 200, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(200, 200, 100, 100)));
+
+ EXPECT_TRUE(occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(0, 0, 300, 300)).IsEmpty());
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestViewportRectOverChild);
+
+template <class Types>
+class OcclusionTrackerTestLayerClipRectPartlyOverChild
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestLayerClipRectPartlyOverChild(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(300, 300));
+ typename Types::ContentLayerType* clip =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(50.f, 50.f),
+ gfx::Size(200, 200),
+ false);
+ clip->SetMasksToBounds(true);
+ typename Types::ContentLayerType* layer =
+ this->CreateDrawingSurface(clip,
+ this->identity_matrix,
+ gfx::PointF(-50.f, -50.f),
+ gfx::Size(200, 200),
+ true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->EnterLayer(layer, &occlusion, false);
+
+ EXPECT_FALSE(occlusion.OccludedLayer(layer, gfx::Rect(0, 0, 100, 100)));
+ EXPECT_FALSE(occlusion.OccludedLayer(layer, gfx::Rect(0, 100, 100, 100)));
+ EXPECT_FALSE(occlusion.OccludedLayer(layer, gfx::Rect(100, 0, 100, 100)));
+ EXPECT_FALSE(occlusion.OccludedLayer(layer, gfx::Rect(100, 100, 100, 100)));
+
+ EXPECT_TRUE(occlusion.OccludedLayer(layer, gfx::Rect(0, 0, 100, 50)));
+ EXPECT_TRUE(occlusion.OccludedLayer(layer, gfx::Rect(0, 0, 50, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(layer, gfx::Rect(100, 0, 100, 50)));
+ EXPECT_TRUE(occlusion.OccludedLayer(layer, gfx::Rect(0, 100, 50, 100)));
+
+ this->LeaveLayer(layer, &occlusion);
+ this->VisitContributingSurface(layer, &occlusion);
+ this->EnterLayer(clip, &occlusion, false);
+
+ EXPECT_EQ(gfx::Rect(50, 50, 150, 150).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestLayerClipRectPartlyOverChild);
+
+template <class Types>
+class OcclusionTrackerTestViewportRectPartlyOverChild
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestViewportRectPartlyOverChild(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(300, 300));
+ typename Types::ContentLayerType* layer =
+ this->CreateDrawingSurface(parent,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(200, 200),
+ true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(50, 50, 200, 200));
+
+ this->EnterLayer(layer, &occlusion, false);
+
+ EXPECT_FALSE(occlusion.OccludedLayer(layer, gfx::Rect(0, 0, 100, 100)));
+ EXPECT_FALSE(occlusion.OccludedLayer(layer, gfx::Rect(0, 100, 100, 100)));
+ EXPECT_FALSE(occlusion.OccludedLayer(layer, gfx::Rect(100, 0, 100, 100)));
+ EXPECT_FALSE(occlusion.OccludedLayer(layer, gfx::Rect(100, 100, 100, 100)));
+
+ this->LeaveLayer(layer, &occlusion);
+ this->VisitContributingSurface(layer, &occlusion);
+ this->EnterLayer(parent, &occlusion, false);
+
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(0, 0, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(0, 100, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(100, 0, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(100, 100, 100, 100)));
+ EXPECT_FALSE(
+ occlusion.OccludedLayer(parent, gfx::Rect(200, 100, 100, 100)));
+ EXPECT_FALSE(occlusion.OccludedLayer(parent, gfx::Rect(200, 0, 100, 100)));
+ EXPECT_FALSE(occlusion.OccludedLayer(parent, gfx::Rect(0, 200, 100, 100)));
+ EXPECT_FALSE(
+ occlusion.OccludedLayer(parent, gfx::Rect(100, 200, 100, 100)));
+ EXPECT_FALSE(
+ occlusion.OccludedLayer(parent, gfx::Rect(200, 200, 100, 100)));
+
+ EXPECT_RECT_EQ(gfx::Rect(50, 50, 200, 200),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(0, 0, 300, 300)));
+ EXPECT_RECT_EQ(gfx::Rect(200, 50, 50, 50),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(0, 0, 300, 100)));
+ EXPECT_RECT_EQ(gfx::Rect(200, 100, 50, 100),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(0, 100, 300, 100)));
+ EXPECT_RECT_EQ(gfx::Rect(200, 100, 50, 100),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(200, 100, 100, 100)));
+ EXPECT_RECT_EQ(gfx::Rect(100, 200, 100, 50),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(100, 200, 100, 100)));
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestViewportRectPartlyOverChild);
+
+template <class Types>
+class OcclusionTrackerTestViewportRectOverNothing
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestViewportRectOverNothing(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(300, 300));
+ typename Types::ContentLayerType* layer =
+ this->CreateDrawingSurface(parent,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(200, 200),
+ true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(500, 500, 100, 100));
+
+ this->EnterLayer(layer, &occlusion, false);
+
+ EXPECT_TRUE(occlusion.OccludedLayer(layer, gfx::Rect(0, 0, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(layer, gfx::Rect(0, 100, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(layer, gfx::Rect(100, 0, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(layer, gfx::Rect(100, 100, 100, 100)));
+
+ this->LeaveLayer(layer, &occlusion);
+ this->VisitContributingSurface(layer, &occlusion);
+ this->EnterLayer(parent, &occlusion, false);
+
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(0, 0, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(0, 100, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(100, 0, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(100, 100, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(200, 100, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(200, 0, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(0, 200, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(100, 200, 100, 100)));
+ EXPECT_TRUE(occlusion.OccludedLayer(parent, gfx::Rect(200, 200, 100, 100)));
+
+ EXPECT_TRUE(occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(0, 0, 300, 300)).IsEmpty());
+ EXPECT_TRUE(occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(0, 0, 300, 100)).IsEmpty());
+ EXPECT_TRUE(occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(0, 100, 300, 100)).IsEmpty());
+ EXPECT_TRUE(occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(200, 100, 100, 100)).IsEmpty());
+ EXPECT_TRUE(occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(100, 200, 100, 100)).IsEmpty());
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestViewportRectOverNothing);
+
+template <class Types>
+class OcclusionTrackerTestLayerClipRectForLayerOffOrigin
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestLayerClipRectForLayerOffOrigin(
+ bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(300, 300));
+ typename Types::ContentLayerType* layer =
+ this->CreateDrawingSurface(parent,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(200, 200),
+ true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+ this->EnterLayer(layer, &occlusion, false);
+
+ // This layer is translated when drawn into its target. So if the clip rect
+ // given from the target surface is not in that target space, then after
+ // translating these query rects into the target, they will fall outside the
+ // clip and be considered occluded.
+ EXPECT_FALSE(occlusion.OccludedLayer(layer, gfx::Rect(0, 0, 100, 100)));
+ EXPECT_FALSE(occlusion.OccludedLayer(layer, gfx::Rect(0, 100, 100, 100)));
+ EXPECT_FALSE(occlusion.OccludedLayer(layer, gfx::Rect(100, 0, 100, 100)));
+ EXPECT_FALSE(occlusion.OccludedLayer(layer, gfx::Rect(100, 100, 100, 100)));
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestLayerClipRectForLayerOffOrigin);
+
+template <class Types>
+class OcclusionTrackerTestOpaqueContentsRegionEmpty
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestOpaqueContentsRegionEmpty(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(300, 300));
+ typename Types::ContentLayerType* layer =
+ this->CreateDrawingSurface(parent,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(200, 200),
+ false);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+ this->EnterLayer(layer, &occlusion, false);
+
+ EXPECT_FALSE(occlusion.OccludedLayer(layer, gfx::Rect(0, 0, 100, 100)));
+ EXPECT_FALSE(occlusion.OccludedLayer(layer, gfx::Rect(100, 0, 100, 100)));
+ EXPECT_FALSE(occlusion.OccludedLayer(layer, gfx::Rect(0, 100, 100, 100)));
+ EXPECT_FALSE(occlusion.OccludedLayer(layer, gfx::Rect(100, 100, 100, 100)));
+
+ // Occluded since its outside the surface bounds.
+ EXPECT_TRUE(occlusion.OccludedLayer(layer, gfx::Rect(200, 100, 100, 100)));
+
+ this->LeaveLayer(layer, &occlusion);
+ this->VisitContributingSurface(layer, &occlusion);
+ this->EnterLayer(parent, &occlusion, false);
+
+ EXPECT_TRUE(occlusion.occlusion_from_outside_target().IsEmpty());
+ }
+};
+
+MAIN_AND_IMPL_THREAD_TEST(OcclusionTrackerTestOpaqueContentsRegionEmpty);
+
+template <class Types>
+class OcclusionTrackerTestOpaqueContentsRegionNonEmpty
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestOpaqueContentsRegionNonEmpty(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(300, 300));
+ typename Types::ContentLayerType* layer =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(100.f, 100.f),
+ gfx::Size(200, 200),
+ false);
+ this->CalcDrawEtc(parent);
+ {
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+ layer->SetOpaqueContentsRect(gfx::Rect(0, 0, 100, 100));
+
+ this->ResetLayerIterator();
+ this->VisitLayer(layer, &occlusion);
+ this->EnterLayer(parent, &occlusion, false);
+
+ EXPECT_EQ(gfx::Rect(100, 100, 100, 100).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ EXPECT_FALSE(
+ occlusion.OccludedLayer(parent, gfx::Rect(0, 100, 100, 100)));
+ EXPECT_TRUE(
+ occlusion.OccludedLayer(parent, gfx::Rect(100, 100, 100, 100)));
+ EXPECT_FALSE(
+ occlusion.OccludedLayer(parent, gfx::Rect(200, 200, 100, 100)));
+ }
+ {
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+ layer->SetOpaqueContentsRect(gfx::Rect(20, 20, 180, 180));
+
+ this->ResetLayerIterator();
+ this->VisitLayer(layer, &occlusion);
+ this->EnterLayer(parent, &occlusion, false);
+
+ EXPECT_EQ(gfx::Rect(120, 120, 180, 180).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ EXPECT_FALSE(
+ occlusion.OccludedLayer(parent, gfx::Rect(0, 100, 100, 100)));
+ EXPECT_FALSE(
+ occlusion.OccludedLayer(parent, gfx::Rect(100, 100, 100, 100)));
+ EXPECT_TRUE(
+ occlusion.OccludedLayer(parent, gfx::Rect(200, 200, 100, 100)));
+ }
+ {
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+ layer->SetOpaqueContentsRect(gfx::Rect(150, 150, 100, 100));
+
+ this->ResetLayerIterator();
+ this->VisitLayer(layer, &occlusion);
+ this->EnterLayer(parent, &occlusion, false);
+
+ EXPECT_EQ(gfx::Rect(250, 250, 50, 50).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ EXPECT_FALSE(
+ occlusion.OccludedLayer(parent, gfx::Rect(0, 100, 100, 100)));
+ EXPECT_FALSE(
+ occlusion.OccludedLayer(parent, gfx::Rect(100, 100, 100, 100)));
+ EXPECT_FALSE(
+ occlusion.OccludedLayer(parent, gfx::Rect(200, 200, 100, 100)));
+ }
+ }
+};
+
+MAIN_AND_IMPL_THREAD_TEST(OcclusionTrackerTestOpaqueContentsRegionNonEmpty);
+
+template <class Types>
+class OcclusionTrackerTest3dTransform : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTest3dTransform(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ gfx::Transform transform;
+ transform.RotateAboutYAxis(30.0);
+
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(300, 300));
+ typename Types::LayerType* container = this->CreateLayer(
+ parent, this->identity_matrix, gfx::PointF(), gfx::Size(300, 300));
+ typename Types::ContentLayerType* layer =
+ this->CreateDrawingLayer(container,
+ transform,
+ gfx::PointF(100.f, 100.f),
+ gfx::Size(200, 200),
+ true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+ this->EnterLayer(layer, &occlusion, false);
+
+ // The layer is rotated in 3d but without preserving 3d, so it only gets
+ // resized.
+ EXPECT_RECT_EQ(
+ gfx::Rect(0, 0, 200, 200),
+ occlusion.UnoccludedLayerContentRect(layer, gfx::Rect(0, 0, 200, 200)));
+ }
+};
+
+MAIN_AND_IMPL_THREAD_TEST(OcclusionTrackerTest3dTransform);
+
+template <class Types>
+class OcclusionTrackerTestUnsorted3dLayers
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestUnsorted3dLayers(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ // Currently, The main thread layer iterator does not iterate over 3d items
+ // in sorted order, because layer sorting is not performed on the main
+ // thread. Because of this, the occlusion tracker cannot assume that a 3d
+ // layer occludes other layers that have not yet been iterated over. For
+ // now, the expected behavior is that a 3d layer simply does not add any
+ // occlusion to the occlusion tracker.
+
+ gfx::Transform translation_to_front;
+ translation_to_front.Translate3d(0.0, 0.0, -10.0);
+ gfx::Transform translation_to_back;
+ translation_to_front.Translate3d(0.0, 0.0, -100.0);
+
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(300, 300));
+ typename Types::ContentLayerType* child1 = this->CreateDrawingLayer(
+ parent, translation_to_back, gfx::PointF(), gfx::Size(100, 100), true);
+ typename Types::ContentLayerType* child2 =
+ this->CreateDrawingLayer(parent,
+ translation_to_front,
+ gfx::PointF(50.f, 50.f),
+ gfx::Size(100, 100),
+ true);
+ parent->SetPreserves3d(true);
+
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+ this->VisitLayer(child2, &occlusion);
+ EXPECT_TRUE(occlusion.occlusion_from_outside_target().IsEmpty());
+ EXPECT_TRUE(occlusion.occlusion_from_inside_target().IsEmpty());
+
+ this->VisitLayer(child1, &occlusion);
+ EXPECT_TRUE(occlusion.occlusion_from_outside_target().IsEmpty());
+ EXPECT_TRUE(occlusion.occlusion_from_inside_target().IsEmpty());
+ }
+};
+
+// This test will have different layer ordering on the impl thread; the test
+// will only work on the main thread.
+MAIN_THREAD_TEST(OcclusionTrackerTestUnsorted3dLayers);
+
+template <class Types>
+class OcclusionTrackerTestPerspectiveTransform
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestPerspectiveTransform(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ gfx::Transform transform;
+ transform.Translate(150.0, 150.0);
+ transform.ApplyPerspectiveDepth(400.0);
+ transform.RotateAboutXAxis(-30.0);
+ transform.Translate(-150.0, -150.0);
+
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(300, 300));
+ typename Types::LayerType* container = this->CreateLayer(
+ parent, this->identity_matrix, gfx::PointF(), gfx::Size(300, 300));
+ typename Types::ContentLayerType* layer =
+ this->CreateDrawingLayer(container,
+ transform,
+ gfx::PointF(100.f, 100.f),
+ gfx::Size(200, 200),
+ true);
+ container->SetPreserves3d(true);
+ layer->SetPreserves3d(true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+ this->EnterLayer(layer, &occlusion, false);
+
+ EXPECT_RECT_EQ(
+ gfx::Rect(0, 0, 200, 200),
+ occlusion.UnoccludedLayerContentRect(layer, gfx::Rect(0, 0, 200, 200)));
+ }
+};
+
+// This test requires accumulating occlusion of 3d layers, which are skipped by
+// the occlusion tracker on the main thread. So this test should run on the impl
+// thread.
+IMPL_THREAD_TEST(OcclusionTrackerTestPerspectiveTransform);
+
+template <class Types>
+class OcclusionTrackerTestPerspectiveTransformBehindCamera
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestPerspectiveTransformBehindCamera(
+ bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ // This test is based on the platform/chromium/compositing/3d-corners.html
+ // layout test.
+ gfx::Transform transform;
+ transform.Translate(250.0, 50.0);
+ transform.ApplyPerspectiveDepth(10.0);
+ transform.Translate(-250.0, -50.0);
+ transform.Translate(250.0, 50.0);
+ transform.RotateAboutXAxis(-167.0);
+ transform.Translate(-250.0, -50.0);
+
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(500, 100));
+ typename Types::LayerType* container = this->CreateLayer(
+ parent, this->identity_matrix, gfx::PointF(), gfx::Size(500, 500));
+ typename Types::ContentLayerType* layer = this->CreateDrawingLayer(
+ container, transform, gfx::PointF(), gfx::Size(500, 500), true);
+ container->SetPreserves3d(true);
+ layer->SetPreserves3d(true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+ this->EnterLayer(layer, &occlusion, false);
+
+ // The bottom 11 pixel rows of this layer remain visible inside the
+ // container, after translation to the target surface. When translated back,
+ // this will include many more pixels but must include at least the bottom
+ // 11 rows.
+ EXPECT_TRUE(occlusion.UnoccludedLayerContentRect(
+ layer, gfx::Rect(0, 0, 500, 500)).Contains(gfx::Rect(0, 489, 500, 11)));
+ }
+};
+
+// This test requires accumulating occlusion of 3d layers, which are skipped by
+// the occlusion tracker on the main thread. So this test should run on the impl
+// thread.
+IMPL_THREAD_TEST(OcclusionTrackerTestPerspectiveTransformBehindCamera);
+
+template <class Types>
+class OcclusionTrackerTestLayerBehindCameraDoesNotOcclude
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestLayerBehindCameraDoesNotOcclude(
+ bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ gfx::Transform transform;
+ transform.Translate(50.0, 50.0);
+ transform.ApplyPerspectiveDepth(100.0);
+ transform.Translate3d(0.0, 0.0, 110.0);
+ transform.Translate(-50.0, -50.0);
+
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(100, 100));
+ typename Types::ContentLayerType* layer = this->CreateDrawingLayer(
+ parent, transform, gfx::PointF(), gfx::Size(100, 100), true);
+ parent->SetPreserves3d(true);
+ layer->SetPreserves3d(true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ // The |layer| is entirely behind the camera and should not occlude.
+ this->VisitLayer(layer, &occlusion);
+ this->EnterLayer(parent, &occlusion, false);
+ EXPECT_TRUE(occlusion.occlusion_from_inside_target().IsEmpty());
+ EXPECT_TRUE(occlusion.occlusion_from_outside_target().IsEmpty());
+ }
+};
+
+// This test requires accumulating occlusion of 3d layers, which are skipped by
+// the occlusion tracker on the main thread. So this test should run on the impl
+// thread.
+IMPL_THREAD_TEST(OcclusionTrackerTestLayerBehindCameraDoesNotOcclude);
+
+template <class Types>
+class OcclusionTrackerTestLargePixelsOccludeInsideClipRect
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestLargePixelsOccludeInsideClipRect(
+ bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ gfx::Transform transform;
+ transform.Translate(50.0, 50.0);
+ transform.ApplyPerspectiveDepth(100.0);
+ transform.Translate3d(0.0, 0.0, 99.0);
+ transform.Translate(-50.0, -50.0);
+
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(100, 100));
+ parent->SetMasksToBounds(true);
+ typename Types::ContentLayerType* layer = this->CreateDrawingLayer(
+ parent, transform, gfx::PointF(), gfx::Size(100, 100), true);
+ parent->SetPreserves3d(true);
+ layer->SetPreserves3d(true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ // This is very close to the camera, so pixels in its visible_content_rect()
+ // will actually go outside of the layer's clip rect. Ensure that those
+ // pixels don't occlude things outside the clip rect.
+ this->VisitLayer(layer, &occlusion);
+ this->EnterLayer(parent, &occlusion, false);
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ }
+};
+
+// This test requires accumulating occlusion of 3d layers, which are skipped by
+// the occlusion tracker on the main thread. So this test should run on the impl
+// thread.
+IMPL_THREAD_TEST(OcclusionTrackerTestLargePixelsOccludeInsideClipRect);
+
+template <class Types>
+class OcclusionTrackerTestAnimationOpacity1OnMainThread
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestAnimationOpacity1OnMainThread(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ // parent
+ // +--layer
+ // +--surface
+ // | +--surface_child
+ // | +--surface_child2
+ // +--parent2
+ // +--topmost
+
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(300, 300));
+ typename Types::ContentLayerType* layer =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(300, 300),
+ true);
+ typename Types::ContentLayerType* surface =
+ this->CreateDrawingSurface(parent,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(300, 300),
+ true);
+ typename Types::ContentLayerType* surface_child =
+ this->CreateDrawingLayer(surface,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(200, 300),
+ true);
+ typename Types::ContentLayerType* surface_child2 =
+ this->CreateDrawingLayer(surface,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(100, 300),
+ true);
+ typename Types::ContentLayerType* parent2 =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(300, 300),
+ false);
+ typename Types::ContentLayerType* topmost =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(250.f, 0.f),
+ gfx::Size(50, 300),
+ true);
+
+ AddOpacityTransitionToController(
+ layer->layer_animation_controller(), 10.0, 0.f, 1.f, false);
+ AddOpacityTransitionToController(
+ surface->layer_animation_controller(), 10.0, 0.f, 1.f, false);
+ this->CalcDrawEtc(parent);
+
+ EXPECT_TRUE(layer->draw_opacity_is_animating());
+ EXPECT_FALSE(surface->draw_opacity_is_animating());
+ EXPECT_TRUE(surface->render_surface()->draw_opacity_is_animating());
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->VisitLayer(topmost, &occlusion);
+ this->EnterLayer(parent2, &occlusion, false);
+ // This occlusion will affect all surfaces.
+ EXPECT_EQ(gfx::Rect(250, 0, 50, 300).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(0, 0, 250, 300).ToString(),
+ occlusion.UnoccludedLayerContentRect(
+ parent2, gfx::Rect(0, 0, 300, 300)).ToString());
+ this->LeaveLayer(parent2, &occlusion);
+
+ this->VisitLayer(surface_child2, &occlusion);
+ this->EnterLayer(surface_child, &occlusion, false);
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 300).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ EXPECT_EQ(gfx::Rect(250, 0, 50, 300).ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_RECT_EQ(gfx::Rect(100, 0, 100, 300),
+ occlusion.UnoccludedLayerContentRect(
+ surface_child, gfx::Rect(0, 0, 200, 300)));
+ this->LeaveLayer(surface_child, &occlusion);
+ this->EnterLayer(surface, &occlusion, false);
+ EXPECT_EQ(gfx::Rect(0, 0, 200, 300).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ EXPECT_EQ(gfx::Rect(250, 0, 50, 300).ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_RECT_EQ(gfx::Rect(200, 0, 50, 300),
+ occlusion.UnoccludedLayerContentRect(
+ surface, gfx::Rect(0, 0, 300, 300)));
+ this->LeaveLayer(surface, &occlusion);
+
+ this->EnterContributingSurface(surface, &occlusion, false);
+ // Occlusion within the surface is lost when leaving the animating surface.
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 250, 300),
+ occlusion.UnoccludedContributingSurfaceContentRect(
+ surface, false, gfx::Rect(0, 0, 300, 300), NULL));
+ this->LeaveContributingSurface(surface, &occlusion);
+
+ // Occlusion from outside the animating surface still exists.
+ EXPECT_EQ(gfx::Rect(250, 0, 50, 300).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+
+ this->VisitLayer(layer, &occlusion);
+ this->EnterLayer(parent, &occlusion, false);
+
+ // Occlusion is not added for the animating |layer|.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 250, 300),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(0, 0, 300, 300)));
+ }
+};
+
+MAIN_THREAD_TEST(OcclusionTrackerTestAnimationOpacity1OnMainThread);
+
+template <class Types>
+class OcclusionTrackerTestAnimationOpacity0OnMainThread
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestAnimationOpacity0OnMainThread(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(300, 300));
+ typename Types::ContentLayerType* layer =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(300, 300),
+ true);
+ typename Types::ContentLayerType* surface =
+ this->CreateDrawingSurface(parent,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(300, 300),
+ true);
+ typename Types::ContentLayerType* surface_child =
+ this->CreateDrawingLayer(surface,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(200, 300),
+ true);
+ typename Types::ContentLayerType* surface_child2 =
+ this->CreateDrawingLayer(surface,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(100, 300),
+ true);
+ typename Types::ContentLayerType* parent2 =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(300, 300),
+ false);
+ typename Types::ContentLayerType* topmost =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(250.f, 0.f),
+ gfx::Size(50, 300),
+ true);
+
+ AddOpacityTransitionToController(
+ layer->layer_animation_controller(), 10.0, 1.f, 0.f, false);
+ AddOpacityTransitionToController(
+ surface->layer_animation_controller(), 10.0, 1.f, 0.f, false);
+ this->CalcDrawEtc(parent);
+
+ EXPECT_TRUE(layer->draw_opacity_is_animating());
+ EXPECT_FALSE(surface->draw_opacity_is_animating());
+ EXPECT_TRUE(surface->render_surface()->draw_opacity_is_animating());
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->VisitLayer(topmost, &occlusion);
+ this->EnterLayer(parent2, &occlusion, false);
+ // This occlusion will affect all surfaces.
+ EXPECT_EQ(gfx::Rect(250, 0, 50, 300).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 250, 300),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(0, 0, 300, 300)));
+ this->LeaveLayer(parent2, &occlusion);
+
+ this->VisitLayer(surface_child2, &occlusion);
+ this->EnterLayer(surface_child, &occlusion, false);
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 300).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ EXPECT_EQ(gfx::Rect(250, 0, 50, 300).ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_RECT_EQ(gfx::Rect(100, 0, 100, 300),
+ occlusion.UnoccludedLayerContentRect(
+ surface_child, gfx::Rect(0, 0, 200, 300)));
+ this->LeaveLayer(surface_child, &occlusion);
+ this->EnterLayer(surface, &occlusion, false);
+ EXPECT_EQ(gfx::Rect(0, 0, 200, 300).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ EXPECT_EQ(gfx::Rect(250, 0, 50, 300).ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_RECT_EQ(gfx::Rect(200, 0, 50, 300),
+ occlusion.UnoccludedLayerContentRect(
+ surface, gfx::Rect(0, 0, 300, 300)));
+ this->LeaveLayer(surface, &occlusion);
+
+ this->EnterContributingSurface(surface, &occlusion, false);
+ // Occlusion within the surface is lost when leaving the animating surface.
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 250, 300),
+ occlusion.UnoccludedContributingSurfaceContentRect(
+ surface, false, gfx::Rect(0, 0, 300, 300), NULL));
+ this->LeaveContributingSurface(surface, &occlusion);
+
+ // Occlusion from outside the animating surface still exists.
+ EXPECT_EQ(gfx::Rect(250, 0, 50, 300).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+
+ this->VisitLayer(layer, &occlusion);
+ this->EnterLayer(parent, &occlusion, false);
+
+ // Occlusion is not added for the animating |layer|.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 250, 300),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(0, 0, 300, 300)));
+ }
+};
+
+MAIN_THREAD_TEST(OcclusionTrackerTestAnimationOpacity0OnMainThread);
+
+template <class Types>
+class OcclusionTrackerTestAnimationTranslateOnMainThread
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestAnimationTranslateOnMainThread(
+ bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(300, 300));
+ typename Types::ContentLayerType* layer =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(300, 300),
+ true);
+ typename Types::ContentLayerType* surface =
+ this->CreateDrawingSurface(parent,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(300, 300),
+ true);
+ typename Types::ContentLayerType* surface_child =
+ this->CreateDrawingLayer(surface,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(200, 300),
+ true);
+ typename Types::ContentLayerType* surface_child2 =
+ this->CreateDrawingLayer(surface,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(100, 300),
+ true);
+ typename Types::ContentLayerType* surface2 = this->CreateDrawingSurface(
+ parent, this->identity_matrix, gfx::PointF(), gfx::Size(50, 300), true);
+
+ AddAnimatedTransformToController(
+ layer->layer_animation_controller(), 10.0, 30, 0);
+ AddAnimatedTransformToController(
+ surface->layer_animation_controller(), 10.0, 30, 0);
+ AddAnimatedTransformToController(
+ surface_child->layer_animation_controller(), 10.0, 30, 0);
+ this->CalcDrawEtc(parent);
+
+ EXPECT_TRUE(layer->draw_transform_is_animating());
+ EXPECT_TRUE(layer->screen_space_transform_is_animating());
+ EXPECT_TRUE(
+ surface->render_surface()->target_surface_transforms_are_animating());
+ EXPECT_TRUE(
+ surface->render_surface()->screen_space_transforms_are_animating());
+ // The surface owning layer doesn't animate against its own surface.
+ EXPECT_FALSE(surface->draw_transform_is_animating());
+ EXPECT_TRUE(surface->screen_space_transform_is_animating());
+ EXPECT_TRUE(surface_child->draw_transform_is_animating());
+ EXPECT_TRUE(surface_child->screen_space_transform_is_animating());
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->VisitLayer(surface2, &occlusion);
+ this->EnterContributingSurface(surface2, &occlusion, false);
+
+ EXPECT_EQ(gfx::Rect(0, 0, 50, 300).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ this->LeaveContributingSurface(surface2, &occlusion);
+ this->EnterLayer(surface_child2, &occlusion, false);
+
+ // surface_child2 is moving in screen space but not relative to its target,
+ // so occlusion should happen in its target space only. It also means that
+ // things occluding from outside the target (e.g. surface2) cannot occlude
+ // this layer.
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 300),
+ occlusion.UnoccludedLayerContentRect(
+ surface_child2, gfx::Rect(0, 0, 100, 300)));
+ EXPECT_FALSE(
+ occlusion.OccludedLayer(surface_child, gfx::Rect(0, 0, 50, 300)));
+
+ this->LeaveLayer(surface_child2, &occlusion);
+ this->EnterLayer(surface_child, &occlusion, false);
+ EXPECT_FALSE(
+ occlusion.OccludedLayer(surface_child, gfx::Rect(0, 0, 100, 300)));
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 300).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ EXPECT_RECT_EQ(gfx::Rect(100, 0, 200, 300),
+ occlusion.UnoccludedLayerContentRect(
+ surface, gfx::Rect(0, 0, 300, 300)));
+
+ // The surface_child is occluded by the surface_child2, but is moving
+ // relative its target, so it can't be occluded.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 200, 300),
+ occlusion.UnoccludedLayerContentRect(
+ surface_child, gfx::Rect(0, 0, 200, 300)));
+ EXPECT_FALSE(
+ occlusion.OccludedLayer(surface_child, gfx::Rect(0, 0, 50, 300)));
+
+ this->LeaveLayer(surface_child, &occlusion);
+ this->EnterLayer(surface, &occlusion, false);
+ // The surface_child is moving in screen space but not relative to its
+ // target, so occlusion should happen from within the target only.
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 300).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ EXPECT_RECT_EQ(gfx::Rect(100, 0, 200, 300),
+ occlusion.UnoccludedLayerContentRect(
+ surface, gfx::Rect(0, 0, 300, 300)));
+
+ this->LeaveLayer(surface, &occlusion);
+ // The surface's owning layer is moving in screen space but not relative to
+ // its target, so occlusion should happen within the target only.
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(0, 0, 300, 300).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 0, 0),
+ occlusion.UnoccludedLayerContentRect(
+ surface, gfx::Rect(0, 0, 300, 300)));
+
+ this->EnterContributingSurface(surface, &occlusion, false);
+ // The contributing |surface| is animating so it can't be occluded.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 300, 300),
+ occlusion.UnoccludedContributingSurfaceContentRect(
+ surface, false, gfx::Rect(0, 0, 300, 300), NULL));
+ this->LeaveContributingSurface(surface, &occlusion);
+
+ this->EnterLayer(layer, &occlusion, false);
+ // The |surface| is moving in the screen and in its target, so all occlusion
+ // within the surface is lost when leaving it.
+ EXPECT_RECT_EQ(gfx::Rect(50, 0, 250, 300),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(0, 0, 300, 300)));
+ this->LeaveLayer(layer, &occlusion);
+
+ this->EnterLayer(parent, &occlusion, false);
+ // The |layer| is animating in the screen and in its target, so no occlusion
+ // is added.
+ EXPECT_RECT_EQ(gfx::Rect(50, 0, 250, 300),
+ occlusion.UnoccludedLayerContentRect(
+ parent, gfx::Rect(0, 0, 300, 300)));
+ }
+};
+
+MAIN_THREAD_TEST(OcclusionTrackerTestAnimationTranslateOnMainThread);
+
+template <class Types>
+class OcclusionTrackerTestSurfaceOcclusionTranslatesToParent
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestSurfaceOcclusionTranslatesToParent(
+ bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ gfx::Transform surface_transform;
+ surface_transform.Translate(300.0, 300.0);
+ surface_transform.Scale(2.0, 2.0);
+ surface_transform.Translate(-150.0, -150.0);
+
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(500, 500));
+ typename Types::ContentLayerType* surface = this->CreateDrawingSurface(
+ parent, surface_transform, gfx::PointF(), gfx::Size(300, 300), false);
+ typename Types::ContentLayerType* surface2 =
+ this->CreateDrawingSurface(parent,
+ this->identity_matrix,
+ gfx::PointF(50.f, 50.f),
+ gfx::Size(300, 300),
+ false);
+ surface->SetOpaqueContentsRect(gfx::Rect(0, 0, 200, 200));
+ surface2->SetOpaqueContentsRect(gfx::Rect(0, 0, 200, 200));
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->VisitLayer(surface2, &occlusion);
+ this->VisitContributingSurface(surface2, &occlusion);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(50, 50, 200, 200).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ // Clear any stored occlusion.
+ occlusion.set_occlusion_from_outside_target(Region());
+ occlusion.set_occlusion_from_inside_target(Region());
+
+ this->VisitLayer(surface, &occlusion);
+ this->VisitContributingSurface(surface, &occlusion);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(0, 0, 400, 400).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ }
+};
+
+MAIN_AND_IMPL_THREAD_TEST(
+ OcclusionTrackerTestSurfaceOcclusionTranslatesToParent);
+
+template <class Types>
+class OcclusionTrackerTestSurfaceOcclusionTranslatesWithClipping
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestSurfaceOcclusionTranslatesWithClipping(
+ bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(300, 300));
+ parent->SetMasksToBounds(true);
+ typename Types::ContentLayerType* surface =
+ this->CreateDrawingSurface(parent,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(500, 300),
+ false);
+ surface->SetOpaqueContentsRect(gfx::Rect(0, 0, 400, 200));
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->VisitLayer(surface, &occlusion);
+ this->VisitContributingSurface(surface, &occlusion);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(0, 0, 300, 200).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ }
+};
+
+MAIN_AND_IMPL_THREAD_TEST(
+ OcclusionTrackerTestSurfaceOcclusionTranslatesWithClipping);
+
+template <class Types>
+class OcclusionTrackerTestReplicaOccluded : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestReplicaOccluded(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(100, 200));
+ typename Types::LayerType* surface =
+ this->CreateDrawingSurface(parent,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ true);
+ this->CreateReplicaLayer(surface,
+ this->identity_matrix,
+ gfx::PointF(0.f, 100.f),
+ gfx::Size(100, 100));
+ typename Types::LayerType* topmost =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(0.f, 100.f),
+ gfx::Size(100, 100),
+ true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ // |topmost| occludes the replica, but not the surface itself.
+ this->VisitLayer(topmost, &occlusion);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(0, 100, 100, 100).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ this->VisitLayer(surface, &occlusion);
+
+ EXPECT_EQ(gfx::Rect(0, 100, 100, 100).ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ this->EnterContributingSurface(surface, &occlusion, false);
+
+ // Surface is not occluded so it shouldn't think it is.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 100),
+ occlusion.UnoccludedContributingSurfaceContentRect(
+ surface, false, gfx::Rect(0, 0, 100, 100), NULL));
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestReplicaOccluded);
+
+template <class Types>
+class OcclusionTrackerTestSurfaceWithReplicaUnoccluded
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestSurfaceWithReplicaUnoccluded(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(100, 200));
+ typename Types::LayerType* surface =
+ this->CreateDrawingSurface(parent,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ true);
+ this->CreateReplicaLayer(surface,
+ this->identity_matrix,
+ gfx::PointF(0.f, 100.f),
+ gfx::Size(100, 100));
+ typename Types::LayerType* topmost =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(100, 110),
+ true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ // |topmost| occludes the surface, but not the entire surface's replica.
+ this->VisitLayer(topmost, &occlusion);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 110).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ this->VisitLayer(surface, &occlusion);
+
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 110).ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ this->EnterContributingSurface(surface, &occlusion, false);
+
+ // Surface is occluded, but only the top 10px of the replica.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 0, 0),
+ occlusion.UnoccludedContributingSurfaceContentRect(
+ surface, false, gfx::Rect(0, 0, 100, 100), NULL));
+ EXPECT_RECT_EQ(gfx::Rect(0, 10, 100, 90),
+ occlusion.UnoccludedContributingSurfaceContentRect(
+ surface, true, gfx::Rect(0, 0, 100, 100), NULL));
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestSurfaceWithReplicaUnoccluded);
+
+template <class Types>
+class OcclusionTrackerTestSurfaceAndReplicaOccludedDifferently
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestSurfaceAndReplicaOccludedDifferently(
+ bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(100, 200));
+ typename Types::LayerType* surface =
+ this->CreateDrawingSurface(parent,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ true);
+ this->CreateReplicaLayer(surface,
+ this->identity_matrix,
+ gfx::PointF(0.f, 100.f),
+ gfx::Size(100, 100));
+ typename Types::LayerType* over_surface = this->CreateDrawingLayer(
+ parent, this->identity_matrix, gfx::PointF(), gfx::Size(40, 100), true);
+ typename Types::LayerType* over_replica =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(0.f, 100.f),
+ gfx::Size(50, 100),
+ true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ // These occlude the surface and replica differently, so we can test each
+ // one.
+ this->VisitLayer(over_replica, &occlusion);
+ this->VisitLayer(over_surface, &occlusion);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(UnionRegions(gfx::Rect(0, 0, 40, 100), gfx::Rect(0, 100, 50, 100))
+ .ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ this->VisitLayer(surface, &occlusion);
+
+ EXPECT_EQ(UnionRegions(gfx::Rect(0, 0, 40, 100), gfx::Rect(0, 100, 50, 100))
+ .ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ this->EnterContributingSurface(surface, &occlusion, false);
+
+ // Surface and replica are occluded different amounts.
+ EXPECT_RECT_EQ(gfx::Rect(40, 0, 60, 100),
+ occlusion.UnoccludedContributingSurfaceContentRect(
+ surface, false, gfx::Rect(0, 0, 100, 100), NULL));
+ EXPECT_RECT_EQ(gfx::Rect(50, 0, 50, 100),
+ occlusion.UnoccludedContributingSurfaceContentRect(
+ surface, true, gfx::Rect(0, 0, 100, 100), NULL));
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(
+ OcclusionTrackerTestSurfaceAndReplicaOccludedDifferently);
+
+template <class Types>
+class OcclusionTrackerTestSurfaceChildOfSurface
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestSurfaceChildOfSurface(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ // This test verifies that the surface cliprect does not end up empty and
+ // clip away the entire unoccluded rect.
+
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(100, 200));
+ typename Types::LayerType* surface =
+ this->CreateDrawingSurface(parent,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ true);
+ typename Types::LayerType* surface_child =
+ this->CreateDrawingSurface(surface,
+ this->identity_matrix,
+ gfx::PointF(0.f, 10.f),
+ gfx::Size(100, 50),
+ true);
+ typename Types::LayerType* topmost = this->CreateDrawingLayer(
+ parent, this->identity_matrix, gfx::PointF(), gfx::Size(100, 50), true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(-100, -100, 1000, 1000));
+
+ // |topmost| occludes everything partially so we know occlusion is happening
+ // at all.
+ this->VisitLayer(topmost, &occlusion);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 50).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ this->VisitLayer(surface_child, &occlusion);
+
+ // surface_child increases the occlusion in the screen by a narrow sliver.
+ EXPECT_EQ(gfx::Rect(0, -10, 100, 50).ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ // In its own surface, surface_child is at 0,0 as is its occlusion.
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 50).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ // The root layer always has a clip rect. So the parent of |surface| has a
+ // clip rect. However, the owning layer for |surface| does not mask to
+ // bounds, so it doesn't have a clip rect of its own. Thus the parent of
+ // |surface_child| exercises different code paths as its parent does not
+ // have a clip rect.
+
+ this->EnterContributingSurface(surface_child, &occlusion, false);
+ // The surface_child's parent does not have a clip rect as it owns a render
+ // surface. Make sure the unoccluded rect does not get clipped away
+ // inappropriately.
+ EXPECT_RECT_EQ(gfx::Rect(0, 40, 100, 10),
+ occlusion.UnoccludedContributingSurfaceContentRect(
+ surface_child, false, gfx::Rect(0, 0, 100, 50), NULL));
+ this->LeaveContributingSurface(surface_child, &occlusion);
+
+ // When the surface_child's occlusion is transformed up to its parent, make
+ // sure it is not clipped away inappropriately also.
+ this->EnterLayer(surface, &occlusion, false);
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 50).ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(0, 10, 100, 50).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ this->LeaveLayer(surface, &occlusion);
+
+ this->EnterContributingSurface(surface, &occlusion, false);
+ // The surface's parent does have a clip rect as it is the root layer.
+ EXPECT_RECT_EQ(gfx::Rect(0, 50, 100, 50),
+ occlusion.UnoccludedContributingSurfaceContentRect(
+ surface, false, gfx::Rect(0, 0, 100, 100), NULL));
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestSurfaceChildOfSurface);
+
+template <class Types>
+class OcclusionTrackerTestTopmostSurfaceIsClippedToViewport
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestTopmostSurfaceIsClippedToViewport(
+ bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ // This test verifies that the top-most surface is considered occluded
+ // outside of its target's clip rect and outside the viewport rect.
+
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(100, 200));
+ typename Types::LayerType* surface =
+ this->CreateDrawingSurface(parent,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(100, 300),
+ true);
+ this->CalcDrawEtc(parent);
+ {
+ // Make a viewport rect that is larger than the root layer.
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->VisitLayer(surface, &occlusion);
+
+ // The root layer always has a clip rect. So the parent of |surface| has a
+ // clip rect giving the surface itself a clip rect.
+ this->EnterContributingSurface(surface, &occlusion, false);
+ // Make sure the parent's clip rect clips the unoccluded region of the
+ // child surface.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 200),
+ occlusion.UnoccludedContributingSurfaceContentRect(
+ surface, false, gfx::Rect(0, 0, 100, 300), NULL));
+ }
+ this->ResetLayerIterator();
+ {
+ // Make a viewport rect that is smaller than the root layer.
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 100, 100));
+
+ this->VisitLayer(surface, &occlusion);
+
+ // The root layer always has a clip rect. So the parent of |surface| has a
+ // clip rect giving the surface itself a clip rect.
+ this->EnterContributingSurface(surface, &occlusion, false);
+ // Make sure the viewport rect clips the unoccluded region of the child
+ // surface.
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 100),
+ occlusion.UnoccludedContributingSurfaceContentRect(
+ surface, false, gfx::Rect(0, 0, 100, 300), NULL));
+ }
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(
+ OcclusionTrackerTestTopmostSurfaceIsClippedToViewport);
+
+template <class Types>
+class OcclusionTrackerTestSurfaceChildOfClippingSurface
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestSurfaceChildOfClippingSurface(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ // This test verifies that the surface cliprect does not end up empty and
+ // clip away the entire unoccluded rect.
+
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(80, 200));
+ parent->SetMasksToBounds(true);
+ typename Types::LayerType* surface =
+ this->CreateDrawingSurface(parent,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ true);
+ typename Types::LayerType* surface_child =
+ this->CreateDrawingSurface(surface,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ typename Types::LayerType* topmost = this->CreateDrawingLayer(
+ parent, this->identity_matrix, gfx::PointF(), gfx::Size(100, 50), true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ // |topmost| occludes everything partially so we know occlusion is happening
+ // at all.
+ this->VisitLayer(topmost, &occlusion);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(0, 0, 80, 50).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ // surface_child is not opaque and does not occlude, so we have a non-empty
+ // unoccluded area on surface.
+ this->VisitLayer(surface_child, &occlusion);
+
+ EXPECT_EQ(gfx::Rect(0, 0, 80, 50).ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(0, 0, 0, 0).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ // The root layer always has a clip rect. So the parent of |surface| has a
+ // clip rect. However, the owning layer for |surface| does not mask to
+ // bounds, so it doesn't have a clip rect of its own. Thus the parent of
+ // |surface_child| exercises different code paths as its parent does not
+ // have a clip rect.
+
+ this->EnterContributingSurface(surface_child, &occlusion, false);
+ // The surface_child's parent does not have a clip rect as it owns a render
+ // surface.
+ EXPECT_EQ(
+ gfx::Rect(0, 50, 80, 50).ToString(),
+ occlusion.UnoccludedContributingSurfaceContentRect(
+ surface_child, false, gfx::Rect(0, 0, 100, 100), NULL).ToString());
+ this->LeaveContributingSurface(surface_child, &occlusion);
+
+ this->VisitLayer(surface, &occlusion);
+ this->EnterContributingSurface(surface, &occlusion, false);
+ // The surface's parent does have a clip rect as it is the root layer.
+ EXPECT_EQ(gfx::Rect(0, 50, 80, 50).ToString(),
+ occlusion.UnoccludedContributingSurfaceContentRect(
+ surface, false, gfx::Rect(0, 0, 100, 100), NULL).ToString());
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestSurfaceChildOfClippingSurface);
+
+template <class Types>
+class OcclusionTrackerTestDontOccludePixelsNeededForBackgroundFilter
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestDontOccludePixelsNeededForBackgroundFilter(
+ bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ gfx::Transform scale_by_half;
+ scale_by_half.Scale(0.5, 0.5);
+
+ // Make a surface and its replica, each 50x50, that are completely
+ // surrounded by opaque layers which are above them in the z-order. The
+ // surface is scaled to test that the pixel moving is done in the target
+ // space, where the background filter is applied, but the surface appears at
+ // 50, 50 and the replica at 200, 50.
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(300, 150));
+ typename Types::LayerType* filtered_surface =
+ this->CreateDrawingLayer(parent,
+ scale_by_half,
+ gfx::PointF(50.f, 50.f),
+ gfx::Size(100, 100),
+ false);
+ this->CreateReplicaLayer(filtered_surface,
+ this->identity_matrix,
+ gfx::PointF(300.f, 0.f),
+ gfx::Size());
+ typename Types::LayerType* occluding_layer1 = this->CreateDrawingLayer(
+ parent, this->identity_matrix, gfx::PointF(), gfx::Size(300, 50), true);
+ typename Types::LayerType* occluding_layer2 =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(0.f, 100.f),
+ gfx::Size(300, 50),
+ true);
+ typename Types::LayerType* occluding_layer3 =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(0.f, 50.f),
+ gfx::Size(50, 50),
+ true);
+ typename Types::LayerType* occluding_layer4 =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(100.f, 50.f),
+ gfx::Size(100, 50),
+ true);
+ typename Types::LayerType* occluding_layer5 =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(250.f, 50.f),
+ gfx::Size(50, 50),
+ true);
+
+ // Filters make the layer own a surface.
+ FilterOperations filters;
+ filters.Append(FilterOperation::CreateBlurFilter(10.f));
+ filtered_surface->SetBackgroundFilters(filters);
+
+ // Save the distance of influence for the blur effect.
+ int outset_top, outset_right, outset_bottom, outset_left;
+ filters.GetOutsets(
+ &outset_top, &outset_right, &outset_bottom, &outset_left);
+
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ // These layers occlude pixels directly beside the filtered_surface. Because
+ // filtered surface blends pixels in a radius, it will need to see some of
+ // the pixels (up to radius far) underneath the occluding layers.
+ this->VisitLayer(occluding_layer5, &occlusion);
+ this->VisitLayer(occluding_layer4, &occlusion);
+ this->VisitLayer(occluding_layer3, &occlusion);
+ this->VisitLayer(occluding_layer2, &occlusion);
+ this->VisitLayer(occluding_layer1, &occlusion);
+
+ Region expected_occlusion;
+ expected_occlusion.Union(gfx::Rect(0, 0, 300, 50));
+ expected_occlusion.Union(gfx::Rect(0, 50, 50, 50));
+ expected_occlusion.Union(gfx::Rect(100, 50, 100, 50));
+ expected_occlusion.Union(gfx::Rect(250, 50, 50, 50));
+ expected_occlusion.Union(gfx::Rect(0, 100, 300, 50));
+
+ EXPECT_EQ(expected_occlusion.ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+
+ this->VisitLayer(filtered_surface, &occlusion);
+
+ // The filtered layer/replica does not occlude.
+ Region expected_occlusion_outside_surface;
+ expected_occlusion_outside_surface.Union(gfx::Rect(-50, -50, 300, 50));
+ expected_occlusion_outside_surface.Union(gfx::Rect(-50, 0, 50, 50));
+ expected_occlusion_outside_surface.Union(gfx::Rect(50, 0, 100, 50));
+ expected_occlusion_outside_surface.Union(gfx::Rect(200, 0, 50, 50));
+ expected_occlusion_outside_surface.Union(gfx::Rect(-50, 50, 300, 50));
+
+ EXPECT_EQ(expected_occlusion_outside_surface.ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ // The surface has a background blur, so it needs pixels that are currently
+ // considered occluded in order to be drawn. So the pixels it needs should
+ // be removed some the occluded area so that when we get to the parent they
+ // are drawn.
+ this->VisitContributingSurface(filtered_surface, &occlusion);
+
+ this->EnterLayer(parent, &occlusion, false);
+
+ Region expected_blurred_occlusion;
+ expected_blurred_occlusion.Union(gfx::Rect(0, 0, 300, 50 - outset_top));
+ expected_blurred_occlusion.Union(gfx::Rect(
+ 0, 50 - outset_top, 50 - outset_left, 50 + outset_top + outset_bottom));
+ expected_blurred_occlusion.Union(
+ gfx::Rect(100 + outset_right,
+ 50 - outset_top,
+ 100 - outset_right - outset_left,
+ 50 + outset_top + outset_bottom));
+ expected_blurred_occlusion.Union(
+ gfx::Rect(250 + outset_right,
+ 50 - outset_top,
+ 50 - outset_right,
+ 50 + outset_top + outset_bottom));
+ expected_blurred_occlusion.Union(
+ gfx::Rect(0, 100 + outset_bottom, 300, 50 - outset_bottom));
+
+ EXPECT_EQ(expected_blurred_occlusion.ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+
+ gfx::Rect outset_rect;
+ gfx::Rect test_rect;
+
+ // Nothing in the blur outsets for the filtered_surface is occluded.
+ outset_rect = gfx::Rect(50 - outset_left,
+ 50 - outset_top,
+ 50 + outset_left + outset_right,
+ 50 + outset_top + outset_bottom);
+ test_rect = outset_rect;
+ EXPECT_EQ(
+ outset_rect.ToString(),
+ occlusion.UnoccludedLayerContentRect(parent, test_rect).ToString());
+
+ // Stuff outside the blur outsets is still occluded though.
+ test_rect = outset_rect;
+ test_rect.Inset(0, 0, -1, 0);
+ EXPECT_EQ(
+ outset_rect.ToString(),
+ occlusion.UnoccludedLayerContentRect(parent, test_rect).ToString());
+ test_rect = outset_rect;
+ test_rect.Inset(0, 0, 0, -1);
+ EXPECT_EQ(
+ outset_rect.ToString(),
+ occlusion.UnoccludedLayerContentRect(parent, test_rect).ToString());
+ test_rect = outset_rect;
+ test_rect.Inset(-1, 0, 0, 0);
+ EXPECT_EQ(
+ outset_rect.ToString(),
+ occlusion.UnoccludedLayerContentRect(parent, test_rect).ToString());
+ test_rect = outset_rect;
+ test_rect.Inset(0, -1, 0, 0);
+ EXPECT_EQ(
+ outset_rect.ToString(),
+ occlusion.UnoccludedLayerContentRect(parent, test_rect).ToString());
+
+ // Nothing in the blur outsets for the filtered_surface's replica is
+ // occluded.
+ outset_rect = gfx::Rect(200 - outset_left,
+ 50 - outset_top,
+ 50 + outset_left + outset_right,
+ 50 + outset_top + outset_bottom);
+ test_rect = outset_rect;
+ EXPECT_EQ(
+ outset_rect.ToString(),
+ occlusion.UnoccludedLayerContentRect(parent, test_rect).ToString());
+
+ // Stuff outside the blur outsets is still occluded though.
+ test_rect = outset_rect;
+ test_rect.Inset(0, 0, -1, 0);
+ EXPECT_EQ(
+ outset_rect.ToString(),
+ occlusion.UnoccludedLayerContentRect(parent, test_rect).ToString());
+ test_rect = outset_rect;
+ test_rect.Inset(0, 0, 0, -1);
+ EXPECT_EQ(
+ outset_rect.ToString(),
+ occlusion.UnoccludedLayerContentRect(parent, test_rect).ToString());
+ test_rect = outset_rect;
+ test_rect.Inset(-1, 0, 0, 0);
+ EXPECT_EQ(
+ outset_rect.ToString(),
+ occlusion.UnoccludedLayerContentRect(parent, test_rect).ToString());
+ test_rect = outset_rect;
+ test_rect.Inset(0, -1, 0, 0);
+ EXPECT_EQ(
+ outset_rect.ToString(),
+ occlusion.UnoccludedLayerContentRect(parent, test_rect).ToString());
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(
+ OcclusionTrackerTestDontOccludePixelsNeededForBackgroundFilter);
+
+template <class Types>
+class OcclusionTrackerTestTwoBackgroundFiltersReduceOcclusionTwice
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestTwoBackgroundFiltersReduceOcclusionTwice(
+ bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ gfx::Transform scale_by_half;
+ scale_by_half.Scale(0.5, 0.5);
+
+ // Makes two surfaces that completely cover |parent|. The occlusion both
+ // above and below the filters will be reduced by each of them.
+ typename Types::ContentLayerType* root = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(75, 75));
+ typename Types::LayerType* parent = this->CreateSurface(
+ root, scale_by_half, gfx::PointF(), gfx::Size(150, 150));
+ parent->SetMasksToBounds(true);
+ typename Types::LayerType* filtered_surface1 = this->CreateDrawingLayer(
+ parent, scale_by_half, gfx::PointF(), gfx::Size(300, 300), false);
+ typename Types::LayerType* filtered_surface2 = this->CreateDrawingLayer(
+ parent, scale_by_half, gfx::PointF(), gfx::Size(300, 300), false);
+ typename Types::LayerType* occluding_layer_above =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(100.f, 100.f),
+ gfx::Size(50, 50),
+ true);
+
+ // Filters make the layers own surfaces.
+ FilterOperations filters;
+ filters.Append(FilterOperation::CreateBlurFilter(1.f));
+ filtered_surface1->SetBackgroundFilters(filters);
+ filtered_surface2->SetBackgroundFilters(filters);
+
+ // Save the distance of influence for the blur effect.
+ int outset_top, outset_right, outset_bottom, outset_left;
+ filters.GetOutsets(
+ &outset_top, &outset_right, &outset_bottom, &outset_left);
+
+ this->CalcDrawEtc(root);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->VisitLayer(occluding_layer_above, &occlusion);
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(100 / 2, 100 / 2, 50 / 2, 50 / 2).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ this->VisitLayer(filtered_surface2, &occlusion);
+ this->VisitContributingSurface(filtered_surface2, &occlusion);
+ this->VisitLayer(filtered_surface1, &occlusion);
+ this->VisitContributingSurface(filtered_surface1, &occlusion);
+
+ // Test expectations in the target.
+ gfx::Rect expected_occlusion =
+ gfx::Rect(100 / 2 + outset_right * 2,
+ 100 / 2 + outset_bottom * 2,
+ 50 / 2 - (outset_left + outset_right) * 2,
+ 50 / 2 - (outset_top + outset_bottom) * 2);
+ EXPECT_EQ(expected_occlusion.ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ // Test expectations in the screen are the same as in the target, as the
+ // render surface is 1:1 with the screen.
+ EXPECT_EQ(expected_occlusion.ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(
+ OcclusionTrackerTestTwoBackgroundFiltersReduceOcclusionTwice);
+
+template <class Types>
+class OcclusionTrackerTestDontOccludePixelsNeededForBackgroundFilterWithClip
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit
+ OcclusionTrackerTestDontOccludePixelsNeededForBackgroundFilterWithClip(
+ bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ // Make a surface and its replica, Each 50x50, that are completely
+ // surrounded by opaque layers which are above them in the z-order.
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(300, 150));
+ // We stick the filtered surface inside a clipping surface so that we can
+ // make sure the clip is honored when exposing pixels for
+ // the background filter.
+ typename Types::LayerType* clipping_surface =
+ this->CreateDrawingSurface(parent,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(300, 70),
+ false);
+ clipping_surface->SetMasksToBounds(true);
+ typename Types::LayerType* filtered_surface =
+ this->CreateDrawingLayer(clipping_surface,
+ this->identity_matrix,
+ gfx::PointF(50.f, 50.f),
+ gfx::Size(50, 50),
+ false);
+ this->CreateReplicaLayer(filtered_surface,
+ this->identity_matrix,
+ gfx::PointF(150.f, 0.f),
+ gfx::Size());
+ typename Types::LayerType* occluding_layer1 = this->CreateDrawingLayer(
+ parent, this->identity_matrix, gfx::PointF(), gfx::Size(300, 50), true);
+ typename Types::LayerType* occluding_layer2 =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(0.f, 100.f),
+ gfx::Size(300, 50),
+ true);
+ typename Types::LayerType* occluding_layer3 =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(0.f, 50.f),
+ gfx::Size(50, 50),
+ true);
+ typename Types::LayerType* occluding_layer4 =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(100.f, 50.f),
+ gfx::Size(100, 50),
+ true);
+ typename Types::LayerType* occluding_layer5 =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(250.f, 50.f),
+ gfx::Size(50, 50),
+ true);
+
+ // Filters make the layer own a surface. This filter is large enough that it
+ // goes outside the bottom of the clipping_surface.
+ FilterOperations filters;
+ filters.Append(FilterOperation::CreateBlurFilter(12.f));
+ filtered_surface->SetBackgroundFilters(filters);
+
+ // Save the distance of influence for the blur effect.
+ int outset_top, outset_right, outset_bottom, outset_left;
+ filters.GetOutsets(
+ &outset_top, &outset_right, &outset_bottom, &outset_left);
+
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ // These layers occlude pixels directly beside the filtered_surface. Because
+ // filtered surface blends pixels in a radius, it will need to see some of
+ // the pixels (up to radius far) underneath the occluding layers.
+ this->VisitLayer(occluding_layer5, &occlusion);
+ this->VisitLayer(occluding_layer4, &occlusion);
+ this->VisitLayer(occluding_layer3, &occlusion);
+ this->VisitLayer(occluding_layer2, &occlusion);
+ this->VisitLayer(occluding_layer1, &occlusion);
+
+ Region expected_occlusion;
+ expected_occlusion.Union(gfx::Rect(0, 0, 300, 50));
+ expected_occlusion.Union(gfx::Rect(0, 50, 50, 50));
+ expected_occlusion.Union(gfx::Rect(100, 50, 100, 50));
+ expected_occlusion.Union(gfx::Rect(250, 50, 50, 50));
+ expected_occlusion.Union(gfx::Rect(0, 100, 300, 50));
+
+ EXPECT_EQ(expected_occlusion.ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+
+ // Everything outside the surface/replica is occluded but the
+ // surface/replica itself is not.
+ this->VisitLayer(filtered_surface, &occlusion);
+
+ // The filtered layer/replica does not occlude.
+ Region expected_occlusion_outside_surface;
+ expected_occlusion_outside_surface.Union(gfx::Rect(-50, -50, 300, 50));
+ expected_occlusion_outside_surface.Union(gfx::Rect(-50, 0, 50, 50));
+ expected_occlusion_outside_surface.Union(gfx::Rect(50, 0, 100, 50));
+ expected_occlusion_outside_surface.Union(gfx::Rect(200, 0, 50, 50));
+ expected_occlusion_outside_surface.Union(gfx::Rect(-50, 50, 300, 50));
+
+ EXPECT_EQ(expected_occlusion_outside_surface.ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ // The surface has a background blur, so it needs pixels that are currently
+ // considered occluded in order to be drawn. So the pixels it needs should
+ // be removed some the occluded area so that when we get to the parent they
+ // are drawn.
+ this->VisitContributingSurface(filtered_surface, &occlusion);
+
+ this->VisitLayer(clipping_surface, &occlusion);
+ this->EnterContributingSurface(clipping_surface, &occlusion, false);
+
+ Region expected_blurred_occlusion;
+ expected_blurred_occlusion.Union(gfx::Rect(0, 0, 300, 50 - outset_top));
+ expected_blurred_occlusion.Union(gfx::Rect(
+ 0, 50 - outset_top, 50 - outset_left, 20 + outset_top + outset_bottom));
+ expected_blurred_occlusion.Union(
+ gfx::Rect(100 + outset_right,
+ 50 - outset_top,
+ 100 - outset_right - outset_left,
+ 20 + outset_top + outset_bottom));
+ expected_blurred_occlusion.Union(
+ gfx::Rect(250 + outset_right,
+ 50 - outset_top,
+ 50 - outset_right,
+ 20 + outset_top + outset_bottom));
+ expected_blurred_occlusion.Union(gfx::Rect(0, 100 + 5, 300, 50 - 5));
+
+ EXPECT_EQ(expected_blurred_occlusion.ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ gfx::Rect outset_rect;
+ gfx::Rect clipped_outset_rect;
+ gfx::Rect test_rect;
+
+ // Nothing in the (clipped) blur outsets for the filtered_surface is
+ // occluded.
+ outset_rect = gfx::Rect(50 - outset_left,
+ 50 - outset_top,
+ 50 + outset_left + outset_right,
+ 50 + outset_top + outset_bottom);
+ clipped_outset_rect = outset_rect;
+ clipped_outset_rect.Intersect(gfx::Rect(0 - outset_left,
+ 0 - outset_top,
+ 300 + outset_left + outset_right,
+ 70 + outset_top + outset_bottom));
+ clipped_outset_rect.Intersect(gfx::Rect(0, 0, 300, 70));
+ test_rect = outset_rect;
+ EXPECT_RECT_EQ(
+ clipped_outset_rect,
+ occlusion.UnoccludedLayerContentRect(clipping_surface, test_rect));
+
+ // Stuff outside the (clipped) blur outsets is still occluded though.
+ test_rect = outset_rect;
+ test_rect.Inset(0, 0, -1, 0);
+ EXPECT_RECT_EQ(
+ clipped_outset_rect,
+ occlusion.UnoccludedLayerContentRect(clipping_surface, test_rect));
+ test_rect = outset_rect;
+ test_rect.Inset(0, 0, 0, -1);
+ EXPECT_RECT_EQ(
+ clipped_outset_rect,
+ occlusion.UnoccludedLayerContentRect(clipping_surface, test_rect));
+ test_rect = outset_rect;
+ test_rect.Inset(-1, 0, 0, 0);
+ EXPECT_RECT_EQ(
+ clipped_outset_rect,
+ occlusion.UnoccludedLayerContentRect(clipping_surface, test_rect));
+ test_rect = outset_rect;
+ test_rect.Inset(0, -1, 0, 0);
+ EXPECT_RECT_EQ(
+ clipped_outset_rect,
+ occlusion.UnoccludedLayerContentRect(clipping_surface, test_rect));
+
+ // Nothing in the (clipped) blur outsets for the filtered_surface's replica
+ // is occluded.
+ outset_rect = gfx::Rect(200 - outset_left,
+ 50 - outset_top,
+ 50 + outset_left + outset_right,
+ 50 + outset_top + outset_bottom);
+ clipped_outset_rect = outset_rect;
+ clipped_outset_rect.Intersect(gfx::Rect(0 - outset_left,
+ 0 - outset_top,
+ 300 + outset_left + outset_right,
+ 70 + outset_top + outset_bottom));
+ clipped_outset_rect.Intersect(gfx::Rect(0, 0, 300, 70));
+ test_rect = outset_rect;
+ EXPECT_RECT_EQ(
+ clipped_outset_rect,
+ occlusion.UnoccludedLayerContentRect(clipping_surface, test_rect));
+
+ // Stuff outside the (clipped) blur outsets is still occluded though.
+ test_rect = outset_rect;
+ test_rect.Inset(0, 0, -1, 0);
+ EXPECT_RECT_EQ(
+ clipped_outset_rect,
+ occlusion.UnoccludedLayerContentRect(clipping_surface, test_rect));
+ test_rect = outset_rect;
+ test_rect.Inset(0, 0, 0, -1);
+ EXPECT_RECT_EQ(
+ clipped_outset_rect,
+ occlusion.UnoccludedLayerContentRect(clipping_surface, test_rect));
+ test_rect = outset_rect;
+ test_rect.Inset(-1, 0, 0, 0);
+ EXPECT_RECT_EQ(
+ clipped_outset_rect,
+ occlusion.UnoccludedLayerContentRect(clipping_surface, test_rect));
+ test_rect = outset_rect;
+ test_rect.Inset(0, -1, 0, 0);
+ EXPECT_RECT_EQ(
+ clipped_outset_rect,
+ occlusion.UnoccludedLayerContentRect(clipping_surface, test_rect));
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(
+ OcclusionTrackerTestDontOccludePixelsNeededForBackgroundFilterWithClip);
+
+template <class Types>
+class OcclusionTrackerTestDontReduceOcclusionBelowBackgroundFilter
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestDontReduceOcclusionBelowBackgroundFilter(
+ bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ gfx::Transform scale_by_half;
+ scale_by_half.Scale(0.5, 0.5);
+
+ // Make a surface and its replica, each 50x50, with a smaller 30x30 layer
+ // centered below each. The surface is scaled to test that the pixel moving
+ // is done in the target space, where the background filter is applied, but
+ // the surface appears at 50, 50 and the replica at 200, 50.
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(300, 150));
+ typename Types::LayerType* behind_surface_layer =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(60.f, 60.f),
+ gfx::Size(30, 30),
+ true);
+ typename Types::LayerType* behind_replica_layer =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(210.f, 60.f),
+ gfx::Size(30, 30),
+ true);
+ typename Types::LayerType* filtered_surface =
+ this->CreateDrawingLayer(parent,
+ scale_by_half,
+ gfx::PointF(50.f, 50.f),
+ gfx::Size(100, 100),
+ false);
+ this->CreateReplicaLayer(filtered_surface,
+ this->identity_matrix,
+ gfx::PointF(300.f, 0.f),
+ gfx::Size());
+
+ // Filters make the layer own a surface.
+ FilterOperations filters;
+ filters.Append(FilterOperation::CreateBlurFilter(3.f));
+ filtered_surface->SetBackgroundFilters(filters);
+
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ // The surface has a background blur, so it blurs non-opaque pixels below
+ // it.
+ this->VisitLayer(filtered_surface, &occlusion);
+ this->VisitContributingSurface(filtered_surface, &occlusion);
+
+ this->VisitLayer(behind_replica_layer, &occlusion);
+ this->VisitLayer(behind_surface_layer, &occlusion);
+
+ // The layers behind the surface are not blurred, and their occlusion does
+ // not change, until we leave the surface. So it should not be modified by
+ // the filter here.
+ gfx::Rect occlusion_behind_surface = gfx::Rect(60, 60, 30, 30);
+ gfx::Rect occlusion_behind_replica = gfx::Rect(210, 60, 30, 30);
+
+ Region expected_opaque_bounds =
+ UnionRegions(occlusion_behind_surface, occlusion_behind_replica);
+ EXPECT_EQ(expected_opaque_bounds.ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(
+ OcclusionTrackerTestDontReduceOcclusionBelowBackgroundFilter);
+
+template <class Types>
+class OcclusionTrackerTestDontReduceOcclusionIfBackgroundFilterIsOccluded
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestDontReduceOcclusionIfBackgroundFilterIsOccluded(
+ bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ gfx::Transform scale_by_half;
+ scale_by_half.Scale(0.5, 0.5);
+
+ // Make a surface and its replica, each 50x50, that are completely occluded
+ // by opaque layers which are above them in the z-order. The surface is
+ // scaled to test that the pixel moving is done in the target space, where
+ // the background filter is applied, but the surface appears at 50, 50 and
+ // the replica at 200, 50.
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(300, 150));
+ typename Types::LayerType* filtered_surface =
+ this->CreateDrawingLayer(parent,
+ scale_by_half,
+ gfx::PointF(50.f, 50.f),
+ gfx::Size(100, 100),
+ false);
+ this->CreateReplicaLayer(filtered_surface,
+ this->identity_matrix,
+ gfx::PointF(300.f, 0.f),
+ gfx::Size());
+ typename Types::LayerType* above_surface_layer =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(50.f, 50.f),
+ gfx::Size(50, 50),
+ true);
+ typename Types::LayerType* above_replica_layer =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(200.f, 50.f),
+ gfx::Size(50, 50),
+ true);
+
+ // Filters make the layer own a surface.
+ FilterOperations filters;
+ filters.Append(FilterOperation::CreateBlurFilter(3.f));
+ filtered_surface->SetBackgroundFilters(filters);
+
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->VisitLayer(above_replica_layer, &occlusion);
+ this->VisitLayer(above_surface_layer, &occlusion);
+
+ this->VisitLayer(filtered_surface, &occlusion);
+ {
+ // The layers above the filtered surface occlude from outside.
+ gfx::Rect occlusion_above_surface = gfx::Rect(0, 0, 50, 50);
+ gfx::Rect occlusion_above_replica = gfx::Rect(150, 0, 50, 50);
+ Region expected_opaque_region =
+ UnionRegions(occlusion_above_surface, occlusion_above_replica);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ EXPECT_EQ(expected_opaque_region.ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ }
+
+ // The surface has a background blur, so it blurs non-opaque pixels below
+ // it.
+ this->VisitContributingSurface(filtered_surface, &occlusion);
+ {
+ // The filter is completely occluded, so it should not blur anything and
+ // reduce any occlusion.
+ gfx::Rect occlusion_above_surface = gfx::Rect(50, 50, 50, 50);
+ gfx::Rect occlusion_above_replica = gfx::Rect(200, 50, 50, 50);
+ Region expected_opaque_region =
+ UnionRegions(occlusion_above_surface, occlusion_above_replica);
+
+ EXPECT_EQ(expected_opaque_region.ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ }
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(
+ OcclusionTrackerTestDontReduceOcclusionIfBackgroundFilterIsOccluded);
+
+template <class Types>
+class OcclusionTrackerTestReduceOcclusionWhenBackgroundFilterIsPartiallyOccluded
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit
+ OcclusionTrackerTestReduceOcclusionWhenBackgroundFilterIsPartiallyOccluded(
+ bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ gfx::Transform scale_by_half;
+ scale_by_half.Scale(0.5, 0.5);
+
+ // Make a surface and its replica, each 50x50, that are partially occluded
+ // by opaque layers which are above them in the z-order. The surface is
+ // scaled to test that the pixel moving is done in the target space, where
+ // the background filter is applied, but the surface appears at 50, 50 and
+ // the replica at 200, 50.
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(300, 150));
+ typename Types::LayerType* filtered_surface =
+ this->CreateDrawingLayer(parent,
+ scale_by_half,
+ gfx::PointF(50.f, 50.f),
+ gfx::Size(100, 100),
+ false);
+ this->CreateReplicaLayer(filtered_surface,
+ this->identity_matrix,
+ gfx::PointF(300.f, 0.f),
+ gfx::Size());
+ typename Types::LayerType* above_surface_layer =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(70.f, 50.f),
+ gfx::Size(30, 50),
+ true);
+ typename Types::LayerType* above_replica_layer =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(200.f, 50.f),
+ gfx::Size(30, 50),
+ true);
+ typename Types::LayerType* beside_surface_layer =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(90.f, 40.f),
+ gfx::Size(10, 10),
+ true);
+ typename Types::LayerType* beside_replica_layer =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(200.f, 40.f),
+ gfx::Size(10, 10),
+ true);
+
+ // Filters make the layer own a surface.
+ FilterOperations filters;
+ filters.Append(FilterOperation::CreateBlurFilter(3.f));
+ filtered_surface->SetBackgroundFilters(filters);
+
+ // Save the distance of influence for the blur effect.
+ int outset_top, outset_right, outset_bottom, outset_left;
+ filters.GetOutsets(
+ &outset_top, &outset_right, &outset_bottom, &outset_left);
+
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->VisitLayer(beside_replica_layer, &occlusion);
+ this->VisitLayer(beside_surface_layer, &occlusion);
+ this->VisitLayer(above_replica_layer, &occlusion);
+ this->VisitLayer(above_surface_layer, &occlusion);
+
+ // The surface has a background blur, so it blurs non-opaque pixels below
+ // it.
+ this->VisitLayer(filtered_surface, &occlusion);
+ this->VisitContributingSurface(filtered_surface, &occlusion);
+
+ // The filter in the surface and replica are partially unoccluded. Only the
+ // unoccluded parts should reduce occlusion. This means it will push back
+ // the occlusion that touches the unoccluded part (occlusion_above___), but
+ // it will not touch occlusion_beside____ since that is not beside the
+ // unoccluded part of the surface, even though it is beside the occluded
+ // part of the surface.
+ gfx::Rect occlusion_above_surface =
+ gfx::Rect(70 + outset_right, 50, 30 - outset_right, 50);
+ gfx::Rect occlusion_above_replica =
+ gfx::Rect(200, 50, 30 - outset_left, 50);
+ gfx::Rect occlusion_beside_surface = gfx::Rect(90, 40, 10, 10);
+ gfx::Rect occlusion_beside_replica = gfx::Rect(200, 40, 10, 10);
+
+ Region expected_occlusion;
+ expected_occlusion.Union(occlusion_above_surface);
+ expected_occlusion.Union(occlusion_above_replica);
+ expected_occlusion.Union(occlusion_beside_surface);
+ expected_occlusion.Union(occlusion_beside_replica);
+
+ ASSERT_EQ(expected_occlusion.ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+
+ Region::Iterator expected_rects(expected_occlusion);
+ Region::Iterator target_surface_rects(
+ occlusion.occlusion_from_inside_target());
+ for (; expected_rects.has_rect();
+ expected_rects.next(), target_surface_rects.next()) {
+ ASSERT_TRUE(target_surface_rects.has_rect());
+ EXPECT_EQ(expected_rects.rect(), target_surface_rects.rect());
+ }
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(
+ OcclusionTrackerTestReduceOcclusionWhenBackgroundFilterIsPartiallyOccluded);
+
+template <class Types>
+class OcclusionTrackerTestMinimumTrackingSize
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestMinimumTrackingSize(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ gfx::Size tracking_size(100, 100);
+ gfx::Size below_tracking_size(99, 99);
+
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(400, 400));
+ typename Types::LayerType* large = this->CreateDrawingLayer(
+ parent, this->identity_matrix, gfx::PointF(), tracking_size, true);
+ typename Types::LayerType* small =
+ this->CreateDrawingLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(),
+ below_tracking_size,
+ true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+ occlusion.set_minimum_tracking_size(tracking_size);
+
+ // The small layer is not tracked because it is too small.
+ this->VisitLayer(small, &occlusion);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ // The large layer is tracked as it is large enough.
+ this->VisitLayer(large, &occlusion);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(tracking_size).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestMinimumTrackingSize);
+
+template <class Types>
+class OcclusionTrackerTestViewportClipIsExternalOcclusion
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestViewportClipIsExternalOcclusion(
+ bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(400, 400));
+ typename Types::LayerType* small =
+ this->CreateDrawingSurface(parent,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(200, 200),
+ false);
+ typename Types::LayerType* large =
+ this->CreateDrawingLayer(small,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(400, 400),
+ false);
+ small->SetMasksToBounds(true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 100, 100));
+
+ this->EnterLayer(large, &occlusion, false);
+
+ bool has_occlusion_from_outside_target_surface = false;
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 100),
+ occlusion.UnoccludedLayerContentRect(
+ large,
+ gfx::Rect(0, 0, 400, 400),
+ &has_occlusion_from_outside_target_surface));
+ EXPECT_TRUE(has_occlusion_from_outside_target_surface);
+
+ has_occlusion_from_outside_target_surface = false;
+ EXPECT_FALSE(
+ occlusion.OccludedLayer(large,
+ gfx::Rect(0, 0, 400, 400),
+ &has_occlusion_from_outside_target_surface));
+ EXPECT_TRUE(has_occlusion_from_outside_target_surface);
+
+ this->LeaveLayer(large, &occlusion);
+ this->VisitLayer(small, &occlusion);
+
+ has_occlusion_from_outside_target_surface = false;
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 100),
+ occlusion.UnoccludedLayerContentRect(
+ small,
+ gfx::Rect(0, 0, 200, 200),
+ &has_occlusion_from_outside_target_surface));
+ EXPECT_TRUE(has_occlusion_from_outside_target_surface);
+
+ has_occlusion_from_outside_target_surface = false;
+ EXPECT_FALSE(
+ occlusion.OccludedLayer(small,
+ gfx::Rect(0, 0, 200, 200),
+ &has_occlusion_from_outside_target_surface));
+ EXPECT_TRUE(has_occlusion_from_outside_target_surface);
+
+ this->EnterContributingSurface(small, &occlusion, false);
+
+ has_occlusion_from_outside_target_surface = false;
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 100),
+ occlusion.UnoccludedContributingSurfaceContentRect(
+ small,
+ false,
+ gfx::Rect(0, 0, 200, 200),
+ &has_occlusion_from_outside_target_surface));
+ EXPECT_TRUE(has_occlusion_from_outside_target_surface);
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(
+ OcclusionTrackerTestViewportClipIsExternalOcclusion)
+
+template <class Types>
+class OcclusionTrackerTestLayerClipIsExternalOcclusion
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestLayerClipIsExternalOcclusion(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(400, 400));
+ typename Types::LayerType* smallest = this->CreateDrawingLayer(
+ parent, this->identity_matrix, gfx::PointF(), gfx::Size(50, 50), false);
+ typename Types::LayerType* smaller =
+ this->CreateDrawingSurface(smallest,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(100, 100),
+ false);
+ typename Types::LayerType* small =
+ this->CreateDrawingSurface(smaller,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(200, 200),
+ false);
+ typename Types::LayerType* large =
+ this->CreateDrawingLayer(small,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(400, 400),
+ false);
+ smallest->SetMasksToBounds(true);
+ smaller->SetMasksToBounds(true);
+ small->SetMasksToBounds(true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->EnterLayer(large, &occlusion, false);
+
+ // Clipping from the smaller layer is from outside the target surface.
+ bool has_occlusion_from_outside_target_surface = false;
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 100),
+ occlusion.UnoccludedLayerContentRect(
+ large,
+ gfx::Rect(0, 0, 400, 400),
+ &has_occlusion_from_outside_target_surface));
+ EXPECT_TRUE(has_occlusion_from_outside_target_surface);
+
+ has_occlusion_from_outside_target_surface = false;
+ EXPECT_FALSE(
+ occlusion.OccludedLayer(large,
+ gfx::Rect(0, 0, 400, 400),
+ &has_occlusion_from_outside_target_surface));
+ EXPECT_TRUE(has_occlusion_from_outside_target_surface);
+
+ this->LeaveLayer(large, &occlusion);
+ this->VisitLayer(small, &occlusion);
+
+ // Clipping from the smaller layer is from outside the target surface.
+ has_occlusion_from_outside_target_surface = false;
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 100, 100),
+ occlusion.UnoccludedLayerContentRect(
+ small,
+ gfx::Rect(0, 0, 200, 200),
+ &has_occlusion_from_outside_target_surface));
+ EXPECT_TRUE(has_occlusion_from_outside_target_surface);
+
+ has_occlusion_from_outside_target_surface = false;
+ EXPECT_FALSE(
+ occlusion.OccludedLayer(small,
+ gfx::Rect(0, 0, 200, 200),
+ &has_occlusion_from_outside_target_surface));
+ EXPECT_TRUE(has_occlusion_from_outside_target_surface);
+
+ this->EnterContributingSurface(small, &occlusion, false);
+
+ // The |small| surface is clipped from outside its target by |smallest|.
+ has_occlusion_from_outside_target_surface = false;
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 50, 50),
+ occlusion.UnoccludedContributingSurfaceContentRect(
+ small,
+ false,
+ gfx::Rect(0, 0, 200, 200),
+ &has_occlusion_from_outside_target_surface));
+ EXPECT_TRUE(has_occlusion_from_outside_target_surface);
+
+ this->LeaveContributingSurface(small, &occlusion);
+ this->VisitLayer(smaller, &occlusion);
+ this->EnterContributingSurface(smaller, &occlusion, false);
+
+ // The |smaller| surface is clipped from inside its target by |smallest|.
+ has_occlusion_from_outside_target_surface = false;
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 50, 50),
+ occlusion.UnoccludedContributingSurfaceContentRect(
+ smaller,
+ false,
+ gfx::Rect(0, 0, 100, 100),
+ &has_occlusion_from_outside_target_surface));
+ EXPECT_FALSE(has_occlusion_from_outside_target_surface);
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestLayerClipIsExternalOcclusion)
+
+template <class Types>
+class OcclusionTrackerTestPreventOcclusionOnLayer
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestPreventOcclusionOnLayer(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(400, 400));
+ typename Types::LayerType* unprevented = this->CreateDrawingLayer(
+ parent, this->identity_matrix, gfx::PointF(), gfx::Size(50, 50), false);
+ typename Types::LayerType* prevented = this->CreateDrawingLayer(
+ parent, this->identity_matrix, gfx::PointF(), gfx::Size(50, 50), false);
+ typename Types::LayerType* occluding = this->CreateDrawingLayer(
+ parent, this->identity_matrix, gfx::PointF(), gfx::Size(50, 50), true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+ bool external_occlusion = false;
+
+ this->VisitLayer(occluding, &occlusion);
+ this->EnterLayer(prevented, &occlusion, true);
+
+ // This layer is not occluded because it is prevented.
+ EXPECT_FALSE(occlusion.OccludedLayer(prevented,
+ gfx::Rect(50, 50),
+ &external_occlusion));
+ EXPECT_FALSE(external_occlusion);
+
+ EXPECT_EQ(gfx::Rect(50, 50).ToString(),
+ occlusion.UnoccludedLayerContentRect(
+ prevented,
+ gfx::Rect(50, 50),
+ &external_occlusion).ToString());
+ EXPECT_FALSE(external_occlusion);
+
+ this->LeaveLayer(prevented, &occlusion);
+ this->EnterLayer(unprevented, &occlusion, false);
+
+ // This layer is fully occluded.
+ EXPECT_TRUE(occlusion.OccludedLayer(unprevented,
+ gfx::Rect(50, 50),
+ &external_occlusion));
+ EXPECT_FALSE(external_occlusion);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.UnoccludedLayerContentRect(
+ unprevented,
+ gfx::Rect(50, 50),
+ &external_occlusion).ToString());
+ EXPECT_FALSE(external_occlusion);
+
+ this->LeaveLayer(unprevented, &occlusion);
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestPreventOcclusionOnLayer)
+
+template <class Types>
+class OcclusionTrackerTestPreventOcclusionOnContributingSurface
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestPreventOcclusionOnContributingSurface(
+ bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(400, 400));
+ typename Types::LayerType* unprevented = this->CreateDrawingSurface(
+ parent, this->identity_matrix, gfx::PointF(), gfx::Size(50, 50), false);
+ typename Types::LayerType* prevented = this->CreateDrawingSurface(
+ parent, this->identity_matrix, gfx::PointF(), gfx::Size(50, 50), false);
+ typename Types::LayerType* occluding = this->CreateDrawingLayer(
+ parent, this->identity_matrix, gfx::PointF(), gfx::Size(50, 50), true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+ bool external_occlusion = false;
+
+ this->VisitLayer(occluding, &occlusion);
+ this->EnterLayer(prevented, &occlusion, true);
+
+ // This layer is not occluded because it is prevented.
+ EXPECT_EQ(gfx::Rect(50, 50).ToString(),
+ occlusion.UnoccludedLayerContentRect(
+ prevented,
+ gfx::Rect(50, 50),
+ &external_occlusion).ToString());
+ EXPECT_FALSE(external_occlusion);
+
+ this->LeaveLayer(prevented, &occlusion);
+ this->EnterContributingSurface(prevented, &occlusion, true);
+
+ // This contributing surface is not occluded because it is prevented.
+ EXPECT_EQ(gfx::Rect(50, 50).ToString(),
+ occlusion.UnoccludedContributingSurfaceContentRect(
+ prevented,
+ false, // is_replica
+ gfx::Rect(50, 50),
+ &external_occlusion).ToString());
+ EXPECT_FALSE(external_occlusion);
+
+ this->LeaveContributingSurface(prevented, &occlusion);
+ this->EnterLayer(unprevented, &occlusion, false);
+
+ // This layer is fully occluded from outside its surface.
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.UnoccludedLayerContentRect(
+ unprevented,
+ gfx::Rect(50, 50),
+ &external_occlusion).ToString());
+ EXPECT_TRUE(external_occlusion);
+
+ this->LeaveLayer(unprevented, &occlusion);
+ this->EnterContributingSurface(unprevented, &occlusion, false);
+
+ // This contributing surface is fully occluded.
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.UnoccludedContributingSurfaceContentRect(
+ unprevented,
+ false, // is_replica
+ gfx::Rect(50, 50),
+ &external_occlusion).ToString());
+ EXPECT_FALSE(external_occlusion);
+
+ this->LeaveContributingSurface(unprevented, &occlusion);
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(
+ OcclusionTrackerTestPreventOcclusionOnContributingSurface)
+
+template <class Types>
+class OcclusionTrackerTestPreventOcclusionByClipping
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestPreventOcclusionByClipping(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(400, 400));
+ typename Types::LayerType* unprevented = this->CreateDrawingLayer(
+ parent, this->identity_matrix, gfx::PointF(), gfx::Size(50, 50), false);
+ typename Types::LayerType* prevented = this->CreateDrawingLayer(
+ parent, this->identity_matrix, gfx::PointF(), gfx::Size(50, 50), false);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 10, 10));
+ bool external_occlusion = false;
+
+ this->EnterLayer(prevented, &occlusion, true);
+
+ // This layer is not occluded because it is prevented.
+ EXPECT_FALSE(occlusion.OccludedLayer(prevented,
+ gfx::Rect(50, 50),
+ &external_occlusion));
+ EXPECT_FALSE(external_occlusion);
+
+ EXPECT_EQ(gfx::Rect(50, 50).ToString(),
+ occlusion.UnoccludedLayerContentRect(
+ prevented,
+ gfx::Rect(50, 50),
+ &external_occlusion).ToString());
+ EXPECT_FALSE(external_occlusion);
+
+ this->LeaveLayer(prevented, &occlusion);
+ this->EnterLayer(unprevented, &occlusion, false);
+
+ // This layer is clipped by the screen space clip rect.
+ EXPECT_EQ(gfx::Rect(10, 10).ToString(),
+ occlusion.UnoccludedLayerContentRect(
+ unprevented,
+ gfx::Rect(50, 50),
+ &external_occlusion).ToString());
+ EXPECT_TRUE(external_occlusion);
+
+ this->LeaveLayer(unprevented, &occlusion);
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestPreventOcclusionByClipping)
+
+template <class Types>
+class OcclusionTrackerTestScaledLayerIsClipped
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestScaledLayerIsClipped(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ gfx::Transform scale_transform;
+ scale_transform.Scale(512.0, 512.0);
+
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(400, 400));
+ typename Types::LayerType* clip = this->CreateLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(10.f, 10.f),
+ gfx::Size(50, 50));
+ clip->SetMasksToBounds(true);
+ typename Types::LayerType* scale = this->CreateLayer(
+ clip, scale_transform, gfx::PointF(), gfx::Size(1, 1));
+ typename Types::LayerType* scaled = this->CreateDrawingLayer(
+ scale, this->identity_matrix, gfx::PointF(), gfx::Size(500, 500), true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->VisitLayer(scaled, &occlusion);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(10, 10, 50, 50).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestScaledLayerIsClipped)
+
+template <class Types>
+class OcclusionTrackerTestScaledLayerInSurfaceIsClipped
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestScaledLayerInSurfaceIsClipped(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ gfx::Transform scale_transform;
+ scale_transform.Scale(512.0, 512.0);
+
+ typename Types::ContentLayerType* parent = this->CreateRoot(
+ this->identity_matrix, gfx::PointF(), gfx::Size(400, 400));
+ typename Types::LayerType* clip = this->CreateLayer(parent,
+ this->identity_matrix,
+ gfx::PointF(10.f, 10.f),
+ gfx::Size(50, 50));
+ clip->SetMasksToBounds(true);
+ typename Types::LayerType* surface = this->CreateDrawingSurface(
+ clip, this->identity_matrix, gfx::PointF(), gfx::Size(400, 30), false);
+ typename Types::LayerType* scale = this->CreateLayer(
+ surface, scale_transform, gfx::PointF(), gfx::Size(1, 1));
+ typename Types::LayerType* scaled = this->CreateDrawingLayer(
+ scale, this->identity_matrix, gfx::PointF(), gfx::Size(500, 500), true);
+ this->CalcDrawEtc(parent);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->VisitLayer(scaled, &occlusion);
+ this->VisitLayer(surface, &occlusion);
+ this->VisitContributingSurface(surface, &occlusion);
+
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(10, 10, 50, 50).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestScaledLayerInSurfaceIsClipped)
+
+template <class Types>
+class OcclusionTrackerTestCopyRequestDoesOcclude
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestCopyRequestDoesOcclude(bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ typename Types::ContentLayerType* root = this->CreateRoot(
+ this->identity_matrix, gfx::Point(), gfx::Size(400, 400));
+ typename Types::ContentLayerType* parent = this->CreateDrawingLayer(
+ root, this->identity_matrix, gfx::Point(), gfx::Size(400, 400), true);
+ typename Types::LayerType* copy = this->CreateLayer(parent,
+ this->identity_matrix,
+ gfx::Point(100, 0),
+ gfx::Size(200, 400));
+ this->AddCopyRequest(copy);
+ typename Types::LayerType* copy_child = this->CreateDrawingLayer(
+ copy,
+ this->identity_matrix,
+ gfx::PointF(),
+ gfx::Size(200, 400),
+ true);
+ this->CalcDrawEtc(root);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->VisitLayer(copy_child, &occlusion);
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(200, 400).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ // CopyRequests cause the layer to own a surface.
+ this->VisitContributingSurface(copy, &occlusion);
+
+ // The occlusion from the copy should be kept.
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(100, 0, 200, 400).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestCopyRequestDoesOcclude)
+
+template <class Types>
+class OcclusionTrackerTestHiddenCopyRequestDoesNotOcclude
+ : public OcclusionTrackerTest<Types> {
+ protected:
+ explicit OcclusionTrackerTestHiddenCopyRequestDoesNotOcclude(
+ bool opaque_layers)
+ : OcclusionTrackerTest<Types>(opaque_layers) {}
+ void RunMyTest() {
+ typename Types::ContentLayerType* root = this->CreateRoot(
+ this->identity_matrix, gfx::Point(), gfx::Size(400, 400));
+ typename Types::ContentLayerType* parent = this->CreateDrawingLayer(
+ root, this->identity_matrix, gfx::Point(), gfx::Size(400, 400), true);
+ typename Types::LayerType* hide = this->CreateLayer(
+ parent, this->identity_matrix, gfx::Point(), gfx::Size());
+ typename Types::LayerType* copy = this->CreateLayer(
+ hide, this->identity_matrix, gfx::Point(100, 0), gfx::Size(200, 400));
+ this->AddCopyRequest(copy);
+ typename Types::LayerType* copy_child = this->CreateDrawingLayer(
+ copy, this->identity_matrix, gfx::PointF(), gfx::Size(200, 400), true);
+
+ // The |copy| layer is hidden but since it is being copied, it will be
+ // drawn.
+ hide->SetHideLayerAndSubtree(true);
+
+ this->CalcDrawEtc(root);
+
+ TestOcclusionTrackerWithClip<typename Types::LayerType,
+ typename Types::RenderSurfaceType> occlusion(
+ gfx::Rect(0, 0, 1000, 1000));
+
+ this->VisitLayer(copy_child, &occlusion);
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect(200, 400).ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+
+ // CopyRequests cause the layer to own a surface.
+ this->VisitContributingSurface(copy, &occlusion);
+
+ // The occlusion from the copy should be dropped since it is hidden.
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_outside_target().ToString());
+ EXPECT_EQ(gfx::Rect().ToString(),
+ occlusion.occlusion_from_inside_target().ToString());
+ }
+};
+
+ALL_OCCLUSIONTRACKER_TEST(OcclusionTrackerTestHiddenCopyRequestDoesNotOcclude)
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/trees/proxy.cc b/chromium/cc/trees/proxy.cc
new file mode 100644
index 00000000000..c8dacafb234
--- /dev/null
+++ b/chromium/cc/trees/proxy.cc
@@ -0,0 +1,84 @@
+// Copyright 2011 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 "cc/trees/proxy.h"
+
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/single_thread_task_runner.h"
+
+namespace cc {
+
+base::SingleThreadTaskRunner* Proxy::MainThreadTaskRunner() const {
+ return main_task_runner_.get();
+}
+
+bool Proxy::HasImplThread() const { return !!impl_task_runner_.get(); }
+
+base::SingleThreadTaskRunner* Proxy::ImplThreadTaskRunner() const {
+ return impl_task_runner_.get();
+}
+
+bool Proxy::IsMainThread() const {
+#ifndef NDEBUG
+ DCHECK(main_task_runner_.get());
+ if (impl_thread_is_overridden_)
+ return false;
+ return main_task_runner_->BelongsToCurrentThread();
+#else
+ return true;
+#endif
+}
+
+bool Proxy::IsImplThread() const {
+#ifndef NDEBUG
+ if (impl_thread_is_overridden_)
+ return true;
+ if (!impl_task_runner_.get())
+ return false;
+ return impl_task_runner_->BelongsToCurrentThread();
+#else
+ return true;
+#endif
+}
+
+#ifndef NDEBUG
+void Proxy::SetCurrentThreadIsImplThread(bool is_impl_thread) {
+ impl_thread_is_overridden_ = is_impl_thread;
+}
+#endif
+
+bool Proxy::IsMainThreadBlocked() const {
+#ifndef NDEBUG
+ return is_main_thread_blocked_;
+#else
+ return true;
+#endif
+}
+
+#ifndef NDEBUG
+void Proxy::SetMainThreadBlocked(bool is_main_thread_blocked) {
+ is_main_thread_blocked_ = is_main_thread_blocked;
+}
+#endif
+
+Proxy::Proxy(
+ scoped_refptr<base::SingleThreadTaskRunner> impl_task_runner)
+ : main_task_runner_(base::MessageLoopProxy::current()),
+#ifdef NDEBUG
+ impl_task_runner_(impl_task_runner) {}
+#else
+ impl_task_runner_(impl_task_runner),
+ impl_thread_is_overridden_(false),
+ is_main_thread_blocked_(false) {}
+#endif
+
+Proxy::~Proxy() {
+ DCHECK(IsMainThread());
+}
+
+std::string Proxy::SchedulerStateAsStringForTesting() {
+ return "";
+}
+
+} // namespace cc
diff --git a/chromium/cc/trees/proxy.h b/chromium/cc/trees/proxy.h
new file mode 100644
index 00000000000..c5f9d555711
--- /dev/null
+++ b/chromium/cc/trees/proxy.h
@@ -0,0 +1,148 @@
+// Copyright 2011 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.
+
+#ifndef CC_TREES_PROXY_H_
+#define CC_TREES_PROXY_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "cc/base/cc_export.h"
+
+namespace base { class SingleThreadTaskRunner; }
+
+namespace gfx {
+class Rect;
+class Vector2d;
+}
+
+namespace cc {
+
+class OutputSurface;
+struct RendererCapabilities;
+
+// Abstract class responsible for proxying commands from the main-thread side of
+// the compositor over to the compositor implementation.
+class CC_EXPORT Proxy {
+ public:
+ base::SingleThreadTaskRunner* MainThreadTaskRunner() const;
+ bool HasImplThread() const;
+ base::SingleThreadTaskRunner* ImplThreadTaskRunner() const;
+
+ // Debug hooks.
+ bool IsMainThread() const;
+ bool IsImplThread() const;
+ bool IsMainThreadBlocked() const;
+#ifndef NDEBUG
+ void SetMainThreadBlocked(bool is_main_thread_blocked);
+ void SetCurrentThreadIsImplThread(bool is_impl_thread);
+#endif
+
+ virtual ~Proxy();
+
+ virtual bool CompositeAndReadback(void* pixels, gfx::Rect rect) = 0;
+
+ virtual void FinishAllRendering() = 0;
+
+ virtual bool IsStarted() const = 0;
+
+ // Indicates that the compositing surface associated with our context is
+ // ready to use.
+ virtual void SetLayerTreeHostClientReady() = 0;
+
+ virtual void SetVisible(bool visible) = 0;
+
+ // Attempts to recreate the context and renderer synchronously after the
+ // output surface is lost. Calls
+ // LayerTreeHost::OnCreateAndInitializeOutputSurfaceAttempted with the result.
+ virtual void CreateAndInitializeOutputSurface() = 0;
+
+ virtual const RendererCapabilities& GetRendererCapabilities() const = 0;
+
+ virtual void SetNeedsAnimate() = 0;
+ virtual void SetNeedsUpdateLayers() = 0;
+ virtual void SetNeedsCommit() = 0;
+ virtual void SetNeedsRedraw(gfx::Rect damage_rect) = 0;
+
+ virtual void NotifyInputThrottledUntilCommit() = 0;
+
+ // Defers commits until it is reset. It is only supported when in threaded
+ // mode. It's an error to make a sync call like CompositeAndReadback while
+ // commits are deferred.
+ virtual void SetDeferCommits(bool defer_commits) = 0;
+
+ virtual void MainThreadHasStoppedFlinging() = 0;
+
+ virtual bool CommitRequested() const = 0;
+
+ // Must be called before using the proxy.
+ virtual void Start(scoped_ptr<OutputSurface> first_output_surface) = 0;
+ virtual void Stop() = 0; // Must be called before deleting the proxy.
+
+ // Forces 3D commands on all contexts to wait for all previous SwapBuffers
+ // to finish before executing in the GPU process.
+ virtual void ForceSerializeOnSwapBuffers() = 0;
+
+ // Maximum number of sub-region texture updates supported for each commit.
+ virtual size_t MaxPartialTextureUpdates() const = 0;
+
+ virtual void AcquireLayerTextures() = 0;
+
+ virtual scoped_ptr<base::Value> AsValue() const = 0;
+
+ // Testing hooks
+ virtual bool CommitPendingForTesting() = 0;
+ virtual std::string SchedulerStateAsStringForTesting();
+
+ protected:
+ explicit Proxy(
+ scoped_refptr<base::SingleThreadTaskRunner> impl_task_runner);
+ friend class DebugScopedSetImplThread;
+ friend class DebugScopedSetMainThread;
+ friend class DebugScopedSetMainThreadBlocked;
+
+ private:
+ scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
+ scoped_refptr<base::SingleThreadTaskRunner> impl_task_runner_;
+#ifndef NDEBUG
+ bool impl_thread_is_overridden_;
+ bool is_main_thread_blocked_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(Proxy);
+};
+
+#ifndef NDEBUG
+class DebugScopedSetMainThreadBlocked {
+ public:
+ explicit DebugScopedSetMainThreadBlocked(Proxy* proxy) : proxy_(proxy) {
+ DCHECK(!proxy_->IsMainThreadBlocked());
+ proxy_->SetMainThreadBlocked(true);
+ }
+ ~DebugScopedSetMainThreadBlocked() {
+ DCHECK(proxy_->IsMainThreadBlocked());
+ proxy_->SetMainThreadBlocked(false);
+ }
+ private:
+ Proxy* proxy_;
+ DISALLOW_COPY_AND_ASSIGN(DebugScopedSetMainThreadBlocked);
+};
+#else
+class DebugScopedSetMainThreadBlocked {
+ public:
+ explicit DebugScopedSetMainThreadBlocked(Proxy* proxy) {}
+ ~DebugScopedSetMainThreadBlocked() {}
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DebugScopedSetMainThreadBlocked);
+};
+#endif
+
+} // namespace cc
+
+#endif // CC_TREES_PROXY_H_
diff --git a/chromium/cc/trees/quad_culler.cc b/chromium/cc/trees/quad_culler.cc
new file mode 100644
index 00000000000..760c79dad8d
--- /dev/null
+++ b/chromium/cc/trees/quad_culler.cc
@@ -0,0 +1,118 @@
+// 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 "cc/trees/quad_culler.h"
+
+#include "cc/debug/debug_colors.h"
+#include "cc/debug/overdraw_metrics.h"
+#include "cc/layers/append_quads_data.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/quads/debug_border_draw_quad.h"
+#include "cc/quads/render_pass.h"
+#include "cc/trees/occlusion_tracker.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+
+QuadCuller::QuadCuller(QuadList* quad_list,
+ SharedQuadStateList* shared_quad_state_list,
+ const LayerImpl* layer,
+ const OcclusionTrackerImpl& occlusion_tracker,
+ bool show_culling_with_debug_border_quads,
+ bool for_surface)
+ : quad_list_(quad_list),
+ shared_quad_state_list_(shared_quad_state_list),
+ layer_(layer),
+ occlusion_tracker_(occlusion_tracker),
+ current_shared_quad_state_(NULL),
+ show_culling_with_debug_border_quads_(
+ show_culling_with_debug_border_quads),
+ for_surface_(for_surface) {}
+
+SharedQuadState* QuadCuller::UseSharedQuadState(
+ scoped_ptr<SharedQuadState> shared_quad_state) {
+ // TODO(danakj): If all quads are culled for the shared_quad_state, we can
+ // drop it from the list.
+ current_shared_quad_state_ = shared_quad_state.get();
+ shared_quad_state_list_->push_back(shared_quad_state.Pass());
+ return current_shared_quad_state_;
+}
+
+static inline bool AppendQuadInternal(
+ scoped_ptr<DrawQuad> draw_quad,
+ gfx::Rect culled_rect,
+ QuadList* quad_list,
+ const OcclusionTrackerImpl& occlusion_tracker,
+ const LayerImpl* layer,
+ bool create_debug_border_quads) {
+ bool keep_quad = !culled_rect.IsEmpty();
+ if (keep_quad)
+ draw_quad->visible_rect = culled_rect;
+
+ occlusion_tracker.overdraw_metrics()->DidCullForDrawing(
+ draw_quad->quadTransform(), draw_quad->rect, culled_rect);
+ gfx::Rect opaque_draw_rect =
+ draw_quad->opacity() == 1.0f ? draw_quad->opaque_rect : gfx::Rect();
+ occlusion_tracker.overdraw_metrics()->
+ DidDraw(draw_quad->quadTransform(), culled_rect, opaque_draw_rect);
+
+ if (keep_quad) {
+ if (create_debug_border_quads && !draw_quad->IsDebugQuad() &&
+ draw_quad->visible_rect != draw_quad->rect) {
+ SkColor color = DebugColors::CulledTileBorderColor();
+ float width = DebugColors::CulledTileBorderWidth(
+ layer ? layer->layer_tree_impl() : NULL);
+ scoped_ptr<DebugBorderDrawQuad> debug_border_quad =
+ DebugBorderDrawQuad::Create();
+ debug_border_quad->SetNew(
+ draw_quad->shared_quad_state, draw_quad->visible_rect, color, width);
+ quad_list->push_back(debug_border_quad.PassAs<DrawQuad>());
+ }
+
+ // Pass the quad after we're done using it.
+ quad_list->push_back(draw_quad.Pass());
+ }
+ return keep_quad;
+}
+
+bool QuadCuller::Append(scoped_ptr<DrawQuad> draw_quad,
+ AppendQuadsData* append_quads_data) {
+ DCHECK(draw_quad->shared_quad_state == current_shared_quad_state_);
+ DCHECK(!shared_quad_state_list_->empty());
+ DCHECK(shared_quad_state_list_->back() == current_shared_quad_state_);
+
+ gfx::Rect culled_rect;
+ bool has_occlusion_from_outside_target_surface;
+ bool impl_draw_transform_is_unknown = false;
+
+ if (for_surface_) {
+ culled_rect = occlusion_tracker_.UnoccludedContributingSurfaceContentRect(
+ layer_,
+ false,
+ draw_quad->rect,
+ &has_occlusion_from_outside_target_surface);
+ } else {
+ culled_rect = occlusion_tracker_.UnoccludedContentRect(
+ layer_->render_target(),
+ draw_quad->rect,
+ draw_quad->quadTransform(),
+ impl_draw_transform_is_unknown,
+ draw_quad->isClipped(),
+ draw_quad->clipRect(),
+ &has_occlusion_from_outside_target_surface);
+ }
+
+ append_quads_data->had_occlusion_from_outside_target_surface |=
+ has_occlusion_from_outside_target_surface;
+
+ return AppendQuadInternal(draw_quad.Pass(),
+ culled_rect,
+ quad_list_,
+ occlusion_tracker_,
+ layer_,
+ show_culling_with_debug_border_quads_);
+}
+
+} // namespace cc
diff --git a/chromium/cc/trees/quad_culler.h b/chromium/cc/trees/quad_culler.h
new file mode 100644
index 00000000000..be0095f9f41
--- /dev/null
+++ b/chromium/cc/trees/quad_culler.h
@@ -0,0 +1,49 @@
+// 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.
+
+#ifndef CC_TREES_QUAD_CULLER_H_
+#define CC_TREES_QUAD_CULLER_H_
+
+#include "cc/base/cc_export.h"
+#include "cc/layers/quad_sink.h"
+#include "cc/quads/render_pass.h"
+
+namespace cc {
+class LayerImpl;
+class RenderSurfaceImpl;
+template <typename LayerType, typename SurfaceType> class OcclusionTrackerBase;
+
+class CC_EXPORT QuadCuller : public QuadSink {
+ public:
+ QuadCuller(QuadList* quad_list,
+ SharedQuadStateList* shared_quad_state_list,
+ const LayerImpl* layer,
+ const OcclusionTrackerBase<LayerImpl, RenderSurfaceImpl>&
+ occlusion_tracker,
+ bool show_culling_with_debug_border_quads,
+ bool for_surface);
+ virtual ~QuadCuller() {}
+
+ // QuadSink implementation.
+ virtual SharedQuadState* UseSharedQuadState(
+ scoped_ptr<SharedQuadState> shared_quad_state) OVERRIDE;
+ virtual bool Append(scoped_ptr<DrawQuad> draw_quad,
+ AppendQuadsData* append_quads_data) OVERRIDE;
+
+ private:
+ QuadList* quad_list_;
+ SharedQuadStateList* shared_quad_state_list_;
+ const LayerImpl* layer_;
+ const OcclusionTrackerBase<LayerImpl, RenderSurfaceImpl>& occlusion_tracker_;
+
+ SharedQuadState* current_shared_quad_state_;
+ bool show_culling_with_debug_border_quads_;
+ bool for_surface_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuadCuller);
+};
+
+} // namespace cc
+
+#endif // CC_TREES_QUAD_CULLER_H_
diff --git a/chromium/cc/trees/quad_culler_unittest.cc b/chromium/cc/trees/quad_culler_unittest.cc
new file mode 100644
index 00000000000..120e0072087
--- /dev/null
+++ b/chromium/cc/trees/quad_culler_unittest.cc
@@ -0,0 +1,936 @@
+// 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 "cc/trees/quad_culler.h"
+
+#include <vector>
+
+#include "cc/base/math_util.h"
+#include "cc/debug/overdraw_metrics.h"
+#include "cc/layers/append_quads_data.h"
+#include "cc/layers/render_surface_impl.h"
+#include "cc/layers/tiled_layer_impl.h"
+#include "cc/quads/tile_draw_quad.h"
+#include "cc/resources/layer_tiling_data.h"
+#include "cc/test/fake_impl_proxy.h"
+#include "cc/test/fake_layer_tree_host_impl.h"
+#include "cc/trees/occlusion_tracker.h"
+#include "cc/trees/single_thread_proxy.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/transform.h"
+
+namespace cc {
+namespace {
+
+class TestOcclusionTrackerImpl : public OcclusionTrackerImpl {
+ public:
+ TestOcclusionTrackerImpl(gfx::Rect scissor_rect_in_screen,
+ bool record_metrics_for_frame = true)
+ : OcclusionTrackerImpl(scissor_rect_in_screen, record_metrics_for_frame),
+ scissor_rect_in_screen_(scissor_rect_in_screen) {}
+
+ protected:
+ virtual gfx::Rect LayerScissorRectInTargetSurface(
+ const LayerImpl* layer) const {
+ return scissor_rect_in_screen_;
+ }
+
+ private:
+ gfx::Rect scissor_rect_in_screen_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestOcclusionTrackerImpl);
+};
+
+typedef LayerIterator<LayerImpl,
+ LayerImplList,
+ RenderSurfaceImpl,
+ LayerIteratorActions::FrontToBack> LayerIteratorType;
+
+class QuadCullerTest : public testing::Test {
+ public:
+ QuadCullerTest()
+ : host_impl_(&proxy_),
+ layer_id_(1) {}
+
+ scoped_ptr<TiledLayerImpl> MakeLayer(
+ TiledLayerImpl* parent,
+ const gfx::Transform& draw_transform,
+ gfx::Rect layer_rect,
+ float opacity,
+ bool opaque,
+ gfx::Rect layer_opaque_rect,
+ LayerImplList& surface_layer_list) {
+ scoped_ptr<TiledLayerImpl> layer =
+ TiledLayerImpl::Create(host_impl_.active_tree(), layer_id_++);
+ scoped_ptr<LayerTilingData> tiler = LayerTilingData::Create(
+ gfx::Size(100, 100), LayerTilingData::NO_BORDER_TEXELS);
+ tiler->SetBounds(layer_rect.size());
+ layer->SetTilingData(*tiler);
+ layer->set_skips_draw(false);
+ layer->draw_properties().target_space_transform = draw_transform;
+ layer->draw_properties().screen_space_transform = draw_transform;
+ layer->draw_properties().visible_content_rect = layer_rect;
+ layer->draw_properties().opacity = opacity;
+ layer->SetContentsOpaque(opaque);
+ layer->SetBounds(layer_rect.size());
+ layer->SetContentBounds(layer_rect.size());
+
+ ResourceProvider::ResourceId resource_id = 1;
+ for (int i = 0; i < tiler->num_tiles_x(); ++i) {
+ for (int j = 0; j < tiler->num_tiles_y(); ++j) {
+ gfx::Rect tile_opaque_rect =
+ opaque
+ ? tiler->tile_bounds(i, j)
+ : gfx::IntersectRects(tiler->tile_bounds(i, j), layer_opaque_rect);
+ layer->PushTileProperties(i, j, resource_id++, tile_opaque_rect, false);
+ }
+ }
+
+ gfx::Rect rect_in_target = MathUtil::MapClippedRect(
+ layer->draw_transform(), layer->visible_content_rect());
+ if (!parent) {
+ layer->CreateRenderSurface();
+ layer->render_surface()->SetContentRect(rect_in_target);
+ surface_layer_list.push_back(layer.get());
+ layer->render_surface()->layer_list().push_back(layer.get());
+ } else {
+ layer->draw_properties().render_target = parent->render_target();
+ parent->render_surface()->layer_list().push_back(layer.get());
+ rect_in_target.Union(MathUtil::MapClippedRect(
+ parent->draw_transform(), parent->visible_content_rect()));
+ parent->render_surface()->SetContentRect(rect_in_target);
+ }
+ layer->draw_properties().drawable_content_rect = rect_in_target;
+
+ return layer.Pass();
+ }
+
+ void AppendQuads(QuadList* quad_list,
+ SharedQuadStateList* shared_state_list,
+ TiledLayerImpl* layer,
+ LayerIteratorType* it,
+ OcclusionTrackerImpl* occlusion_tracker) {
+ occlusion_tracker->EnterLayer(*it, false);
+ QuadCuller quad_culler(
+ quad_list, shared_state_list, layer, *occlusion_tracker, false, false);
+ AppendQuadsData data;
+ layer->AppendQuads(&quad_culler, &data);
+ occlusion_tracker->LeaveLayer(*it);
+ ++it;
+ }
+
+ protected:
+ FakeImplProxy proxy_;
+ FakeLayerTreeHostImpl host_impl_;
+ int layer_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuadCullerTest);
+};
+
+#define DECLARE_AND_INITIALIZE_TEST_QUADS() \
+ QuadList quad_list; \
+ SharedQuadStateList shared_state_list; \
+ LayerImplList render_surface_layer_list; \
+ gfx::Transform child_transform; \
+ gfx::Size root_size = gfx::Size(300, 300); \
+ gfx::Rect root_rect = gfx::Rect(root_size); \
+ gfx::Size child_size = gfx::Size(200, 200); \
+ gfx::Rect child_rect = gfx::Rect(child_size);
+
+TEST_F(QuadCullerTest, VerifyNoCulling) {
+ DECLARE_AND_INITIALIZE_TEST_QUADS();
+
+ scoped_ptr<TiledLayerImpl> root_layer =
+ MakeLayer(NULL,
+ gfx::Transform(),
+ root_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ scoped_ptr<TiledLayerImpl> child_layer = MakeLayer(root_layer.get(),
+ gfx::Transform(),
+ child_rect,
+ 1,
+ false,
+ gfx::Rect(),
+ render_surface_layer_list);
+ TestOcclusionTrackerImpl occlusion_tracker(gfx::Rect(-100, -100, 1000, 1000));
+ LayerIteratorType it = LayerIteratorType::Begin(&render_surface_layer_list);
+
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ child_layer.get(),
+ &it,
+ &occlusion_tracker);
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ root_layer.get(),
+ &it,
+ &occlusion_tracker);
+ EXPECT_EQ(quad_list.size(), 13u);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_opaque(), 90000, 1);
+ EXPECT_NEAR(occlusion_tracker.overdraw_metrics()->pixels_drawn_translucent(),
+ 40000,
+ 1);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_culled_for_drawing(), 0, 1);
+}
+
+TEST_F(QuadCullerTest, VerifyCullChildLinesUpTopLeft) {
+ DECLARE_AND_INITIALIZE_TEST_QUADS();
+
+ scoped_ptr<TiledLayerImpl> root_layer =
+ MakeLayer(NULL,
+ gfx::Transform(),
+ root_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ scoped_ptr<TiledLayerImpl> child_layer = MakeLayer(root_layer.get(),
+ gfx::Transform(),
+ child_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ TestOcclusionTrackerImpl occlusion_tracker(gfx::Rect(-100, -100, 1000, 1000));
+ LayerIteratorType it = LayerIteratorType::Begin(&render_surface_layer_list);
+
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ child_layer.get(),
+ &it,
+ &occlusion_tracker);
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ root_layer.get(),
+ &it,
+ &occlusion_tracker);
+ EXPECT_EQ(quad_list.size(), 9u);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_opaque(), 90000, 1);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_translucent(), 0, 1);
+ EXPECT_NEAR(occlusion_tracker.overdraw_metrics()->pixels_culled_for_drawing(),
+ 40000,
+ 1);
+}
+
+TEST_F(QuadCullerTest, VerifyCullWhenChildOpacityNotOne) {
+ DECLARE_AND_INITIALIZE_TEST_QUADS();
+
+ scoped_ptr<TiledLayerImpl> root_layer =
+ MakeLayer(NULL,
+ gfx::Transform(),
+ root_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ scoped_ptr<TiledLayerImpl> child_layer = MakeLayer(root_layer.get(),
+ child_transform,
+ child_rect,
+ 0.9f,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ TestOcclusionTrackerImpl occlusion_tracker(gfx::Rect(-100, -100, 1000, 1000));
+ LayerIteratorType it = LayerIteratorType::Begin(&render_surface_layer_list);
+
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ child_layer.get(),
+ &it,
+ &occlusion_tracker);
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ root_layer.get(),
+ &it,
+ &occlusion_tracker);
+ EXPECT_EQ(quad_list.size(), 13u);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_opaque(), 90000, 1);
+ EXPECT_NEAR(occlusion_tracker.overdraw_metrics()->pixels_drawn_translucent(),
+ 40000,
+ 1);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_culled_for_drawing(), 0, 1);
+}
+
+TEST_F(QuadCullerTest, VerifyCullWhenChildOpaqueFlagFalse) {
+ DECLARE_AND_INITIALIZE_TEST_QUADS();
+
+ scoped_ptr<TiledLayerImpl> root_layer =
+ MakeLayer(NULL,
+ gfx::Transform(),
+ root_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ scoped_ptr<TiledLayerImpl> child_layer = MakeLayer(root_layer.get(),
+ child_transform,
+ child_rect,
+ 1,
+ false,
+ gfx::Rect(),
+ render_surface_layer_list);
+ TestOcclusionTrackerImpl occlusion_tracker(gfx::Rect(-100, -100, 1000, 1000));
+ LayerIteratorType it = LayerIteratorType::Begin(&render_surface_layer_list);
+
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ child_layer.get(),
+ &it,
+ &occlusion_tracker);
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ root_layer.get(),
+ &it,
+ &occlusion_tracker);
+ EXPECT_EQ(quad_list.size(), 13u);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_opaque(), 90000, 1);
+ EXPECT_NEAR(occlusion_tracker.overdraw_metrics()->pixels_drawn_translucent(),
+ 40000,
+ 1);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_culled_for_drawing(), 0, 1);
+}
+
+TEST_F(QuadCullerTest, VerifyCullCenterTileOnly) {
+ DECLARE_AND_INITIALIZE_TEST_QUADS();
+
+ child_transform.Translate(50, 50);
+ scoped_ptr<TiledLayerImpl> root_layer = MakeLayer(NULL,
+ gfx::Transform(),
+ root_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ scoped_ptr<TiledLayerImpl> child_layer = MakeLayer(root_layer.get(),
+ child_transform,
+ child_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ TestOcclusionTrackerImpl occlusion_tracker(gfx::Rect(-100, -100, 1000, 1000));
+ LayerIteratorType it = LayerIteratorType::Begin(&render_surface_layer_list);
+
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ child_layer.get(),
+ &it,
+ &occlusion_tracker);
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ root_layer.get(),
+ &it,
+ &occlusion_tracker);
+ ASSERT_EQ(quad_list.size(), 12u);
+
+ gfx::Rect quad_visible_rect1 = quad_list[5]->visible_rect;
+ EXPECT_EQ(quad_visible_rect1.height(), 50);
+
+ gfx::Rect quad_visible_rect3 = quad_list[7]->visible_rect;
+ EXPECT_EQ(quad_visible_rect3.width(), 50);
+
+ // Next index is 8, not 9, since centre quad culled.
+ gfx::Rect quad_visible_rect4 = quad_list[8]->visible_rect;
+ EXPECT_EQ(quad_visible_rect4.width(), 50);
+ EXPECT_EQ(quad_visible_rect4.x(), 250);
+
+ gfx::Rect quad_visible_rect6 = quad_list[10]->visible_rect;
+ EXPECT_EQ(quad_visible_rect6.height(), 50);
+ EXPECT_EQ(quad_visible_rect6.y(), 250);
+
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_opaque(), 100000, 1);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_translucent(), 0, 1);
+ EXPECT_NEAR(occlusion_tracker.overdraw_metrics()->pixels_culled_for_drawing(),
+ 30000,
+ 1);
+}
+
+TEST_F(QuadCullerTest, VerifyCullCenterTileNonIntegralSize1) {
+ DECLARE_AND_INITIALIZE_TEST_QUADS();
+
+ child_transform.Translate(100, 100);
+
+ // Make the root layer's quad have extent (99.1, 99.1) -> (200.9, 200.9) to
+ // make sure it doesn't get culled due to transform rounding.
+ gfx::Transform root_transform;
+ root_transform.Translate(99.1, 99.1);
+ root_transform.Scale(1.018, 1.018);
+
+ root_rect = child_rect = gfx::Rect(0, 0, 100, 100);
+
+ scoped_ptr<TiledLayerImpl> root_layer = MakeLayer(NULL,
+ root_transform,
+ root_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ scoped_ptr<TiledLayerImpl> child_layer = MakeLayer(root_layer.get(),
+ child_transform,
+ child_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ TestOcclusionTrackerImpl occlusion_tracker(gfx::Rect(-100, -100, 1000, 1000));
+ LayerIteratorType it = LayerIteratorType::Begin(&render_surface_layer_list);
+
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ child_layer.get(),
+ &it,
+ &occlusion_tracker);
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ root_layer.get(),
+ &it,
+ &occlusion_tracker);
+ EXPECT_EQ(quad_list.size(), 2u);
+
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_opaque(), 20363, 1);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_translucent(), 0, 1);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_culled_for_drawing(), 0, 1);
+}
+
+TEST_F(QuadCullerTest, VerifyCullCenterTileNonIntegralSize2) {
+ DECLARE_AND_INITIALIZE_TEST_QUADS();
+
+ // Make the child's quad slightly smaller than, and centred over, the root
+ // layer tile. Verify the child does not cause the quad below to be culled
+ // due to rounding.
+ child_transform.Translate(100.1, 100.1);
+ child_transform.Scale(0.982, 0.982);
+
+ gfx::Transform root_transform;
+ root_transform.Translate(100, 100);
+
+ root_rect = child_rect = gfx::Rect(0, 0, 100, 100);
+
+ scoped_ptr<TiledLayerImpl> root_layer = MakeLayer(NULL,
+ root_transform,
+ root_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ scoped_ptr<TiledLayerImpl> child_layer = MakeLayer(root_layer.get(),
+ child_transform,
+ child_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ TestOcclusionTrackerImpl occlusion_tracker(gfx::Rect(-100, -100, 1000, 1000));
+ LayerIteratorType it = LayerIteratorType::Begin(&render_surface_layer_list);
+
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ child_layer.get(),
+ &it,
+ &occlusion_tracker);
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ root_layer.get(),
+ &it,
+ &occlusion_tracker);
+ EXPECT_EQ(quad_list.size(), 2u);
+
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_opaque(), 19643, 1);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_translucent(), 0, 1);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_culled_for_drawing(), 0, 1);
+}
+
+TEST_F(QuadCullerTest, VerifyCullChildLinesUpBottomRight) {
+ DECLARE_AND_INITIALIZE_TEST_QUADS();
+
+ child_transform.Translate(100, 100);
+ scoped_ptr<TiledLayerImpl> root_layer = MakeLayer(NULL,
+ gfx::Transform(),
+ root_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ scoped_ptr<TiledLayerImpl> child_layer = MakeLayer(root_layer.get(),
+ child_transform,
+ child_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ TestOcclusionTrackerImpl occlusion_tracker(gfx::Rect(-100, -100, 1000, 1000));
+ LayerIteratorType it = LayerIteratorType::Begin(&render_surface_layer_list);
+
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ child_layer.get(),
+ &it,
+ &occlusion_tracker);
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ root_layer.get(),
+ &it,
+ &occlusion_tracker);
+ EXPECT_EQ(quad_list.size(), 9u);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_opaque(), 90000, 1);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_translucent(), 0, 1);
+ EXPECT_NEAR(occlusion_tracker.overdraw_metrics()->pixels_culled_for_drawing(),
+ 40000,
+ 1);
+}
+
+TEST_F(QuadCullerTest, VerifyCullSubRegion) {
+ DECLARE_AND_INITIALIZE_TEST_QUADS();
+
+ child_transform.Translate(50, 50);
+ scoped_ptr<TiledLayerImpl> root_layer = MakeLayer(NULL,
+ gfx::Transform(),
+ root_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ gfx::Rect child_opaque_rect(child_rect.x() + child_rect.width() / 4,
+ child_rect.y() + child_rect.height() / 4,
+ child_rect.width() / 2,
+ child_rect.height() / 2);
+ scoped_ptr<TiledLayerImpl> child_layer = MakeLayer(root_layer.get(),
+ child_transform,
+ child_rect,
+ 1,
+ false,
+ child_opaque_rect,
+ render_surface_layer_list);
+ TestOcclusionTrackerImpl occlusion_tracker(gfx::Rect(-100, -100, 1000, 1000));
+ LayerIteratorType it = LayerIteratorType::Begin(&render_surface_layer_list);
+
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ child_layer.get(),
+ &it,
+ &occlusion_tracker);
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ root_layer.get(),
+ &it,
+ &occlusion_tracker);
+ EXPECT_EQ(quad_list.size(), 12u);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_opaque(), 90000, 1);
+ EXPECT_NEAR(occlusion_tracker.overdraw_metrics()->pixels_drawn_translucent(),
+ 30000,
+ 1);
+ EXPECT_NEAR(occlusion_tracker.overdraw_metrics()->pixels_culled_for_drawing(),
+ 10000,
+ 1);
+}
+
+TEST_F(QuadCullerTest, VerifyCullSubRegion2) {
+ DECLARE_AND_INITIALIZE_TEST_QUADS();
+
+ child_transform.Translate(50, 10);
+ scoped_ptr<TiledLayerImpl> root_layer = MakeLayer(NULL,
+ gfx::Transform(),
+ root_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ gfx::Rect child_opaque_rect(child_rect.x() + child_rect.width() / 4,
+ child_rect.y() + child_rect.height() / 4,
+ child_rect.width() / 2,
+ child_rect.height() * 3 / 4);
+ scoped_ptr<TiledLayerImpl> child_layer = MakeLayer(root_layer.get(),
+ child_transform,
+ child_rect,
+ 1,
+ false,
+ child_opaque_rect,
+ render_surface_layer_list);
+ TestOcclusionTrackerImpl occlusion_tracker(gfx::Rect(-100, -100, 1000, 1000));
+ LayerIteratorType it = LayerIteratorType::Begin(&render_surface_layer_list);
+
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ child_layer.get(),
+ &it,
+ &occlusion_tracker);
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ root_layer.get(),
+ &it,
+ &occlusion_tracker);
+ EXPECT_EQ(quad_list.size(), 12u);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_opaque(), 90000, 1);
+ EXPECT_NEAR(occlusion_tracker.overdraw_metrics()->pixels_drawn_translucent(),
+ 25000,
+ 1);
+ EXPECT_NEAR(occlusion_tracker.overdraw_metrics()->pixels_culled_for_drawing(),
+ 15000,
+ 1);
+}
+
+TEST_F(QuadCullerTest, VerifyCullSubRegionCheckOvercull) {
+ DECLARE_AND_INITIALIZE_TEST_QUADS();
+
+ child_transform.Translate(50, 49);
+ scoped_ptr<TiledLayerImpl> root_layer = MakeLayer(NULL,
+ gfx::Transform(),
+ root_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ gfx::Rect child_opaque_rect(child_rect.x() + child_rect.width() / 4,
+ child_rect.y() + child_rect.height() / 4,
+ child_rect.width() / 2,
+ child_rect.height() / 2);
+ scoped_ptr<TiledLayerImpl> child_layer = MakeLayer(root_layer.get(),
+ child_transform,
+ child_rect,
+ 1,
+ false,
+ child_opaque_rect,
+ render_surface_layer_list);
+ TestOcclusionTrackerImpl occlusion_tracker(gfx::Rect(-100, -100, 1000, 1000));
+ LayerIteratorType it = LayerIteratorType::Begin(&render_surface_layer_list);
+
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ child_layer.get(),
+ &it,
+ &occlusion_tracker);
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ root_layer.get(),
+ &it,
+ &occlusion_tracker);
+ EXPECT_EQ(quad_list.size(), 13u);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_opaque(), 90000, 1);
+ EXPECT_NEAR(occlusion_tracker.overdraw_metrics()->pixels_drawn_translucent(),
+ 30000,
+ 1);
+ EXPECT_NEAR(occlusion_tracker.overdraw_metrics()->pixels_culled_for_drawing(),
+ 10000,
+ 1);
+}
+
+TEST_F(QuadCullerTest, VerifyNonAxisAlignedQuadsDontOcclude) {
+ DECLARE_AND_INITIALIZE_TEST_QUADS();
+
+ // Use a small rotation so as to not disturb the geometry significantly.
+ child_transform.Rotate(1);
+
+ scoped_ptr<TiledLayerImpl> root_layer = MakeLayer(NULL,
+ gfx::Transform(),
+ root_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ scoped_ptr<TiledLayerImpl> child_layer = MakeLayer(root_layer.get(),
+ child_transform,
+ child_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ TestOcclusionTrackerImpl occlusion_tracker(gfx::Rect(-100, -100, 1000, 1000));
+ LayerIteratorType it = LayerIteratorType::Begin(&render_surface_layer_list);
+
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ child_layer.get(),
+ &it,
+ &occlusion_tracker);
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ root_layer.get(),
+ &it,
+ &occlusion_tracker);
+ EXPECT_EQ(quad_list.size(), 13u);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_opaque(), 130000, 1);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_translucent(), 0, 1);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_culled_for_drawing(), 0, 1);
+}
+
+// This test requires some explanation: here we are rotating the quads to be
+// culled. The 2x2 tile child layer remains in the top-left corner, unrotated,
+// but the 3x3 tile parent layer is rotated by 1 degree. Of the four tiles the
+// child would normally occlude, three will move (slightly) out from under the
+// child layer, and one moves further under the child. Only this last tile
+// should be culled.
+TEST_F(QuadCullerTest, VerifyNonAxisAlignedQuadsSafelyCulled) {
+ DECLARE_AND_INITIALIZE_TEST_QUADS();
+
+ // Use a small rotation so as to not disturb the geometry significantly.
+ gfx::Transform parent_transform;
+ parent_transform.Rotate(1);
+
+ scoped_ptr<TiledLayerImpl> root_layer = MakeLayer(NULL,
+ parent_transform,
+ root_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ scoped_ptr<TiledLayerImpl> child_layer = MakeLayer(root_layer.get(),
+ gfx::Transform(),
+ child_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ TestOcclusionTrackerImpl occlusion_tracker(gfx::Rect(-100, -100, 1000, 1000));
+ LayerIteratorType it = LayerIteratorType::Begin(&render_surface_layer_list);
+
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ child_layer.get(),
+ &it,
+ &occlusion_tracker);
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ root_layer.get(),
+ &it,
+ &occlusion_tracker);
+ EXPECT_EQ(quad_list.size(), 12u);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_opaque(), 100600, 1);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_translucent(), 0, 1);
+ EXPECT_NEAR(occlusion_tracker.overdraw_metrics()->pixels_culled_for_drawing(),
+ 29400,
+ 1);
+}
+
+TEST_F(QuadCullerTest, VerifyCullOutsideScissorOverTile) {
+ DECLARE_AND_INITIALIZE_TEST_QUADS();
+ scoped_ptr<TiledLayerImpl> root_layer =
+ MakeLayer(NULL,
+ gfx::Transform(),
+ root_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ scoped_ptr<TiledLayerImpl> child_layer = MakeLayer(root_layer.get(),
+ gfx::Transform(),
+ child_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ TestOcclusionTrackerImpl occlusion_tracker(gfx::Rect(200, 100, 100, 100));
+ LayerIteratorType it = LayerIteratorType::Begin(&render_surface_layer_list);
+
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ child_layer.get(),
+ &it,
+ &occlusion_tracker);
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ root_layer.get(),
+ &it,
+ &occlusion_tracker);
+ EXPECT_EQ(quad_list.size(), 1u);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_opaque(), 10000, 1);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_translucent(), 0, 1);
+ EXPECT_NEAR(occlusion_tracker.overdraw_metrics()->pixels_culled_for_drawing(),
+ 120000,
+ 1);
+}
+
+TEST_F(QuadCullerTest, VerifyCullOutsideScissorOverCulledTile) {
+ DECLARE_AND_INITIALIZE_TEST_QUADS();
+ scoped_ptr<TiledLayerImpl> root_layer =
+ MakeLayer(NULL,
+ gfx::Transform(),
+ root_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ scoped_ptr<TiledLayerImpl> child_layer = MakeLayer(root_layer.get(),
+ gfx::Transform(),
+ child_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ TestOcclusionTrackerImpl occlusion_tracker(gfx::Rect(100, 100, 100, 100));
+ LayerIteratorType it = LayerIteratorType::Begin(&render_surface_layer_list);
+
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ child_layer.get(),
+ &it,
+ &occlusion_tracker);
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ root_layer.get(),
+ &it,
+ &occlusion_tracker);
+ EXPECT_EQ(quad_list.size(), 1u);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_opaque(), 10000, 1);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_translucent(), 0, 1);
+ EXPECT_NEAR(occlusion_tracker.overdraw_metrics()->pixels_culled_for_drawing(),
+ 120000,
+ 1);
+}
+
+TEST_F(QuadCullerTest, VerifyCullOutsideScissorOverPartialTiles) {
+ DECLARE_AND_INITIALIZE_TEST_QUADS();
+ scoped_ptr<TiledLayerImpl> root_layer =
+ MakeLayer(NULL,
+ gfx::Transform(),
+ root_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ scoped_ptr<TiledLayerImpl> child_layer = MakeLayer(root_layer.get(),
+ gfx::Transform(),
+ child_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ TestOcclusionTrackerImpl occlusion_tracker(gfx::Rect(50, 50, 200, 200));
+ LayerIteratorType it = LayerIteratorType::Begin(&render_surface_layer_list);
+
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ child_layer.get(),
+ &it,
+ &occlusion_tracker);
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ root_layer.get(),
+ &it,
+ &occlusion_tracker);
+ EXPECT_EQ(quad_list.size(), 9u);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_opaque(), 40000, 1);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_translucent(), 0, 1);
+ EXPECT_NEAR(occlusion_tracker.overdraw_metrics()->pixels_culled_for_drawing(),
+ 90000,
+ 1);
+}
+
+TEST_F(QuadCullerTest, VerifyCullOutsideScissorOverNoTiles) {
+ DECLARE_AND_INITIALIZE_TEST_QUADS();
+ scoped_ptr<TiledLayerImpl> root_layer =
+ MakeLayer(NULL,
+ gfx::Transform(),
+ root_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ scoped_ptr<TiledLayerImpl> child_layer = MakeLayer(root_layer.get(),
+ gfx::Transform(),
+ child_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ TestOcclusionTrackerImpl occlusion_tracker(gfx::Rect(500, 500, 100, 100));
+ LayerIteratorType it = LayerIteratorType::Begin(&render_surface_layer_list);
+
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ child_layer.get(),
+ &it,
+ &occlusion_tracker);
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ root_layer.get(),
+ &it,
+ &occlusion_tracker);
+ EXPECT_EQ(quad_list.size(), 0u);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_opaque(), 0, 1);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_translucent(), 0, 1);
+ EXPECT_NEAR(occlusion_tracker.overdraw_metrics()->pixels_culled_for_drawing(),
+ 130000,
+ 1);
+}
+
+TEST_F(QuadCullerTest, VerifyWithoutMetrics) {
+ DECLARE_AND_INITIALIZE_TEST_QUADS();
+ scoped_ptr<TiledLayerImpl> root_layer =
+ MakeLayer(NULL,
+ gfx::Transform(),
+ root_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ scoped_ptr<TiledLayerImpl> child_layer = MakeLayer(root_layer.get(),
+ gfx::Transform(),
+ child_rect,
+ 1,
+ true,
+ gfx::Rect(),
+ render_surface_layer_list);
+ TestOcclusionTrackerImpl occlusion_tracker(gfx::Rect(50, 50, 200, 200),
+ false);
+ LayerIteratorType it = LayerIteratorType::Begin(&render_surface_layer_list);
+
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ child_layer.get(),
+ &it,
+ &occlusion_tracker);
+ AppendQuads(&quad_list,
+ &shared_state_list,
+ root_layer.get(),
+ &it,
+ &occlusion_tracker);
+ EXPECT_EQ(quad_list.size(), 9u);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_opaque(), 0, 1);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_drawn_translucent(), 0, 1);
+ EXPECT_NEAR(
+ occlusion_tracker.overdraw_metrics()->pixels_culled_for_drawing(), 0, 1);
+}
+
+} // namespace
+} // namespace cc
diff --git a/chromium/cc/trees/single_thread_proxy.cc b/chromium/cc/trees/single_thread_proxy.cc
new file mode 100644
index 00000000000..3a26a079ec6
--- /dev/null
+++ b/chromium/cc/trees/single_thread_proxy.cc
@@ -0,0 +1,498 @@
+// Copyright 2011 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 "cc/trees/single_thread_proxy.h"
+
+#include "base/auto_reset.h"
+#include "base/debug/trace_event.h"
+#include "cc/output/context_provider.h"
+#include "cc/output/output_surface.h"
+#include "cc/quads/draw_quad.h"
+#include "cc/resources/prioritized_resource_manager.h"
+#include "cc/resources/resource_update_controller.h"
+#include "cc/trees/layer_tree_host.h"
+#include "cc/trees/layer_tree_impl.h"
+
+namespace cc {
+
+scoped_ptr<Proxy> SingleThreadProxy::Create(LayerTreeHost* layer_tree_host) {
+ return make_scoped_ptr(
+ new SingleThreadProxy(layer_tree_host)).PassAs<Proxy>();
+}
+
+SingleThreadProxy::SingleThreadProxy(LayerTreeHost* layer_tree_host)
+ : Proxy(NULL),
+ layer_tree_host_(layer_tree_host),
+ created_offscreen_context_provider_(false),
+ next_frame_is_newly_committed_frame_(false),
+ inside_draw_(false) {
+ TRACE_EVENT0("cc", "SingleThreadProxy::SingleThreadProxy");
+ DCHECK(Proxy::IsMainThread());
+ DCHECK(layer_tree_host);
+
+ // Impl-side painting not supported without threaded compositing.
+ CHECK(!layer_tree_host->settings().impl_side_painting)
+ << "Threaded compositing must be enabled to use impl-side painting.";
+}
+
+void SingleThreadProxy::Start(scoped_ptr<OutputSurface> first_output_surface) {
+ DCHECK(first_output_surface);
+ DebugScopedSetImplThread impl(this);
+ layer_tree_host_impl_ = layer_tree_host_->CreateLayerTreeHostImpl(this);
+ first_output_surface_ = first_output_surface.Pass();
+}
+
+SingleThreadProxy::~SingleThreadProxy() {
+ TRACE_EVENT0("cc", "SingleThreadProxy::~SingleThreadProxy");
+ DCHECK(Proxy::IsMainThread());
+ // Make sure Stop() got called or never Started.
+ DCHECK(!layer_tree_host_impl_);
+}
+
+bool SingleThreadProxy::CompositeAndReadback(void* pixels, gfx::Rect rect) {
+ TRACE_EVENT0("cc", "SingleThreadProxy::CompositeAndReadback");
+ DCHECK(Proxy::IsMainThread());
+
+ gfx::Rect device_viewport_damage_rect = rect;
+
+ LayerTreeHostImpl::FrameData frame;
+ if (!CommitAndComposite(base::TimeTicks::Now(),
+ device_viewport_damage_rect,
+ true, // for_readback
+ &frame))
+ return false;
+
+ {
+ DebugScopedSetImplThread impl(this);
+ layer_tree_host_impl_->Readback(pixels, rect);
+
+ if (layer_tree_host_impl_->IsContextLost())
+ return false;
+
+ layer_tree_host_impl_->SwapBuffers(frame);
+ }
+ DidSwapFrame();
+
+ return true;
+}
+
+void SingleThreadProxy::FinishAllRendering() {
+ DCHECK(Proxy::IsMainThread());
+ {
+ DebugScopedSetImplThread impl(this);
+ layer_tree_host_impl_->FinishAllRendering();
+ }
+}
+
+bool SingleThreadProxy::IsStarted() const {
+ DCHECK(Proxy::IsMainThread());
+ return layer_tree_host_impl_;
+}
+
+void SingleThreadProxy::SetLayerTreeHostClientReady() {
+ // Scheduling is controlled by the embedder in the single thread case, so
+ // nothing to do.
+}
+
+void SingleThreadProxy::SetVisible(bool visible) {
+ DebugScopedSetImplThread impl(this);
+ layer_tree_host_impl_->SetVisible(visible);
+
+ // Changing visibility could change ShouldComposite().
+ layer_tree_host_impl_->UpdateBackgroundAnimateTicking(!ShouldComposite());
+}
+
+void SingleThreadProxy::CreateAndInitializeOutputSurface() {
+ TRACE_EVENT0(
+ "cc", "SingleThreadProxy::CreateAndInitializeOutputSurface");
+ DCHECK(Proxy::IsMainThread());
+
+ scoped_ptr<OutputSurface> output_surface = first_output_surface_.Pass();
+ if (!output_surface)
+ output_surface = layer_tree_host_->CreateOutputSurface();
+ if (!output_surface) {
+ OnOutputSurfaceInitializeAttempted(false);
+ return;
+ }
+
+ scoped_refptr<cc::ContextProvider> offscreen_context_provider;
+ if (created_offscreen_context_provider_) {
+ offscreen_context_provider =
+ layer_tree_host_->client()->OffscreenContextProviderForMainThread();
+ if (!offscreen_context_provider.get()) {
+ OnOutputSurfaceInitializeAttempted(false);
+ return;
+ }
+ }
+
+ {
+ DebugScopedSetMainThreadBlocked mainThreadBlocked(this);
+ DebugScopedSetImplThread impl(this);
+ layer_tree_host_->DeleteContentsTexturesOnImplThread(
+ layer_tree_host_impl_->resource_provider());
+ }
+
+ bool initialized;
+ {
+ DebugScopedSetImplThread impl(this);
+
+ DCHECK(output_surface);
+ initialized = layer_tree_host_impl_->InitializeRenderer(
+ output_surface.Pass());
+ if (initialized) {
+ renderer_capabilities_for_main_thread_ =
+ layer_tree_host_impl_->GetRendererCapabilities();
+
+ layer_tree_host_impl_->resource_provider()->
+ set_offscreen_context_provider(offscreen_context_provider);
+ } else if (offscreen_context_provider.get()) {
+ offscreen_context_provider->VerifyContexts();
+ }
+ }
+
+ OnOutputSurfaceInitializeAttempted(initialized);
+}
+
+void SingleThreadProxy::OnOutputSurfaceInitializeAttempted(bool success) {
+ LayerTreeHost::CreateResult result =
+ layer_tree_host_->OnCreateAndInitializeOutputSurfaceAttempted(success);
+ if (result == LayerTreeHost::CreateFailedButTryAgain) {
+ // Force another recreation attempt to happen by requesting another commit.
+ SetNeedsCommit();
+ }
+}
+
+const RendererCapabilities& SingleThreadProxy::GetRendererCapabilities() const {
+ DCHECK(Proxy::IsMainThread());
+ DCHECK(!layer_tree_host_->output_surface_lost());
+ return renderer_capabilities_for_main_thread_;
+}
+
+void SingleThreadProxy::SetNeedsAnimate() {
+ // Thread-only feature.
+ NOTREACHED();
+}
+
+void SingleThreadProxy::SetNeedsUpdateLayers() {
+ DCHECK(Proxy::IsMainThread());
+ layer_tree_host_->ScheduleComposite();
+}
+
+void SingleThreadProxy::DoCommit(scoped_ptr<ResourceUpdateQueue> queue) {
+ DCHECK(Proxy::IsMainThread());
+ // Commit immediately.
+ {
+ DebugScopedSetMainThreadBlocked mainThreadBlocked(this);
+ DebugScopedSetImplThread impl(this);
+
+ RenderingStatsInstrumentation* stats_instrumentation =
+ layer_tree_host_->rendering_stats_instrumentation();
+ base::TimeTicks start_time = stats_instrumentation->StartRecording();
+
+ layer_tree_host_impl_->BeginCommit();
+
+ if (layer_tree_host_->contents_texture_manager()) {
+ layer_tree_host_->contents_texture_manager()->
+ PushTexturePrioritiesToBackings();
+ }
+ layer_tree_host_->BeginCommitOnImplThread(layer_tree_host_impl_.get());
+
+ scoped_ptr<ResourceUpdateController> update_controller =
+ ResourceUpdateController::Create(
+ NULL,
+ Proxy::MainThreadTaskRunner(),
+ queue.Pass(),
+ layer_tree_host_impl_->resource_provider());
+ update_controller->Finalize();
+
+ layer_tree_host_->FinishCommitOnImplThread(layer_tree_host_impl_.get());
+
+ layer_tree_host_impl_->CommitComplete();
+
+#ifndef NDEBUG
+ // In the single-threaded case, the scale and scroll deltas should never be
+ // touched on the impl layer tree.
+ scoped_ptr<ScrollAndScaleSet> scroll_info =
+ layer_tree_host_impl_->ProcessScrollDeltas();
+ DCHECK(!scroll_info->scrolls.size());
+ DCHECK_EQ(1.f, scroll_info->page_scale_delta);
+#endif
+
+ base::TimeDelta duration = stats_instrumentation->EndRecording(start_time);
+ stats_instrumentation->AddCommit(duration);
+ }
+ layer_tree_host_->CommitComplete();
+ next_frame_is_newly_committed_frame_ = true;
+}
+
+void SingleThreadProxy::SetNeedsCommit() {
+ DCHECK(Proxy::IsMainThread());
+ layer_tree_host_->ScheduleComposite();
+}
+
+void SingleThreadProxy::SetNeedsRedraw(gfx::Rect damage_rect) {
+ SetNeedsRedrawRectOnImplThread(damage_rect);
+}
+
+void SingleThreadProxy::OnHasPendingTreeStateChanged(bool have_pending_tree) {
+ // Thread-only feature.
+ NOTREACHED();
+}
+
+void SingleThreadProxy::SetDeferCommits(bool defer_commits) {
+ // Thread-only feature.
+ NOTREACHED();
+}
+
+bool SingleThreadProxy::CommitRequested() const { return false; }
+
+size_t SingleThreadProxy::MaxPartialTextureUpdates() const {
+ return std::numeric_limits<size_t>::max();
+}
+
+void SingleThreadProxy::Stop() {
+ TRACE_EVENT0("cc", "SingleThreadProxy::stop");
+ DCHECK(Proxy::IsMainThread());
+ {
+ DebugScopedSetMainThreadBlocked mainThreadBlocked(this);
+ DebugScopedSetImplThread impl(this);
+
+ layer_tree_host_->DeleteContentsTexturesOnImplThread(
+ layer_tree_host_impl_->resource_provider());
+ layer_tree_host_impl_.reset();
+ }
+ layer_tree_host_ = NULL;
+}
+
+void SingleThreadProxy::OnCanDrawStateChanged(bool can_draw) {
+ DCHECK(Proxy::IsImplThread());
+ layer_tree_host_impl_->UpdateBackgroundAnimateTicking(!ShouldComposite());
+}
+
+void SingleThreadProxy::SetNeedsRedrawOnImplThread() {
+ layer_tree_host_->ScheduleComposite();
+}
+
+void SingleThreadProxy::SetNeedsRedrawRectOnImplThread(gfx::Rect damage_rect) {
+ // TODO(brianderson): Once we move render_widget scheduling into this class,
+ // we can treat redraw requests more efficiently than CommitAndRedraw
+ // requests.
+ layer_tree_host_impl_->SetViewportDamage(damage_rect);
+ SetNeedsCommit();
+}
+
+void SingleThreadProxy::DidInitializeVisibleTileOnImplThread() {
+ // Impl-side painting only.
+ NOTREACHED();
+}
+
+void SingleThreadProxy::SetNeedsCommitOnImplThread() {
+ layer_tree_host_->ScheduleComposite();
+}
+
+void SingleThreadProxy::PostAnimationEventsToMainThreadOnImplThread(
+ scoped_ptr<AnimationEventsVector> events,
+ base::Time wall_clock_time) {
+ DCHECK(Proxy::IsImplThread());
+ DebugScopedSetMainThread main(this);
+ layer_tree_host_->SetAnimationEvents(events.Pass(), wall_clock_time);
+}
+
+bool SingleThreadProxy::ReduceContentsTextureMemoryOnImplThread(
+ size_t limit_bytes,
+ int priority_cutoff) {
+ DCHECK(IsImplThread());
+ if (!layer_tree_host_->contents_texture_manager())
+ return false;
+
+ return layer_tree_host_->contents_texture_manager()->ReduceMemoryOnImplThread(
+ limit_bytes, priority_cutoff, layer_tree_host_impl_->resource_provider());
+}
+
+void SingleThreadProxy::ReduceWastedContentsTextureMemoryOnImplThread() {
+ // Impl-side painting only.
+ NOTREACHED();
+}
+
+void SingleThreadProxy::SendManagedMemoryStats() {
+ DCHECK(Proxy::IsImplThread());
+ if (!layer_tree_host_impl_)
+ return;
+ if (!layer_tree_host_->contents_texture_manager())
+ return;
+
+ PrioritizedResourceManager* contents_texture_manager =
+ layer_tree_host_->contents_texture_manager();
+ layer_tree_host_impl_->SendManagedMemoryStats(
+ contents_texture_manager->MemoryVisibleBytes(),
+ contents_texture_manager->MemoryVisibleAndNearbyBytes(),
+ contents_texture_manager->MemoryUseBytes());
+}
+
+bool SingleThreadProxy::IsInsideDraw() { return inside_draw_; }
+
+void SingleThreadProxy::DidTryInitializeRendererOnImplThread(
+ bool success,
+ scoped_refptr<ContextProvider> offscreen_context_provider) {
+ NOTREACHED()
+ << "This is only used on threaded compositing with impl-side painting";
+}
+
+void SingleThreadProxy::DidLoseOutputSurfaceOnImplThread() {
+ // Cause a commit so we can notice the lost context.
+ SetNeedsCommitOnImplThread();
+}
+
+// Called by the legacy scheduling path (e.g. where render_widget does the
+// scheduling)
+void SingleThreadProxy::CompositeImmediately(base::TimeTicks frame_begin_time) {
+ gfx::Rect device_viewport_damage_rect;
+
+ LayerTreeHostImpl::FrameData frame;
+ if (CommitAndComposite(frame_begin_time,
+ device_viewport_damage_rect,
+ false, // for_readback
+ &frame)) {
+ layer_tree_host_impl_->SwapBuffers(frame);
+ DidSwapFrame();
+ }
+}
+
+scoped_ptr<base::Value> SingleThreadProxy::AsValue() const {
+ scoped_ptr<base::DictionaryValue> state(new base::DictionaryValue());
+ {
+ // The following line casts away const modifiers because it is just
+ // setting debug state. We still want the AsValue() function and its
+ // call chain to be const throughout.
+ DebugScopedSetImplThread impl(const_cast<SingleThreadProxy*>(this));
+
+ state->Set("layer_tree_host_impl",
+ layer_tree_host_impl_->AsValue().release());
+ }
+ return state.PassAs<base::Value>();
+}
+
+void SingleThreadProxy::ForceSerializeOnSwapBuffers() {
+ {
+ DebugScopedSetImplThread impl(this);
+ if (layer_tree_host_impl_->renderer()) {
+ DCHECK(!layer_tree_host_->output_surface_lost());
+ layer_tree_host_impl_->renderer()->DoNoOp();
+ }
+ }
+}
+
+bool SingleThreadProxy::CommitAndComposite(
+ base::TimeTicks frame_begin_time,
+ gfx::Rect device_viewport_damage_rect,
+ bool for_readback,
+ LayerTreeHostImpl::FrameData* frame) {
+ DCHECK(Proxy::IsMainThread());
+
+ if (!layer_tree_host_->InitializeOutputSurfaceIfNeeded())
+ return false;
+
+ layer_tree_host_->AnimateLayers(frame_begin_time);
+
+ scoped_refptr<cc::ContextProvider> offscreen_context_provider;
+ if (renderer_capabilities_for_main_thread_.using_offscreen_context3d &&
+ layer_tree_host_->needs_offscreen_context()) {
+ offscreen_context_provider =
+ layer_tree_host_->client()->OffscreenContextProviderForMainThread();
+ if (offscreen_context_provider.get())
+ created_offscreen_context_provider_ = true;
+ }
+
+ if (layer_tree_host_->contents_texture_manager()) {
+ layer_tree_host_->contents_texture_manager()
+ ->UnlinkAndClearEvictedBackings();
+ }
+
+ scoped_ptr<ResourceUpdateQueue> queue =
+ make_scoped_ptr(new ResourceUpdateQueue);
+ layer_tree_host_->UpdateLayers(
+ queue.get(), layer_tree_host_impl_->memory_allocation_limit_bytes());
+
+ layer_tree_host_->WillCommit();
+ DoCommit(queue.Pass());
+ bool result = DoComposite(offscreen_context_provider,
+ frame_begin_time,
+ device_viewport_damage_rect,
+ for_readback,
+ frame);
+ layer_tree_host_->DidBeginFrame();
+ return result;
+}
+
+bool SingleThreadProxy::ShouldComposite() const {
+ DCHECK(Proxy::IsImplThread());
+ return layer_tree_host_impl_->visible() &&
+ layer_tree_host_impl_->CanDraw();
+}
+
+bool SingleThreadProxy::DoComposite(
+ scoped_refptr<cc::ContextProvider> offscreen_context_provider,
+ base::TimeTicks frame_begin_time,
+ gfx::Rect device_viewport_damage_rect,
+ bool for_readback,
+ LayerTreeHostImpl::FrameData* frame) {
+ DCHECK(!layer_tree_host_->output_surface_lost());
+
+ bool lost_output_surface = false;
+ {
+ DebugScopedSetImplThread impl(this);
+ base::AutoReset<bool> mark_inside(&inside_draw_, true);
+
+ layer_tree_host_impl_->resource_provider()->
+ set_offscreen_context_provider(offscreen_context_provider);
+
+ bool can_do_readback = layer_tree_host_impl_->renderer()->CanReadPixels();
+
+ // We guard PrepareToDraw() with CanDraw() because it always returns a valid
+ // frame, so can only be used when such a frame is possible. Since
+ // DrawLayers() depends on the result of PrepareToDraw(), it is guarded on
+ // CanDraw() as well.
+ if (!ShouldComposite() || (for_readback && !can_do_readback)) {
+ layer_tree_host_impl_->UpdateBackgroundAnimateTicking(true);
+ return false;
+ }
+
+ layer_tree_host_impl_->Animate(
+ layer_tree_host_impl_->CurrentFrameTimeTicks(),
+ layer_tree_host_impl_->CurrentFrameTime());
+ layer_tree_host_impl_->UpdateBackgroundAnimateTicking(false);
+
+ layer_tree_host_impl_->PrepareToDraw(frame, device_viewport_damage_rect);
+ layer_tree_host_impl_->DrawLayers(frame, frame_begin_time);
+ layer_tree_host_impl_->DidDrawAllLayers(*frame);
+ lost_output_surface = layer_tree_host_impl_->IsContextLost();
+
+ bool start_ready_animations = true;
+ layer_tree_host_impl_->UpdateAnimationState(start_ready_animations);
+
+ layer_tree_host_impl_->ResetCurrentFrameTimeForNextFrame();
+ }
+
+ if (lost_output_surface) {
+ cc::ContextProvider* offscreen_contexts = layer_tree_host_impl_->
+ resource_provider()->offscreen_context_provider();
+ if (offscreen_contexts)
+ offscreen_contexts->VerifyContexts();
+ layer_tree_host_->DidLoseOutputSurface();
+ return false;
+ }
+
+ return true;
+}
+
+void SingleThreadProxy::DidSwapFrame() {
+ if (next_frame_is_newly_committed_frame_) {
+ next_frame_is_newly_committed_frame_ = false;
+ layer_tree_host_->DidCommitAndDrawFrame();
+ }
+}
+
+bool SingleThreadProxy::CommitPendingForTesting() { return false; }
+
+} // namespace cc
diff --git a/chromium/cc/trees/single_thread_proxy.h b/chromium/cc/trees/single_thread_proxy.h
new file mode 100644
index 00000000000..8e9438ccbef
--- /dev/null
+++ b/chromium/cc/trees/single_thread_proxy.h
@@ -0,0 +1,183 @@
+// Copyright 2011 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.
+
+#ifndef CC_TREES_SINGLE_THREAD_PROXY_H_
+#define CC_TREES_SINGLE_THREAD_PROXY_H_
+
+#include <limits>
+
+#include "base/time/time.h"
+#include "cc/animation/animation_events.h"
+#include "cc/output/begin_frame_args.h"
+#include "cc/trees/layer_tree_host_impl.h"
+#include "cc/trees/proxy.h"
+
+namespace cc {
+
+class ContextProvider;
+class LayerTreeHost;
+
+class SingleThreadProxy : public Proxy, LayerTreeHostImplClient {
+ public:
+ static scoped_ptr<Proxy> Create(LayerTreeHost* layer_tree_host);
+ virtual ~SingleThreadProxy();
+
+ // Proxy implementation
+ virtual bool CompositeAndReadback(void* pixels, gfx::Rect rect) OVERRIDE;
+ virtual void FinishAllRendering() OVERRIDE;
+ virtual bool IsStarted() const OVERRIDE;
+ virtual void SetLayerTreeHostClientReady() OVERRIDE;
+ virtual void SetVisible(bool visible) OVERRIDE;
+ virtual void CreateAndInitializeOutputSurface() OVERRIDE;
+ virtual const RendererCapabilities& GetRendererCapabilities() const OVERRIDE;
+ virtual void SetNeedsAnimate() OVERRIDE;
+ virtual void SetNeedsUpdateLayers() OVERRIDE;
+ virtual void SetNeedsCommit() OVERRIDE;
+ virtual void SetNeedsRedraw(gfx::Rect damage_rect) OVERRIDE;
+ virtual void NotifyInputThrottledUntilCommit() OVERRIDE {}
+ virtual void SetDeferCommits(bool defer_commits) OVERRIDE;
+ virtual bool CommitRequested() const OVERRIDE;
+ virtual void MainThreadHasStoppedFlinging() OVERRIDE {}
+ virtual void Start(scoped_ptr<OutputSurface> first_output_surface) OVERRIDE;
+ virtual void Stop() OVERRIDE;
+ virtual size_t MaxPartialTextureUpdates() const OVERRIDE;
+ virtual void AcquireLayerTextures() OVERRIDE {}
+ virtual void ForceSerializeOnSwapBuffers() OVERRIDE;
+ virtual scoped_ptr<base::Value> AsValue() const OVERRIDE;
+ virtual bool CommitPendingForTesting() OVERRIDE;
+
+ // LayerTreeHostImplClient implementation
+ virtual void DidTryInitializeRendererOnImplThread(
+ bool success,
+ scoped_refptr<ContextProvider> offscreen_context_provider) OVERRIDE;
+ virtual void DidLoseOutputSurfaceOnImplThread() OVERRIDE;
+ virtual void OnSwapBuffersCompleteOnImplThread() OVERRIDE {}
+ virtual void BeginFrameOnImplThread(const BeginFrameArgs& args)
+ OVERRIDE {}
+ virtual void OnCanDrawStateChanged(bool can_draw) OVERRIDE;
+ virtual void OnHasPendingTreeStateChanged(bool have_pending_tree) OVERRIDE;
+ virtual void SetNeedsRedrawOnImplThread() OVERRIDE;
+ virtual void SetNeedsRedrawRectOnImplThread(gfx::Rect dirty_rect) OVERRIDE;
+ virtual void DidInitializeVisibleTileOnImplThread() OVERRIDE;
+ virtual void SetNeedsCommitOnImplThread() OVERRIDE;
+ virtual void PostAnimationEventsToMainThreadOnImplThread(
+ scoped_ptr<AnimationEventsVector> events,
+ base::Time wall_clock_time) OVERRIDE;
+ virtual bool ReduceContentsTextureMemoryOnImplThread(
+ size_t limit_bytes,
+ int priority_cutoff) OVERRIDE;
+ virtual void ReduceWastedContentsTextureMemoryOnImplThread() OVERRIDE;
+ virtual void SendManagedMemoryStats() OVERRIDE;
+ virtual bool IsInsideDraw() OVERRIDE;
+ virtual void RenewTreePriority() OVERRIDE {}
+ virtual void RequestScrollbarAnimationOnImplThread(base::TimeDelta delay)
+ OVERRIDE {}
+ virtual void DidActivatePendingTree() OVERRIDE {}
+
+ // Called by the legacy path where RenderWidget does the scheduling.
+ void CompositeImmediately(base::TimeTicks frame_begin_time);
+
+ private:
+ explicit SingleThreadProxy(LayerTreeHost* layer_tree_host);
+
+ void OnOutputSurfaceInitializeAttempted(bool success);
+ bool CommitAndComposite(base::TimeTicks frame_begin_time,
+ gfx::Rect device_viewport_damage_rect,
+ bool for_readback,
+ LayerTreeHostImpl::FrameData* frame);
+ void DoCommit(scoped_ptr<ResourceUpdateQueue> queue);
+ bool DoComposite(
+ scoped_refptr<cc::ContextProvider> offscreen_context_provider,
+ base::TimeTicks frame_begin_time,
+ gfx::Rect device_viewport_damage_rect,
+ bool for_readback,
+ LayerTreeHostImpl::FrameData* frame);
+ void DidSwapFrame();
+
+ bool ShouldComposite() const;
+
+ // Accessed on main thread only.
+ LayerTreeHost* layer_tree_host_;
+ bool created_offscreen_context_provider_;
+
+ // Holds the first output surface passed from Start. Should not be used for
+ // anything else.
+ scoped_ptr<OutputSurface> first_output_surface_;
+
+ // Used on the Thread, but checked on main thread during
+ // initialization/shutdown.
+ scoped_ptr<LayerTreeHostImpl> layer_tree_host_impl_;
+ RendererCapabilities renderer_capabilities_for_main_thread_;
+
+ bool next_frame_is_newly_committed_frame_;
+
+ bool inside_draw_;
+
+ DISALLOW_COPY_AND_ASSIGN(SingleThreadProxy);
+};
+
+// For use in the single-threaded case. In debug builds, it pretends that the
+// code is running on the impl thread to satisfy assertion checks.
+class DebugScopedSetImplThread {
+ public:
+ explicit DebugScopedSetImplThread(Proxy* proxy) : proxy_(proxy) {
+#ifndef NDEBUG
+ previous_value_ = proxy_->impl_thread_is_overridden_;
+ proxy_->SetCurrentThreadIsImplThread(true);
+#endif
+ }
+ ~DebugScopedSetImplThread() {
+#ifndef NDEBUG
+ proxy_->SetCurrentThreadIsImplThread(previous_value_);
+#endif
+ }
+
+ private:
+ bool previous_value_;
+ Proxy* proxy_;
+
+ DISALLOW_COPY_AND_ASSIGN(DebugScopedSetImplThread);
+};
+
+// For use in the single-threaded case. In debug builds, it pretends that the
+// code is running on the main thread to satisfy assertion checks.
+class DebugScopedSetMainThread {
+ public:
+ explicit DebugScopedSetMainThread(Proxy* proxy) : proxy_(proxy) {
+#ifndef NDEBUG
+ previous_value_ = proxy_->impl_thread_is_overridden_;
+ proxy_->SetCurrentThreadIsImplThread(false);
+#endif
+ }
+ ~DebugScopedSetMainThread() {
+#ifndef NDEBUG
+ proxy_->SetCurrentThreadIsImplThread(previous_value_);
+#endif
+ }
+
+ private:
+ bool previous_value_;
+ Proxy* proxy_;
+
+ DISALLOW_COPY_AND_ASSIGN(DebugScopedSetMainThread);
+};
+
+// For use in the single-threaded case. In debug builds, it pretends that the
+// code is running on the impl thread and that the main thread is blocked to
+// satisfy assertion checks
+class DebugScopedSetImplThreadAndMainThreadBlocked {
+ public:
+ explicit DebugScopedSetImplThreadAndMainThreadBlocked(Proxy* proxy)
+ : impl_thread_(proxy), main_thread_blocked_(proxy) {}
+
+ private:
+ DebugScopedSetImplThread impl_thread_;
+ DebugScopedSetMainThreadBlocked main_thread_blocked_;
+
+ DISALLOW_COPY_AND_ASSIGN(DebugScopedSetImplThreadAndMainThreadBlocked);
+};
+
+} // namespace cc
+
+#endif // CC_TREES_SINGLE_THREAD_PROXY_H_
diff --git a/chromium/cc/trees/thread_proxy.cc b/chromium/cc/trees/thread_proxy.cc
new file mode 100644
index 00000000000..a0bd4bf5a7e
--- /dev/null
+++ b/chromium/cc/trees/thread_proxy.cc
@@ -0,0 +1,1519 @@
+// Copyright 2011 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 "cc/trees/thread_proxy.h"
+
+#include <string>
+
+#include "base/auto_reset.h"
+#include "base/bind.h"
+#include "base/debug/trace_event.h"
+#include "base/metrics/histogram.h"
+#include "cc/input/input_handler.h"
+#include "cc/output/context_provider.h"
+#include "cc/output/output_surface.h"
+#include "cc/quads/draw_quad.h"
+#include "cc/resources/prioritized_resource_manager.h"
+#include "cc/scheduler/delay_based_time_source.h"
+#include "cc/scheduler/frame_rate_controller.h"
+#include "cc/scheduler/scheduler.h"
+#include "cc/trees/layer_tree_host.h"
+#include "cc/trees/layer_tree_impl.h"
+
+namespace {
+
+// Measured in seconds.
+const double kContextRecreationTickRate = 0.03;
+
+// Measured in seconds.
+const double kSmoothnessTakesPriorityExpirationDelay = 0.25;
+
+const size_t kDurationHistorySize = 60;
+const double kCommitAndActivationDurationEstimationPercentile = 50.0;
+const double kDrawDurationEstimationPercentile = 100.0;
+const int kDrawDurationEstimatePaddingInMicroseconds = 0;
+
+} // namespace
+
+namespace cc {
+
+struct ThreadProxy::ReadbackRequest {
+ CompletionEvent completion;
+ bool success;
+ void* pixels;
+ gfx::Rect rect;
+};
+
+struct ThreadProxy::CommitPendingRequest {
+ CompletionEvent completion;
+ bool commit_pending;
+};
+
+struct ThreadProxy::SchedulerStateRequest {
+ CompletionEvent completion;
+ std::string state;
+};
+
+scoped_ptr<Proxy> ThreadProxy::Create(
+ LayerTreeHost* layer_tree_host,
+ scoped_refptr<base::SingleThreadTaskRunner> impl_task_runner) {
+ return make_scoped_ptr(
+ new ThreadProxy(layer_tree_host, impl_task_runner)).PassAs<Proxy>();
+}
+
+ThreadProxy::ThreadProxy(
+ LayerTreeHost* layer_tree_host,
+ scoped_refptr<base::SingleThreadTaskRunner> impl_task_runner)
+ : Proxy(impl_task_runner),
+ animate_requested_(false),
+ commit_requested_(false),
+ commit_request_sent_to_impl_thread_(false),
+ created_offscreen_context_provider_(false),
+ layer_tree_host_(layer_tree_host),
+ started_(false),
+ textures_acquired_(true),
+ in_composite_and_readback_(false),
+ manage_tiles_pending_(false),
+ weak_factory_on_impl_thread_(this),
+ weak_factory_(this),
+ begin_frame_sent_to_main_thread_completion_event_on_impl_thread_(NULL),
+ readback_request_on_impl_thread_(NULL),
+ commit_completion_event_on_impl_thread_(NULL),
+ completion_event_for_commit_held_on_tree_activation_(NULL),
+ texture_acquisition_completion_event_on_impl_thread_(NULL),
+ next_frame_is_newly_committed_frame_on_impl_thread_(false),
+ throttle_frame_production_(
+ layer_tree_host->settings().throttle_frame_production),
+ begin_frame_scheduling_enabled_(
+ layer_tree_host->settings().begin_frame_scheduling_enabled),
+ using_synchronous_renderer_compositor_(
+ layer_tree_host->settings().using_synchronous_renderer_compositor),
+ inside_draw_(false),
+ can_cancel_commit_(true),
+ defer_commits_(false),
+ input_throttled_until_commit_(false),
+ renew_tree_priority_on_impl_thread_pending_(false),
+ draw_duration_history_(kDurationHistorySize),
+ begin_frame_to_commit_duration_history_(kDurationHistorySize),
+ commit_to_activate_duration_history_(kDurationHistorySize) {
+ TRACE_EVENT0("cc", "ThreadProxy::ThreadProxy");
+ DCHECK(IsMainThread());
+ DCHECK(layer_tree_host_);
+}
+
+ThreadProxy::~ThreadProxy() {
+ TRACE_EVENT0("cc", "ThreadProxy::~ThreadProxy");
+ DCHECK(IsMainThread());
+ DCHECK(!started_);
+}
+
+bool ThreadProxy::CompositeAndReadback(void* pixels, gfx::Rect rect) {
+ TRACE_EVENT0("cc", "ThreadProxy::CompositeAndReadback");
+ DCHECK(IsMainThread());
+ DCHECK(layer_tree_host_);
+
+ if (defer_commits_) {
+ TRACE_EVENT0("cc", "CompositeAndReadback_DeferCommit");
+ return false;
+ }
+
+ if (!layer_tree_host_->InitializeOutputSurfaceIfNeeded()) {
+ TRACE_EVENT0("cc", "CompositeAndReadback_EarlyOut_LR_Uninitialized");
+ return false;
+ }
+
+ // Perform a synchronous commit.
+ {
+ DebugScopedSetMainThreadBlocked main_thread_blocked(this);
+ CompletionEvent begin_frame_sent_to_main_thread_completion;
+ Proxy::ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::ForceCommitOnImplThread,
+ impl_thread_weak_ptr_,
+ &begin_frame_sent_to_main_thread_completion));
+ begin_frame_sent_to_main_thread_completion.Wait();
+ }
+
+ in_composite_and_readback_ = true;
+ BeginFrameOnMainThread(scoped_ptr<BeginFrameAndCommitState>());
+ in_composite_and_readback_ = false;
+
+ // Composite and readback requires a second commit to undo any changes
+ // that it made.
+ can_cancel_commit_ = false;
+
+ // Perform a synchronous readback.
+ ReadbackRequest request;
+ request.rect = rect;
+ request.pixels = pixels;
+ {
+ DebugScopedSetMainThreadBlocked main_thread_blocked(this);
+ Proxy::ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::RequestReadbackOnImplThread,
+ impl_thread_weak_ptr_,
+ &request));
+ request.completion.Wait();
+ }
+ return request.success;
+}
+
+void ThreadProxy::ForceCommitOnImplThread(CompletionEvent* completion) {
+ TRACE_EVENT0("cc", "ThreadProxy::ForceCommitOnImplThread");
+ DCHECK(IsImplThread());
+ DCHECK(!begin_frame_sent_to_main_thread_completion_event_on_impl_thread_);
+
+ scheduler_on_impl_thread_->SetNeedsForcedCommit();
+ if (scheduler_on_impl_thread_->CommitPending()) {
+ completion->Signal();
+ return;
+ }
+
+ begin_frame_sent_to_main_thread_completion_event_on_impl_thread_ = completion;
+}
+
+void ThreadProxy::RequestReadbackOnImplThread(ReadbackRequest* request) {
+ DCHECK(Proxy::IsImplThread());
+ DCHECK(!readback_request_on_impl_thread_);
+ if (!layer_tree_host_impl_) {
+ request->success = false;
+ request->completion.Signal();
+ return;
+ }
+
+ readback_request_on_impl_thread_ = request;
+ scheduler_on_impl_thread_->SetNeedsRedraw();
+ scheduler_on_impl_thread_->SetNeedsForcedRedraw();
+}
+
+void ThreadProxy::FinishAllRendering() {
+ DCHECK(Proxy::IsMainThread());
+ DCHECK(!defer_commits_);
+
+ // Make sure all GL drawing is finished on the impl thread.
+ DebugScopedSetMainThreadBlocked main_thread_blocked(this);
+ CompletionEvent completion;
+ Proxy::ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::FinishAllRenderingOnImplThread,
+ impl_thread_weak_ptr_,
+ &completion));
+ completion.Wait();
+}
+
+bool ThreadProxy::IsStarted() const {
+ DCHECK(Proxy::IsMainThread());
+ return started_;
+}
+
+void ThreadProxy::SetLayerTreeHostClientReady() {
+ TRACE_EVENT0("cc", "ThreadProxy::SetLayerTreeHostClientReady");
+ Proxy::ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::SetLayerTreeHostClientReadyOnImplThread,
+ impl_thread_weak_ptr_));
+}
+
+void ThreadProxy::SetLayerTreeHostClientReadyOnImplThread() {
+ TRACE_EVENT0("cc", "ThreadProxy::SetLayerTreeHostClientReadyOnImplThread");
+ scheduler_on_impl_thread_->SetCanStart();
+}
+
+void ThreadProxy::SetVisible(bool visible) {
+ TRACE_EVENT0("cc", "ThreadProxy::SetVisible");
+ DebugScopedSetMainThreadBlocked main_thread_blocked(this);
+ CompletionEvent completion;
+ Proxy::ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::SetVisibleOnImplThread,
+ impl_thread_weak_ptr_,
+ &completion,
+ visible));
+ completion.Wait();
+}
+
+void ThreadProxy::SetVisibleOnImplThread(CompletionEvent* completion,
+ bool visible) {
+ TRACE_EVENT0("cc", "ThreadProxy::SetVisibleOnImplThread");
+ layer_tree_host_impl_->SetVisible(visible);
+ scheduler_on_impl_thread_->SetVisible(visible);
+ layer_tree_host_impl_->UpdateBackgroundAnimateTicking(
+ !scheduler_on_impl_thread_->WillDrawIfNeeded());
+ completion->Signal();
+}
+
+void ThreadProxy::DoCreateAndInitializeOutputSurface() {
+ TRACE_EVENT0("cc", "ThreadProxy::DoCreateAndInitializeOutputSurface");
+ DCHECK(IsMainThread());
+
+ scoped_ptr<OutputSurface> output_surface = first_output_surface_.Pass();
+ if (!output_surface)
+ output_surface = layer_tree_host_->CreateOutputSurface();
+
+ RendererCapabilities capabilities;
+ bool success = !!output_surface;
+ if (!success) {
+ OnOutputSurfaceInitializeAttempted(false, capabilities);
+ return;
+ }
+
+ scoped_refptr<ContextProvider> offscreen_context_provider;
+ if (created_offscreen_context_provider_) {
+ offscreen_context_provider = layer_tree_host_->client()->
+ OffscreenContextProviderForCompositorThread();
+ success = !!offscreen_context_provider.get();
+ if (!success) {
+ OnOutputSurfaceInitializeAttempted(false, capabilities);
+ return;
+ }
+ }
+
+ success = false;
+ {
+ // Make a blocking call to InitializeOutputSurfaceOnImplThread. The results
+ // of that call are pushed into the success and capabilities local
+ // variables.
+ CompletionEvent completion;
+ DebugScopedSetMainThreadBlocked main_thread_blocked(this);
+
+ Proxy::ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::InitializeOutputSurfaceOnImplThread,
+ impl_thread_weak_ptr_,
+ &completion,
+ base::Passed(&output_surface),
+ offscreen_context_provider,
+ &success,
+ &capabilities));
+ completion.Wait();
+ }
+
+ OnOutputSurfaceInitializeAttempted(success, capabilities);
+}
+
+void ThreadProxy::OnOutputSurfaceInitializeAttempted(
+ bool success,
+ const RendererCapabilities& capabilities) {
+ DCHECK(IsMainThread());
+ DCHECK(layer_tree_host_);
+
+ if (success) {
+ renderer_capabilities_main_thread_copy_ = capabilities;
+ }
+
+ LayerTreeHost::CreateResult result =
+ layer_tree_host_->OnCreateAndInitializeOutputSurfaceAttempted(success);
+ if (result == LayerTreeHost::CreateFailedButTryAgain) {
+ if (!output_surface_creation_callback_.callback().is_null()) {
+ Proxy::MainThreadTaskRunner()->PostTask(
+ FROM_HERE, output_surface_creation_callback_.callback());
+ }
+ } else {
+ output_surface_creation_callback_.Cancel();
+ }
+}
+
+void ThreadProxy::SendCommitRequestToImplThreadIfNeeded() {
+ DCHECK(IsMainThread());
+ if (commit_request_sent_to_impl_thread_)
+ return;
+ commit_request_sent_to_impl_thread_ = true;
+ Proxy::ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::SetNeedsCommitOnImplThread,
+ impl_thread_weak_ptr_));
+}
+
+const RendererCapabilities& ThreadProxy::GetRendererCapabilities() const {
+ DCHECK(IsMainThread());
+ DCHECK(!layer_tree_host_->output_surface_lost());
+ return renderer_capabilities_main_thread_copy_;
+}
+
+void ThreadProxy::SetNeedsAnimate() {
+ DCHECK(IsMainThread());
+ if (animate_requested_)
+ return;
+
+ TRACE_EVENT0("cc", "ThreadProxy::SetNeedsAnimate");
+ animate_requested_ = true;
+ can_cancel_commit_ = false;
+ SendCommitRequestToImplThreadIfNeeded();
+}
+
+void ThreadProxy::SetNeedsUpdateLayers() {
+ DCHECK(IsMainThread());
+ SendCommitRequestToImplThreadIfNeeded();
+}
+
+void ThreadProxy::SetNeedsCommit() {
+ DCHECK(IsMainThread());
+ // Unconditionally set here to handle SetNeedsCommit calls during a commit.
+ can_cancel_commit_ = false;
+
+ if (commit_requested_)
+ return;
+ TRACE_EVENT0("cc", "ThreadProxy::SetNeedsCommit");
+ commit_requested_ = true;
+
+ SendCommitRequestToImplThreadIfNeeded();
+}
+
+void ThreadProxy::DidLoseOutputSurfaceOnImplThread() {
+ DCHECK(IsImplThread());
+ TRACE_EVENT0("cc", "ThreadProxy::DidLoseOutputSurfaceOnImplThread");
+ Proxy::ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::CheckOutputSurfaceStatusOnImplThread,
+ impl_thread_weak_ptr_));
+}
+
+void ThreadProxy::CheckOutputSurfaceStatusOnImplThread() {
+ DCHECK(IsImplThread());
+ TRACE_EVENT0("cc", "ThreadProxy::CheckOutputSurfaceStatusOnImplThread");
+ if (!layer_tree_host_impl_->IsContextLost())
+ return;
+ if (cc::ContextProvider* offscreen_contexts = layer_tree_host_impl_
+ ->resource_provider()->offscreen_context_provider())
+ offscreen_contexts->VerifyContexts();
+ scheduler_on_impl_thread_->DidLoseOutputSurface();
+}
+
+void ThreadProxy::OnSwapBuffersCompleteOnImplThread() {
+ DCHECK(IsImplThread());
+ TRACE_EVENT0("cc", "ThreadProxy::OnSwapBuffersCompleteOnImplThread");
+ Proxy::MainThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::DidCompleteSwapBuffers, main_thread_weak_ptr_));
+}
+
+void ThreadProxy::SetNeedsBeginFrameOnImplThread(bool enable) {
+ DCHECK(IsImplThread());
+ TRACE_EVENT1("cc", "ThreadProxy::SetNeedsBeginFrameOnImplThread",
+ "enable", enable);
+ layer_tree_host_impl_->SetNeedsBeginFrame(enable);
+}
+
+void ThreadProxy::BeginFrameOnImplThread(const BeginFrameArgs& args) {
+ DCHECK(IsImplThread());
+ TRACE_EVENT0("cc", "ThreadProxy::BeginFrameOnImplThread");
+ scheduler_on_impl_thread_->BeginFrame(args);
+}
+
+void ThreadProxy::OnCanDrawStateChanged(bool can_draw) {
+ DCHECK(IsImplThread());
+ TRACE_EVENT1(
+ "cc", "ThreadProxy::OnCanDrawStateChanged", "can_draw", can_draw);
+ scheduler_on_impl_thread_->SetCanDraw(can_draw);
+ layer_tree_host_impl_->UpdateBackgroundAnimateTicking(
+ !scheduler_on_impl_thread_->WillDrawIfNeeded());
+}
+
+void ThreadProxy::OnHasPendingTreeStateChanged(bool has_pending_tree) {
+ DCHECK(IsImplThread());
+ TRACE_EVENT1("cc", "ThreadProxy::OnHasPendingTreeStateChanged",
+ "has_pending_tree", has_pending_tree);
+ scheduler_on_impl_thread_->SetHasPendingTree(has_pending_tree);
+}
+
+void ThreadProxy::SetNeedsCommitOnImplThread() {
+ DCHECK(IsImplThread());
+ TRACE_EVENT0("cc", "ThreadProxy::SetNeedsCommitOnImplThread");
+ scheduler_on_impl_thread_->SetNeedsCommit();
+}
+
+void ThreadProxy::PostAnimationEventsToMainThreadOnImplThread(
+ scoped_ptr<AnimationEventsVector> events,
+ base::Time wall_clock_time) {
+ DCHECK(IsImplThread());
+ TRACE_EVENT0("cc",
+ "ThreadProxy::PostAnimationEventsToMainThreadOnImplThread");
+ Proxy::MainThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::SetAnimationEvents,
+ main_thread_weak_ptr_,
+ base::Passed(&events),
+ wall_clock_time));
+}
+
+bool ThreadProxy::ReduceContentsTextureMemoryOnImplThread(size_t limit_bytes,
+ int priority_cutoff) {
+ DCHECK(IsImplThread());
+
+ if (!layer_tree_host_->contents_texture_manager())
+ return false;
+
+ bool reduce_result = layer_tree_host_->contents_texture_manager()->
+ ReduceMemoryOnImplThread(limit_bytes,
+ priority_cutoff,
+ layer_tree_host_impl_->resource_provider());
+ if (!reduce_result)
+ return false;
+
+ // The texture upload queue may reference textures that were just purged,
+ // clear them from the queue.
+ if (current_resource_update_controller_on_impl_thread_) {
+ current_resource_update_controller_on_impl_thread_->
+ DiscardUploadsToEvictedResources();
+ }
+ return true;
+}
+
+void ThreadProxy::ReduceWastedContentsTextureMemoryOnImplThread() {
+ DCHECK(IsImplThread());
+
+ if (!layer_tree_host_->contents_texture_manager())
+ return;
+
+ layer_tree_host_->contents_texture_manager()->ReduceWastedMemoryOnImplThread(
+ layer_tree_host_impl_->resource_provider());
+}
+
+void ThreadProxy::SendManagedMemoryStats() {
+ DCHECK(IsImplThread());
+ if (!layer_tree_host_impl_)
+ return;
+ if (!layer_tree_host_->contents_texture_manager())
+ return;
+
+ // If we are using impl-side painting, then SendManagedMemoryStats is called
+ // directly after the tile manager's manage function, and doesn't need to
+ // interact with main thread's layer tree.
+ if (layer_tree_host_->settings().impl_side_painting)
+ return;
+
+ layer_tree_host_impl_->SendManagedMemoryStats(
+ layer_tree_host_->contents_texture_manager()->MemoryVisibleBytes(),
+ layer_tree_host_->contents_texture_manager()->
+ MemoryVisibleAndNearbyBytes(),
+ layer_tree_host_->contents_texture_manager()->MemoryUseBytes());
+}
+
+bool ThreadProxy::IsInsideDraw() { return inside_draw_; }
+
+void ThreadProxy::SetNeedsRedraw(gfx::Rect damage_rect) {
+ DCHECK(IsMainThread());
+ TRACE_EVENT0("cc", "ThreadProxy::SetNeedsRedraw");
+ Proxy::ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::SetNeedsRedrawRectOnImplThread,
+ impl_thread_weak_ptr_,
+ damage_rect));
+}
+
+void ThreadProxy::SetDeferCommits(bool defer_commits) {
+ DCHECK(IsMainThread());
+ DCHECK_NE(defer_commits_, defer_commits);
+ defer_commits_ = defer_commits;
+
+ if (defer_commits_)
+ TRACE_EVENT_ASYNC_BEGIN0("cc", "ThreadProxy::SetDeferCommits", this);
+ else
+ TRACE_EVENT_ASYNC_END0("cc", "ThreadProxy::SetDeferCommits", this);
+
+ if (!defer_commits_ && pending_deferred_commit_)
+ Proxy::MainThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::BeginFrameOnMainThread,
+ main_thread_weak_ptr_,
+ base::Passed(&pending_deferred_commit_)));
+}
+
+bool ThreadProxy::CommitRequested() const {
+ DCHECK(IsMainThread());
+ return commit_requested_;
+}
+
+void ThreadProxy::SetNeedsRedrawOnImplThread() {
+ DCHECK(IsImplThread());
+ TRACE_EVENT0("cc", "ThreadProxy::SetNeedsRedrawOnImplThread");
+ scheduler_on_impl_thread_->SetNeedsRedraw();
+}
+
+void ThreadProxy::SetNeedsRedrawRectOnImplThread(gfx::Rect damage_rect) {
+ DCHECK(IsImplThread());
+ layer_tree_host_impl_->SetViewportDamage(damage_rect);
+ SetNeedsRedrawOnImplThread();
+}
+
+void ThreadProxy::DidSwapUseIncompleteTileOnImplThread() {
+ DCHECK(IsImplThread());
+ TRACE_EVENT0("cc", "ThreadProxy::DidSwapUseIncompleteTileOnImplThread");
+ scheduler_on_impl_thread_->DidSwapUseIncompleteTile();
+}
+
+void ThreadProxy::DidInitializeVisibleTileOnImplThread() {
+ DCHECK(IsImplThread());
+ TRACE_EVENT0("cc", "ThreadProxy::DidInitializeVisibleTileOnImplThread");
+ scheduler_on_impl_thread_->SetNeedsRedraw();
+}
+
+void ThreadProxy::MainThreadHasStoppedFlinging() {
+ DCHECK(IsMainThread());
+ Proxy::ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::MainThreadHasStoppedFlingingOnImplThread,
+ impl_thread_weak_ptr_));
+}
+
+void ThreadProxy::MainThreadHasStoppedFlingingOnImplThread() {
+ DCHECK(IsImplThread());
+ layer_tree_host_impl_->MainThreadHasStoppedFlinging();
+}
+
+void ThreadProxy::NotifyInputThrottledUntilCommit() {
+ DCHECK(IsMainThread());
+ Proxy::ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::SetInputThrottledUntilCommitOnImplThread,
+ impl_thread_weak_ptr_,
+ true));
+}
+
+void ThreadProxy::SetInputThrottledUntilCommitOnImplThread(
+ bool is_throttled) {
+ DCHECK(IsImplThread());
+ if (is_throttled == input_throttled_until_commit_)
+ return;
+ input_throttled_until_commit_ = is_throttled;
+ RenewTreePriority();
+}
+
+void ThreadProxy::Start(scoped_ptr<OutputSurface> first_output_surface) {
+ DCHECK(IsMainThread());
+ DCHECK(Proxy::HasImplThread());
+ DCHECK(first_output_surface);
+ // Create LayerTreeHostImpl.
+ DebugScopedSetMainThreadBlocked main_thread_blocked(this);
+ CompletionEvent completion;
+ Proxy::ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::InitializeImplOnImplThread,
+ base::Unretained(this),
+ &completion));
+ completion.Wait();
+
+ main_thread_weak_ptr_ = weak_factory_.GetWeakPtr();
+ first_output_surface_ = first_output_surface.Pass();
+
+ started_ = true;
+}
+
+void ThreadProxy::Stop() {
+ TRACE_EVENT0("cc", "ThreadProxy::Stop");
+ DCHECK(IsMainThread());
+ DCHECK(started_);
+
+ // Synchronously finishes pending GL operations and deletes the impl.
+ // The two steps are done as separate post tasks, so that tasks posted
+ // by the GL implementation due to the Finish can be executed by the
+ // renderer before shutting it down.
+ {
+ DebugScopedSetMainThreadBlocked main_thread_blocked(this);
+
+ CompletionEvent completion;
+ Proxy::ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::FinishGLOnImplThread,
+ impl_thread_weak_ptr_,
+ &completion));
+ completion.Wait();
+ }
+ {
+ DebugScopedSetMainThreadBlocked main_thread_blocked(this);
+
+ CompletionEvent completion;
+ Proxy::ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::LayerTreeHostClosedOnImplThread,
+ impl_thread_weak_ptr_,
+ &completion));
+ completion.Wait();
+ }
+
+ weak_factory_.InvalidateWeakPtrs();
+
+ DCHECK(!layer_tree_host_impl_.get()); // verify that the impl deleted.
+ layer_tree_host_ = NULL;
+ started_ = false;
+}
+
+void ThreadProxy::ForceSerializeOnSwapBuffers() {
+ DebugScopedSetMainThreadBlocked main_thread_blocked(this);
+ CompletionEvent completion;
+ Proxy::ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::ForceSerializeOnSwapBuffersOnImplThread,
+ impl_thread_weak_ptr_,
+ &completion));
+ completion.Wait();
+}
+
+void ThreadProxy::ForceSerializeOnSwapBuffersOnImplThread(
+ CompletionEvent* completion) {
+ if (layer_tree_host_impl_->renderer())
+ layer_tree_host_impl_->renderer()->DoNoOp();
+ completion->Signal();
+}
+
+void ThreadProxy::FinishAllRenderingOnImplThread(CompletionEvent* completion) {
+ TRACE_EVENT0("cc", "ThreadProxy::FinishAllRenderingOnImplThread");
+ DCHECK(IsImplThread());
+ layer_tree_host_impl_->FinishAllRendering();
+ completion->Signal();
+}
+
+void ThreadProxy::ScheduledActionSendBeginFrameToMainThread() {
+ TRACE_EVENT0("cc", "ThreadProxy::ScheduledActionSendBeginFrameToMainThread");
+ scoped_ptr<BeginFrameAndCommitState> begin_frame_state(
+ new BeginFrameAndCommitState);
+ begin_frame_state->monotonic_frame_begin_time =
+ layer_tree_host_impl_->CurrentPhysicalTimeTicks();
+ begin_frame_state->scroll_info =
+ layer_tree_host_impl_->ProcessScrollDeltas();
+
+ if (!layer_tree_host_impl_->settings().impl_side_painting) {
+ DCHECK_GT(layer_tree_host_impl_->memory_allocation_limit_bytes(), 0u);
+ }
+ begin_frame_state->memory_allocation_limit_bytes =
+ layer_tree_host_impl_->memory_allocation_limit_bytes();
+ Proxy::MainThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::BeginFrameOnMainThread,
+ main_thread_weak_ptr_,
+ base::Passed(&begin_frame_state)));
+
+ if (begin_frame_sent_to_main_thread_completion_event_on_impl_thread_) {
+ begin_frame_sent_to_main_thread_completion_event_on_impl_thread_->Signal();
+ begin_frame_sent_to_main_thread_completion_event_on_impl_thread_ = NULL;
+ }
+ begin_frame_sent_to_main_thread_time_ = base::TimeTicks::HighResNow();
+}
+
+void ThreadProxy::BeginFrameOnMainThread(
+ scoped_ptr<BeginFrameAndCommitState> begin_frame_state) {
+ TRACE_EVENT0("cc", "ThreadProxy::BeginFrameOnMainThread");
+ DCHECK(IsMainThread());
+ if (!layer_tree_host_)
+ return;
+
+ if (defer_commits_) {
+ pending_deferred_commit_ = begin_frame_state.Pass();
+ layer_tree_host_->DidDeferCommit();
+ TRACE_EVENT0("cc", "EarlyOut_DeferCommits");
+ return;
+ }
+
+ // Do not notify the impl thread of commit requests that occur during
+ // the apply/animate/layout part of the BeginFrameAndCommit process since
+ // those commit requests will get painted immediately. Once we have done
+ // the paint, commit_requested_ will be set to false to allow new commit
+ // requests to be scheduled.
+ commit_requested_ = true;
+ commit_request_sent_to_impl_thread_ = true;
+
+ // On the other hand, the AnimationRequested flag needs to be cleared
+ // here so that any animation requests generated by the apply or animate
+ // callbacks will trigger another frame.
+ animate_requested_ = false;
+
+ if (!in_composite_and_readback_ && !layer_tree_host_->visible()) {
+ commit_requested_ = false;
+ commit_request_sent_to_impl_thread_ = false;
+
+ TRACE_EVENT0("cc", "EarlyOut_NotVisible");
+ bool did_handle = false;
+ Proxy::ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::BeginFrameAbortedByMainThreadOnImplThread,
+ impl_thread_weak_ptr_,
+ did_handle));
+ return;
+ }
+
+ if (begin_frame_state)
+ layer_tree_host_->ApplyScrollAndScale(*begin_frame_state->scroll_info);
+
+ layer_tree_host_->WillBeginFrame();
+
+ if (begin_frame_state) {
+ layer_tree_host_->UpdateClientAnimations(
+ begin_frame_state->monotonic_frame_begin_time);
+ layer_tree_host_->AnimateLayers(
+ begin_frame_state->monotonic_frame_begin_time);
+ }
+
+ // Unlink any backings that the impl thread has evicted, so that we know to
+ // re-paint them in UpdateLayers.
+ if (layer_tree_host_->contents_texture_manager()) {
+ layer_tree_host_->contents_texture_manager()->
+ UnlinkAndClearEvictedBackings();
+ }
+
+ layer_tree_host_->Layout();
+
+ // Clear the commit flag after updating animations and layout here --- objects
+ // that only layout when painted will trigger another SetNeedsCommit inside
+ // UpdateLayers.
+ commit_requested_ = false;
+ commit_request_sent_to_impl_thread_ = false;
+ bool can_cancel_this_commit =
+ can_cancel_commit_ && !in_composite_and_readback_;
+ can_cancel_commit_ = true;
+
+ scoped_ptr<ResourceUpdateQueue> queue =
+ make_scoped_ptr(new ResourceUpdateQueue);
+ bool updated = layer_tree_host_->UpdateLayers(
+ queue.get(),
+ begin_frame_state ? begin_frame_state->memory_allocation_limit_bytes
+ : 0u);
+
+ // Once single buffered layers are committed, they cannot be modified until
+ // they are drawn by the impl thread.
+ textures_acquired_ = false;
+
+ layer_tree_host_->WillCommit();
+
+ if (!updated && can_cancel_this_commit) {
+ TRACE_EVENT0("cc", "EarlyOut_NoUpdates");
+ bool did_handle = true;
+ Proxy::ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::BeginFrameAbortedByMainThreadOnImplThread,
+ impl_thread_weak_ptr_,
+ did_handle));
+
+ // Although the commit is internally aborted, this is because it has been
+ // detected to be a no-op. From the perspective of an embedder, this commit
+ // went through, and input should no longer be throttled, etc.
+ layer_tree_host_->CommitComplete();
+ layer_tree_host_->DidBeginFrame();
+ return;
+ }
+
+ // Before calling animate, we set animate_requested_ to false. If it is true
+ // now, it means SetNeedAnimate was called again, but during a state when
+ // commit_request_sent_to_impl_thread_ = true. We need to force that call to
+ // happen again now so that the commit request is sent to the impl thread.
+ if (animate_requested_) {
+ // Forces SetNeedsAnimate to consider posting a commit task.
+ animate_requested_ = false;
+ SetNeedsAnimate();
+ }
+
+ scoped_refptr<cc::ContextProvider> offscreen_context_provider;
+ if (renderer_capabilities_main_thread_copy_.using_offscreen_context3d &&
+ layer_tree_host_->needs_offscreen_context()) {
+ offscreen_context_provider = layer_tree_host_->client()->
+ OffscreenContextProviderForCompositorThread();
+ if (offscreen_context_provider.get())
+ created_offscreen_context_provider_ = true;
+ }
+
+ // Notify the impl thread that the main thread is ready to commit. This will
+ // begin the commit process, which is blocking from the main thread's
+ // point of view, but asynchronously performed on the impl thread,
+ // coordinated by the Scheduler.
+ {
+ TRACE_EVENT0("cc", "ThreadProxy::BeginFrameOnMainThread::commit");
+
+ DebugScopedSetMainThreadBlocked main_thread_blocked(this);
+
+ RenderingStatsInstrumentation* stats_instrumentation =
+ layer_tree_host_->rendering_stats_instrumentation();
+ base::TimeTicks start_time = stats_instrumentation->StartRecording();
+
+ CompletionEvent completion;
+ Proxy::ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::StartCommitOnImplThread,
+ impl_thread_weak_ptr_,
+ &completion,
+ queue.release(),
+ offscreen_context_provider));
+ completion.Wait();
+
+ base::TimeDelta duration = stats_instrumentation->EndRecording(start_time);
+ stats_instrumentation->AddCommit(duration);
+ }
+
+ layer_tree_host_->CommitComplete();
+ layer_tree_host_->DidBeginFrame();
+}
+
+void ThreadProxy::StartCommitOnImplThread(
+ CompletionEvent* completion,
+ ResourceUpdateQueue* raw_queue,
+ scoped_refptr<cc::ContextProvider> offscreen_context_provider) {
+ scoped_ptr<ResourceUpdateQueue> queue(raw_queue);
+
+ TRACE_EVENT0("cc", "ThreadProxy::StartCommitOnImplThread");
+ DCHECK(!commit_completion_event_on_impl_thread_);
+ DCHECK(IsImplThread() && IsMainThreadBlocked());
+ DCHECK(scheduler_on_impl_thread_);
+ DCHECK(scheduler_on_impl_thread_->CommitPending());
+
+ if (!layer_tree_host_impl_) {
+ TRACE_EVENT0("cc", "EarlyOut_NoLayerTree");
+ completion->Signal();
+ return;
+ }
+
+ if (offscreen_context_provider.get())
+ offscreen_context_provider->BindToCurrentThread();
+ layer_tree_host_impl_->resource_provider()->
+ set_offscreen_context_provider(offscreen_context_provider);
+
+ if (layer_tree_host_->contents_texture_manager()) {
+ if (layer_tree_host_->contents_texture_manager()->
+ LinkedEvictedBackingsExist()) {
+ // Clear any uploads we were making to textures linked to evicted
+ // resources
+ queue->ClearUploadsToEvictedResources();
+ // Some textures in the layer tree are invalid. Kick off another commit
+ // to fill them again.
+ SetNeedsCommitOnImplThread();
+ }
+
+ layer_tree_host_->contents_texture_manager()->
+ PushTexturePrioritiesToBackings();
+ }
+
+ commit_completion_event_on_impl_thread_ = completion;
+ current_resource_update_controller_on_impl_thread_ =
+ ResourceUpdateController::Create(
+ this,
+ Proxy::ImplThreadTaskRunner(),
+ queue.Pass(),
+ layer_tree_host_impl_->resource_provider());
+ current_resource_update_controller_on_impl_thread_->PerformMoreUpdates(
+ scheduler_on_impl_thread_->AnticipatedDrawTime());
+}
+
+void ThreadProxy::BeginFrameAbortedByMainThreadOnImplThread(bool did_handle) {
+ TRACE_EVENT0("cc", "ThreadProxy::BeginFrameAbortedByMainThreadOnImplThread");
+ DCHECK(IsImplThread());
+ DCHECK(scheduler_on_impl_thread_);
+ DCHECK(scheduler_on_impl_thread_->CommitPending());
+ DCHECK(!layer_tree_host_impl_->pending_tree());
+
+ // If the begin frame data was handled, then scroll and scale set was applied
+ // by the main thread, so the active tree needs to be updated as if these sent
+ // values were applied and committed.
+ if (did_handle) {
+ layer_tree_host_impl_->active_tree()->ApplySentScrollAndScaleDeltas();
+ layer_tree_host_impl_->active_tree()->ResetContentsTexturesPurged();
+ SetInputThrottledUntilCommitOnImplThread(false);
+ }
+ scheduler_on_impl_thread_->BeginFrameAbortedByMainThread(did_handle);
+}
+
+void ThreadProxy::ScheduledActionCommit() {
+ TRACE_EVENT0("cc", "ThreadProxy::ScheduledActionCommit");
+ DCHECK(IsImplThread());
+ DCHECK(commit_completion_event_on_impl_thread_);
+ DCHECK(current_resource_update_controller_on_impl_thread_);
+
+ // Complete all remaining texture updates.
+ current_resource_update_controller_on_impl_thread_->Finalize();
+ current_resource_update_controller_on_impl_thread_.reset();
+
+ layer_tree_host_impl_->BeginCommit();
+ layer_tree_host_->BeginCommitOnImplThread(layer_tree_host_impl_.get());
+ layer_tree_host_->FinishCommitOnImplThread(layer_tree_host_impl_.get());
+ layer_tree_host_impl_->CommitComplete();
+
+ SetInputThrottledUntilCommitOnImplThread(false);
+
+ layer_tree_host_impl_->UpdateBackgroundAnimateTicking(
+ !scheduler_on_impl_thread_->WillDrawIfNeeded());
+
+ next_frame_is_newly_committed_frame_on_impl_thread_ = true;
+
+ if (layer_tree_host_->settings().impl_side_painting &&
+ layer_tree_host_->BlocksPendingCommit() &&
+ layer_tree_host_impl_->pending_tree()) {
+ // For some layer types in impl-side painting, the commit is held until
+ // the pending tree is activated. It's also possible that the
+ // pending tree has already activated if there was no work to be done.
+ TRACE_EVENT_INSTANT0("cc", "HoldCommit", TRACE_EVENT_SCOPE_THREAD);
+ completion_event_for_commit_held_on_tree_activation_ =
+ commit_completion_event_on_impl_thread_;
+ commit_completion_event_on_impl_thread_ = NULL;
+ } else {
+ commit_completion_event_on_impl_thread_->Signal();
+ commit_completion_event_on_impl_thread_ = NULL;
+ }
+
+ commit_complete_time_ = base::TimeTicks::HighResNow();
+ begin_frame_to_commit_duration_history_.InsertSample(
+ commit_complete_time_ - begin_frame_sent_to_main_thread_time_);
+
+ // SetVisible kicks off the next scheduler action, so this must be last.
+ scheduler_on_impl_thread_->SetVisible(layer_tree_host_impl_->visible());
+}
+
+void ThreadProxy::ScheduledActionUpdateVisibleTiles() {
+ DCHECK(IsImplThread());
+ TRACE_EVENT0("cc", "ThreadProxy::ScheduledActionUpdateVisibleTiles");
+ layer_tree_host_impl_->UpdateVisibleTiles();
+}
+
+void ThreadProxy::ScheduledActionActivatePendingTreeIfNeeded() {
+ DCHECK(IsImplThread());
+ TRACE_EVENT0("cc", "ThreadProxy::ScheduledActionActivatePendingTreeIfNeeded");
+ layer_tree_host_impl_->ActivatePendingTreeIfNeeded();
+}
+
+void ThreadProxy::ScheduledActionBeginOutputSurfaceCreation() {
+ DCHECK(IsImplThread());
+ Proxy::MainThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::CreateAndInitializeOutputSurface,
+ main_thread_weak_ptr_));
+}
+
+ScheduledActionDrawAndSwapResult
+ThreadProxy::ScheduledActionDrawAndSwapInternal(bool forced_draw) {
+ TRACE_EVENT1(
+ "cc", "ThreadProxy::ScheduledActionDrawAndSwap", "forced", forced_draw);
+
+ ScheduledActionDrawAndSwapResult result;
+ result.did_draw = false;
+ result.did_swap = false;
+ DCHECK(IsImplThread());
+ DCHECK(layer_tree_host_impl_.get());
+ if (!layer_tree_host_impl_)
+ return result;
+
+ DCHECK(layer_tree_host_impl_->renderer());
+ if (!layer_tree_host_impl_->renderer())
+ return result;
+
+ base::TimeTicks monotonic_time =
+ layer_tree_host_impl_->CurrentFrameTimeTicks();
+ base::Time wall_clock_time = layer_tree_host_impl_->CurrentFrameTime();
+
+ // TODO(enne): This should probably happen post-animate.
+ if (layer_tree_host_impl_->pending_tree()) {
+ layer_tree_host_impl_->ActivatePendingTreeIfNeeded();
+ if (layer_tree_host_impl_->pending_tree())
+ layer_tree_host_impl_->pending_tree()->UpdateDrawProperties();
+ }
+ layer_tree_host_impl_->Animate(monotonic_time, wall_clock_time);
+ layer_tree_host_impl_->UpdateBackgroundAnimateTicking(false);
+
+ base::TimeTicks start_time = base::TimeTicks::HighResNow();
+ base::TimeDelta draw_duration_estimate = DrawDurationEstimate();
+ base::AutoReset<bool> mark_inside(&inside_draw_, true);
+
+ // This method is called on a forced draw, regardless of whether we are able
+ // to produce a frame, as the calling site on main thread is blocked until its
+ // request completes, and we signal completion here. If CanDraw() is false, we
+ // will indicate success=false to the caller, but we must still signal
+ // completion to avoid deadlock.
+
+ // We guard PrepareToDraw() with CanDraw() because it always returns a valid
+ // frame, so can only be used when such a frame is possible. Since
+ // DrawLayers() depends on the result of PrepareToDraw(), it is guarded on
+ // CanDraw() as well.
+
+ bool drawing_for_readback = !!readback_request_on_impl_thread_;
+ bool can_do_readback = layer_tree_host_impl_->renderer()->CanReadPixels();
+
+ LayerTreeHostImpl::FrameData frame;
+ bool draw_frame = false;
+ bool start_ready_animations = true;
+
+ if (layer_tree_host_impl_->CanDraw() &&
+ (!drawing_for_readback || can_do_readback)) {
+ // If it is for a readback, make sure we draw the portion being read back.
+ gfx::Rect readback_rect;
+ if (drawing_for_readback)
+ readback_rect = readback_request_on_impl_thread_->rect;
+
+ // Do not start animations if we skip drawing the frame to avoid
+ // checkerboarding.
+ if (layer_tree_host_impl_->PrepareToDraw(&frame, readback_rect) ||
+ forced_draw)
+ draw_frame = true;
+ else
+ start_ready_animations = false;
+ }
+
+ if (draw_frame) {
+ layer_tree_host_impl_->DrawLayers(
+ &frame,
+ scheduler_on_impl_thread_->LastBeginFrameOnImplThreadTime());
+ result.did_draw = true;
+ }
+ layer_tree_host_impl_->DidDrawAllLayers(frame);
+
+ layer_tree_host_impl_->UpdateAnimationState(start_ready_animations);
+
+ // Check for a pending CompositeAndReadback.
+ if (readback_request_on_impl_thread_) {
+ readback_request_on_impl_thread_->success = false;
+ if (draw_frame) {
+ layer_tree_host_impl_->Readback(readback_request_on_impl_thread_->pixels,
+ readback_request_on_impl_thread_->rect);
+ readback_request_on_impl_thread_->success =
+ !layer_tree_host_impl_->IsContextLost();
+ }
+ readback_request_on_impl_thread_->completion.Signal();
+ readback_request_on_impl_thread_ = NULL;
+ } else if (draw_frame) {
+ result.did_swap = layer_tree_host_impl_->SwapBuffers(frame);
+
+ if (frame.contains_incomplete_tile)
+ DidSwapUseIncompleteTileOnImplThread();
+ }
+
+ // Tell the main thread that the the newly-commited frame was drawn.
+ if (next_frame_is_newly_committed_frame_on_impl_thread_) {
+ next_frame_is_newly_committed_frame_on_impl_thread_ = false;
+ Proxy::MainThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::DidCommitAndDrawFrame, main_thread_weak_ptr_));
+ }
+
+ if (draw_frame) {
+ CheckOutputSurfaceStatusOnImplThread();
+
+ base::TimeDelta draw_duration = base::TimeTicks::HighResNow() - start_time;
+ draw_duration_history_.InsertSample(draw_duration);
+ base::TimeDelta draw_duration_overestimate;
+ base::TimeDelta draw_duration_underestimate;
+ if (draw_duration > draw_duration_estimate)
+ draw_duration_underestimate = draw_duration - draw_duration_estimate;
+ else
+ draw_duration_overestimate = draw_duration_estimate - draw_duration;
+ UMA_HISTOGRAM_CUSTOM_TIMES("Renderer.DrawDuration",
+ draw_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMilliseconds(100),
+ 50);
+ UMA_HISTOGRAM_CUSTOM_TIMES("Renderer.DrawDurationUnderestimate",
+ draw_duration_underestimate,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMilliseconds(100),
+ 50);
+ UMA_HISTOGRAM_CUSTOM_TIMES("Renderer.DrawDurationOverestimate",
+ draw_duration_overestimate,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMilliseconds(100),
+ 50);
+ }
+
+ // Update the tile state after drawing. This prevents manage tiles from
+ // being in the critical path for getting things on screen, but still
+ // makes sure that tile state is updated on a semi-regular basis.
+ if (layer_tree_host_impl_->settings().impl_side_painting)
+ layer_tree_host_impl_->ManageTiles();
+
+ return result;
+}
+
+void ThreadProxy::AcquireLayerTextures() {
+ // Called when the main thread needs to modify a layer texture that is used
+ // directly by the compositor.
+ // This method will block until the next compositor draw if there is a
+ // previously committed frame that is still undrawn. This is necessary to
+ // ensure that the main thread does not monopolize access to the textures.
+ DCHECK(IsMainThread());
+
+ if (textures_acquired_)
+ return;
+
+ TRACE_EVENT0("cc", "ThreadProxy::AcquireLayerTextures");
+ DebugScopedSetMainThreadBlocked main_thread_blocked(this);
+ CompletionEvent completion;
+ Proxy::ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::AcquireLayerTexturesForMainThreadOnImplThread,
+ impl_thread_weak_ptr_,
+ &completion));
+ // Block until it is safe to write to layer textures from the main thread.
+ completion.Wait();
+
+ textures_acquired_ = true;
+ can_cancel_commit_ = false;
+}
+
+void ThreadProxy::AcquireLayerTexturesForMainThreadOnImplThread(
+ CompletionEvent* completion) {
+ DCHECK(IsImplThread());
+ DCHECK(!texture_acquisition_completion_event_on_impl_thread_);
+
+ texture_acquisition_completion_event_on_impl_thread_ = completion;
+ scheduler_on_impl_thread_->SetMainThreadNeedsLayerTextures();
+}
+
+void ThreadProxy::ScheduledActionAcquireLayerTexturesForMainThread() {
+ DCHECK(texture_acquisition_completion_event_on_impl_thread_);
+ texture_acquisition_completion_event_on_impl_thread_->Signal();
+ texture_acquisition_completion_event_on_impl_thread_ = NULL;
+}
+
+ScheduledActionDrawAndSwapResult
+ThreadProxy::ScheduledActionDrawAndSwapIfPossible() {
+ return ScheduledActionDrawAndSwapInternal(false);
+}
+
+ScheduledActionDrawAndSwapResult
+ThreadProxy::ScheduledActionDrawAndSwapForced() {
+ return ScheduledActionDrawAndSwapInternal(true);
+}
+
+void ThreadProxy::DidAnticipatedDrawTimeChange(base::TimeTicks time) {
+ if (current_resource_update_controller_on_impl_thread_)
+ current_resource_update_controller_on_impl_thread_
+ ->PerformMoreUpdates(time);
+ layer_tree_host_impl_->ResetCurrentFrameTimeForNextFrame();
+}
+
+base::TimeDelta ThreadProxy::DrawDurationEstimate() {
+ base::TimeDelta historical_estimate =
+ draw_duration_history_.Percentile(kDrawDurationEstimationPercentile);
+ base::TimeDelta padding = base::TimeDelta::FromMicroseconds(
+ kDrawDurationEstimatePaddingInMicroseconds);
+ return historical_estimate + padding;
+}
+
+base::TimeDelta ThreadProxy::BeginFrameToCommitDurationEstimate() {
+ return begin_frame_to_commit_duration_history_.Percentile(
+ kCommitAndActivationDurationEstimationPercentile);
+}
+
+base::TimeDelta ThreadProxy::CommitToActivateDurationEstimate() {
+ return commit_to_activate_duration_history_.Percentile(
+ kCommitAndActivationDurationEstimationPercentile);
+}
+
+void ThreadProxy::ReadyToFinalizeTextureUpdates() {
+ DCHECK(IsImplThread());
+ scheduler_on_impl_thread_->FinishCommit();
+}
+
+void ThreadProxy::DidCommitAndDrawFrame() {
+ DCHECK(IsMainThread());
+ if (!layer_tree_host_)
+ return;
+ layer_tree_host_->DidCommitAndDrawFrame();
+}
+
+void ThreadProxy::DidCompleteSwapBuffers() {
+ DCHECK(IsMainThread());
+ if (!layer_tree_host_)
+ return;
+ layer_tree_host_->DidCompleteSwapBuffers();
+}
+
+void ThreadProxy::SetAnimationEvents(scoped_ptr<AnimationEventsVector> events,
+ base::Time wall_clock_time) {
+ TRACE_EVENT0("cc", "ThreadProxy::SetAnimationEvents");
+ DCHECK(IsMainThread());
+ if (!layer_tree_host_)
+ return;
+ layer_tree_host_->SetAnimationEvents(events.Pass(), wall_clock_time);
+}
+
+void ThreadProxy::CreateAndInitializeOutputSurface() {
+ TRACE_EVENT0("cc", "ThreadProxy::CreateAndInitializeOutputSurface");
+ DCHECK(IsMainThread());
+
+ // Check that output surface has not been recreated by CompositeAndReadback
+ // after this task is posted but before it is run.
+ bool has_initialized_output_surface_on_impl_thread = true;
+ {
+ CompletionEvent completion;
+ Proxy::ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::HasInitializedOutputSurfaceOnImplThread,
+ impl_thread_weak_ptr_,
+ &completion,
+ &has_initialized_output_surface_on_impl_thread));
+ completion.Wait();
+ }
+ if (has_initialized_output_surface_on_impl_thread)
+ return;
+
+ layer_tree_host_->DidLoseOutputSurface();
+ output_surface_creation_callback_.Reset(base::Bind(
+ &ThreadProxy::DoCreateAndInitializeOutputSurface,
+ base::Unretained(this)));
+ output_surface_creation_callback_.callback().Run();
+}
+
+void ThreadProxy::HasInitializedOutputSurfaceOnImplThread(
+ CompletionEvent* completion,
+ bool* has_initialized_output_surface) {
+ DCHECK(IsImplThread());
+ *has_initialized_output_surface =
+ scheduler_on_impl_thread_->HasInitializedOutputSurface();
+ completion->Signal();
+}
+
+void ThreadProxy::InitializeImplOnImplThread(CompletionEvent* completion) {
+ TRACE_EVENT0("cc", "ThreadProxy::InitializeImplOnImplThread");
+ DCHECK(IsImplThread());
+ layer_tree_host_impl_ = layer_tree_host_->CreateLayerTreeHostImpl(this);
+ const LayerTreeSettings& settings = layer_tree_host_->settings();
+ SchedulerSettings scheduler_settings;
+ scheduler_settings.impl_side_painting = settings.impl_side_painting;
+ scheduler_settings.timeout_and_draw_when_animation_checkerboards =
+ settings.timeout_and_draw_when_animation_checkerboards;
+ scheduler_settings.using_synchronous_renderer_compositor =
+ settings.using_synchronous_renderer_compositor;
+ scheduler_settings.throttle_frame_production =
+ settings.throttle_frame_production;
+ scheduler_on_impl_thread_ = Scheduler::Create(this, scheduler_settings);
+ scheduler_on_impl_thread_->SetVisible(layer_tree_host_impl_->visible());
+
+ impl_thread_weak_ptr_ = weak_factory_on_impl_thread_.GetWeakPtr();
+ completion->Signal();
+}
+
+void ThreadProxy::InitializeOutputSurfaceOnImplThread(
+ CompletionEvent* completion,
+ scoped_ptr<OutputSurface> output_surface,
+ scoped_refptr<ContextProvider> offscreen_context_provider,
+ bool* success,
+ RendererCapabilities* capabilities) {
+ TRACE_EVENT0("cc", "ThreadProxy::InitializeOutputSurfaceOnImplThread");
+ DCHECK(IsImplThread());
+ DCHECK(IsMainThreadBlocked());
+ DCHECK(success);
+ DCHECK(capabilities);
+
+ layer_tree_host_->DeleteContentsTexturesOnImplThread(
+ layer_tree_host_impl_->resource_provider());
+
+ *success = layer_tree_host_impl_->InitializeRenderer(output_surface.Pass());
+
+ if (*success) {
+ *capabilities = layer_tree_host_impl_->GetRendererCapabilities();
+ scheduler_on_impl_thread_->DidCreateAndInitializeOutputSurface();
+ }
+
+ DidTryInitializeRendererOnImplThread(*success, offscreen_context_provider);
+
+ completion->Signal();
+}
+
+void ThreadProxy::DidTryInitializeRendererOnImplThread(
+ bool success,
+ scoped_refptr<ContextProvider> offscreen_context_provider) {
+ DCHECK(IsImplThread());
+ DCHECK(!inside_draw_);
+
+ if (offscreen_context_provider.get())
+ offscreen_context_provider->BindToCurrentThread();
+
+ if (success) {
+ layer_tree_host_impl_->resource_provider()->
+ set_offscreen_context_provider(offscreen_context_provider);
+ } else if (offscreen_context_provider.get()) {
+ offscreen_context_provider->VerifyContexts();
+ }
+}
+
+void ThreadProxy::FinishGLOnImplThread(CompletionEvent* completion) {
+ TRACE_EVENT0("cc", "ThreadProxy::FinishGLOnImplThread");
+ DCHECK(IsImplThread());
+ if (layer_tree_host_impl_->resource_provider())
+ layer_tree_host_impl_->resource_provider()->Finish();
+ completion->Signal();
+}
+
+void ThreadProxy::LayerTreeHostClosedOnImplThread(CompletionEvent* completion) {
+ TRACE_EVENT0("cc", "ThreadProxy::LayerTreeHostClosedOnImplThread");
+ DCHECK(IsImplThread());
+ layer_tree_host_->DeleteContentsTexturesOnImplThread(
+ layer_tree_host_impl_->resource_provider());
+ layer_tree_host_impl_->SetNeedsBeginFrame(false);
+ scheduler_on_impl_thread_.reset();
+ layer_tree_host_impl_.reset();
+ weak_factory_on_impl_thread_.InvalidateWeakPtrs();
+ completion->Signal();
+}
+
+size_t ThreadProxy::MaxPartialTextureUpdates() const {
+ return ResourceUpdateController::MaxPartialTextureUpdates();
+}
+
+ThreadProxy::BeginFrameAndCommitState::BeginFrameAndCommitState()
+ : memory_allocation_limit_bytes(0) {}
+
+ThreadProxy::BeginFrameAndCommitState::~BeginFrameAndCommitState() {}
+
+scoped_ptr<base::Value> ThreadProxy::AsValue() const {
+ scoped_ptr<base::DictionaryValue> state(new base::DictionaryValue());
+
+ CompletionEvent completion;
+ {
+ DebugScopedSetMainThreadBlocked main_thread_blocked(
+ const_cast<ThreadProxy*>(this));
+ Proxy::ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::AsValueOnImplThread,
+ impl_thread_weak_ptr_,
+ &completion,
+ state.get()));
+ completion.Wait();
+ }
+ return state.PassAs<base::Value>();
+}
+
+void ThreadProxy::AsValueOnImplThread(CompletionEvent* completion,
+ base::DictionaryValue* state) const {
+ state->Set("layer_tree_host_impl",
+ layer_tree_host_impl_->AsValue().release());
+ completion->Signal();
+}
+
+bool ThreadProxy::CommitPendingForTesting() {
+ DCHECK(IsMainThread());
+ CommitPendingRequest commit_pending_request;
+ {
+ DebugScopedSetMainThreadBlocked main_thread_blocked(this);
+ Proxy::ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::CommitPendingOnImplThreadForTesting,
+ impl_thread_weak_ptr_,
+ &commit_pending_request));
+ commit_pending_request.completion.Wait();
+ }
+ return commit_pending_request.commit_pending;
+}
+
+void ThreadProxy::CommitPendingOnImplThreadForTesting(
+ CommitPendingRequest* request) {
+ DCHECK(IsImplThread());
+ if (layer_tree_host_impl_->output_surface())
+ request->commit_pending = scheduler_on_impl_thread_->CommitPending();
+ else
+ request->commit_pending = false;
+ request->completion.Signal();
+}
+
+std::string ThreadProxy::SchedulerStateAsStringForTesting() {
+ if (IsImplThread())
+ return scheduler_on_impl_thread_->StateAsStringForTesting();
+
+ SchedulerStateRequest scheduler_state_request;
+ {
+ DebugScopedSetMainThreadBlocked main_thread_blocked(this);
+ Proxy::ImplThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::SchedulerStateAsStringOnImplThreadForTesting,
+ impl_thread_weak_ptr_,
+ &scheduler_state_request));
+ scheduler_state_request.completion.Wait();
+ }
+ return scheduler_state_request.state;
+}
+
+void ThreadProxy::SchedulerStateAsStringOnImplThreadForTesting(
+ SchedulerStateRequest* request) {
+ DCHECK(IsImplThread());
+ request->state = scheduler_on_impl_thread_->StateAsStringForTesting();
+ request->completion.Signal();
+}
+
+void ThreadProxy::RenewTreePriority() {
+ DCHECK(IsImplThread());
+ bool smoothness_takes_priority =
+ layer_tree_host_impl_->pinch_gesture_active() ||
+ layer_tree_host_impl_->CurrentlyScrollingLayer() ||
+ layer_tree_host_impl_->page_scale_animation_active();
+
+ base::TimeTicks now = layer_tree_host_impl_->CurrentPhysicalTimeTicks();
+
+ // Update expiration time if smoothness currently takes priority.
+ if (smoothness_takes_priority) {
+ smoothness_takes_priority_expiration_time_ =
+ now + base::TimeDelta::FromMilliseconds(
+ kSmoothnessTakesPriorityExpirationDelay * 1000);
+ }
+
+ // We use the same priority for both trees by default.
+ TreePriority priority = SAME_PRIORITY_FOR_BOTH_TREES;
+
+ // Smoothness takes priority if expiration time is in the future.
+ if (smoothness_takes_priority_expiration_time_ > now)
+ priority = SMOOTHNESS_TAKES_PRIORITY;
+
+ // New content always takes priority when the active tree has
+ // evicted resources or there is an invalid viewport size.
+ if (layer_tree_host_impl_->active_tree()->ContentsTexturesPurged() ||
+ layer_tree_host_impl_->active_tree()->ViewportSizeInvalid() ||
+ input_throttled_until_commit_)
+ priority = NEW_CONTENT_TAKES_PRIORITY;
+
+ layer_tree_host_impl_->SetTreePriority(priority);
+
+ // Notify the the client of this compositor via the output surface.
+ // TODO(epenner): Route this to compositor-thread instead of output-surface
+ // after GTFO refactor of compositor-thread (http://crbug/170828).
+ if (layer_tree_host_impl_->output_surface()) {
+ layer_tree_host_impl_->output_surface()->
+ UpdateSmoothnessTakesPriority(priority == SMOOTHNESS_TAKES_PRIORITY);
+ }
+
+ base::TimeDelta delay = smoothness_takes_priority_expiration_time_ - now;
+
+ // Need to make sure a delayed task is posted when we have smoothness
+ // takes priority expiration time in the future.
+ if (delay <= base::TimeDelta())
+ return;
+ if (renew_tree_priority_on_impl_thread_pending_)
+ return;
+
+ Proxy::ImplThreadTaskRunner()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::RenewTreePriorityOnImplThread,
+ weak_factory_on_impl_thread_.GetWeakPtr()),
+ delay);
+
+ renew_tree_priority_on_impl_thread_pending_ = true;
+}
+
+void ThreadProxy::RenewTreePriorityOnImplThread() {
+ DCHECK(renew_tree_priority_on_impl_thread_pending_);
+ renew_tree_priority_on_impl_thread_pending_ = false;
+
+ RenewTreePriority();
+}
+
+void ThreadProxy::RequestScrollbarAnimationOnImplThread(base::TimeDelta delay) {
+ Proxy::ImplThreadTaskRunner()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&ThreadProxy::StartScrollbarAnimationOnImplThread,
+ impl_thread_weak_ptr_),
+ delay);
+}
+
+void ThreadProxy::StartScrollbarAnimationOnImplThread() {
+ layer_tree_host_impl_->StartScrollbarAnimation();
+}
+
+void ThreadProxy::DidActivatePendingTree() {
+ DCHECK(IsImplThread());
+ TRACE_EVENT0("cc", "ThreadProxy::DidActivatePendingTreeOnImplThread");
+
+ if (completion_event_for_commit_held_on_tree_activation_ &&
+ !layer_tree_host_impl_->pending_tree()) {
+ TRACE_EVENT_INSTANT0("cc", "ReleaseCommitbyActivation",
+ TRACE_EVENT_SCOPE_THREAD);
+ DCHECK(layer_tree_host_impl_->settings().impl_side_painting);
+ completion_event_for_commit_held_on_tree_activation_->Signal();
+ completion_event_for_commit_held_on_tree_activation_ = NULL;
+ }
+
+ commit_to_activate_duration_history_.InsertSample(
+ base::TimeTicks::HighResNow() - commit_complete_time_);
+}
+
+} // namespace cc
diff --git a/chromium/cc/trees/thread_proxy.h b/chromium/cc/trees/thread_proxy.h
new file mode 100644
index 00000000000..1f5f29eec6c
--- /dev/null
+++ b/chromium/cc/trees/thread_proxy.h
@@ -0,0 +1,272 @@
+// Copyright 2011 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.
+
+#ifndef CC_TREES_THREAD_PROXY_H_
+#define CC_TREES_THREAD_PROXY_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "cc/animation/animation_events.h"
+#include "cc/base/completion_event.h"
+#include "cc/resources/resource_update_controller.h"
+#include "cc/scheduler/rolling_time_delta_history.h"
+#include "cc/scheduler/scheduler.h"
+#include "cc/trees/layer_tree_host_impl.h"
+#include "cc/trees/proxy.h"
+
+namespace base { class SingleThreadTaskRunner; }
+
+namespace cc {
+
+class ContextProvider;
+class InputHandlerClient;
+class LayerTreeHost;
+class ResourceUpdateQueue;
+class Scheduler;
+class ScopedThreadProxy;
+
+class ThreadProxy : public Proxy,
+ LayerTreeHostImplClient,
+ SchedulerClient,
+ ResourceUpdateControllerClient {
+ public:
+ static scoped_ptr<Proxy> Create(
+ LayerTreeHost* layer_tree_host,
+ scoped_refptr<base::SingleThreadTaskRunner> impl_task_runner);
+
+ virtual ~ThreadProxy();
+
+ // Proxy implementation
+ virtual bool CompositeAndReadback(void* pixels, gfx::Rect rect) OVERRIDE;
+ virtual void FinishAllRendering() OVERRIDE;
+ virtual bool IsStarted() const OVERRIDE;
+ virtual void SetLayerTreeHostClientReady() OVERRIDE;
+ virtual void SetVisible(bool visible) OVERRIDE;
+ virtual void CreateAndInitializeOutputSurface() OVERRIDE;
+ virtual const RendererCapabilities& GetRendererCapabilities() const OVERRIDE;
+ virtual void SetNeedsAnimate() OVERRIDE;
+ virtual void SetNeedsUpdateLayers() OVERRIDE;
+ virtual void SetNeedsCommit() OVERRIDE;
+ virtual void SetNeedsRedraw(gfx::Rect damage_rect) OVERRIDE;
+ virtual void NotifyInputThrottledUntilCommit() OVERRIDE;
+ virtual void SetDeferCommits(bool defer_commits) OVERRIDE;
+ virtual bool CommitRequested() const OVERRIDE;
+ virtual void MainThreadHasStoppedFlinging() OVERRIDE;
+ virtual void Start(scoped_ptr<OutputSurface> first_output_surface) OVERRIDE;
+ virtual void Stop() OVERRIDE;
+ virtual size_t MaxPartialTextureUpdates() const OVERRIDE;
+ virtual void AcquireLayerTextures() OVERRIDE;
+ virtual void ForceSerializeOnSwapBuffers() OVERRIDE;
+ virtual scoped_ptr<base::Value> AsValue() const OVERRIDE;
+ virtual bool CommitPendingForTesting() OVERRIDE;
+ virtual std::string SchedulerStateAsStringForTesting() OVERRIDE;
+
+ // LayerTreeHostImplClient implementation
+ virtual void DidTryInitializeRendererOnImplThread(
+ bool success,
+ scoped_refptr<ContextProvider> offscreen_context_provider) OVERRIDE;
+ virtual void DidLoseOutputSurfaceOnImplThread() OVERRIDE;
+ virtual void OnSwapBuffersCompleteOnImplThread() OVERRIDE;
+ virtual void BeginFrameOnImplThread(const BeginFrameArgs& args) OVERRIDE;
+ virtual void OnCanDrawStateChanged(bool can_draw) OVERRIDE;
+ virtual void OnHasPendingTreeStateChanged(bool has_pending_tree) OVERRIDE;
+ virtual void SetNeedsRedrawOnImplThread() OVERRIDE;
+ virtual void SetNeedsRedrawRectOnImplThread(gfx::Rect dirty_rect) OVERRIDE;
+ virtual void DidInitializeVisibleTileOnImplThread() OVERRIDE;
+ virtual void SetNeedsCommitOnImplThread() OVERRIDE;
+ virtual void PostAnimationEventsToMainThreadOnImplThread(
+ scoped_ptr<AnimationEventsVector> queue,
+ base::Time wall_clock_time) OVERRIDE;
+ virtual bool ReduceContentsTextureMemoryOnImplThread(size_t limit_bytes,
+ int priority_cutoff)
+ OVERRIDE;
+ virtual void ReduceWastedContentsTextureMemoryOnImplThread() OVERRIDE;
+ virtual void SendManagedMemoryStats() OVERRIDE;
+ virtual bool IsInsideDraw() OVERRIDE;
+ virtual void RenewTreePriority() OVERRIDE;
+ virtual void RequestScrollbarAnimationOnImplThread(base::TimeDelta delay)
+ OVERRIDE;
+ virtual void DidActivatePendingTree() OVERRIDE;
+
+ // SchedulerClient implementation
+ virtual void SetNeedsBeginFrameOnImplThread(bool enable) OVERRIDE;
+ virtual void ScheduledActionSendBeginFrameToMainThread() OVERRIDE;
+ virtual ScheduledActionDrawAndSwapResult
+ ScheduledActionDrawAndSwapIfPossible() OVERRIDE;
+ virtual ScheduledActionDrawAndSwapResult ScheduledActionDrawAndSwapForced()
+ OVERRIDE;
+ virtual void ScheduledActionCommit() OVERRIDE;
+ virtual void ScheduledActionUpdateVisibleTiles() OVERRIDE;
+ virtual void ScheduledActionActivatePendingTreeIfNeeded() OVERRIDE;
+ virtual void ScheduledActionBeginOutputSurfaceCreation() OVERRIDE;
+ virtual void ScheduledActionAcquireLayerTexturesForMainThread() OVERRIDE;
+ virtual void DidAnticipatedDrawTimeChange(base::TimeTicks time) OVERRIDE;
+ virtual base::TimeDelta DrawDurationEstimate() OVERRIDE;
+ virtual base::TimeDelta BeginFrameToCommitDurationEstimate() OVERRIDE;
+ virtual base::TimeDelta CommitToActivateDurationEstimate() OVERRIDE;
+
+ // ResourceUpdateControllerClient implementation
+ virtual void ReadyToFinalizeTextureUpdates() OVERRIDE;
+
+ private:
+ ThreadProxy(LayerTreeHost* layer_tree_host,
+ scoped_refptr<base::SingleThreadTaskRunner> impl_task_runner);
+
+ struct BeginFrameAndCommitState {
+ BeginFrameAndCommitState();
+ ~BeginFrameAndCommitState();
+
+ base::TimeTicks monotonic_frame_begin_time;
+ scoped_ptr<ScrollAndScaleSet> scroll_info;
+ size_t memory_allocation_limit_bytes;
+ };
+
+ // Called on main thread.
+ void BeginFrameOnMainThread(
+ scoped_ptr<BeginFrameAndCommitState> begin_frame_state);
+ void DidCommitAndDrawFrame();
+ void DidCompleteSwapBuffers();
+ void SetAnimationEvents(scoped_ptr<AnimationEventsVector> queue,
+ base::Time wall_clock_time);
+ void DoCreateAndInitializeOutputSurface();
+ // |capabilities| is set only when |success| is true.
+ void OnOutputSurfaceInitializeAttempted(
+ bool success,
+ const RendererCapabilities& capabilities);
+ void SendCommitRequestToImplThreadIfNeeded();
+
+ // Called on impl thread.
+ struct ReadbackRequest;
+ struct CommitPendingRequest;
+ struct SchedulerStateRequest;
+
+ void ForceCommitOnImplThread(CompletionEvent* completion);
+ void StartCommitOnImplThread(
+ CompletionEvent* completion,
+ ResourceUpdateQueue* queue,
+ scoped_refptr<cc::ContextProvider> offscreen_context_provider);
+ void BeginFrameAbortedByMainThreadOnImplThread(bool did_handle);
+ void RequestReadbackOnImplThread(ReadbackRequest* request);
+ void FinishAllRenderingOnImplThread(CompletionEvent* completion);
+ void InitializeImplOnImplThread(CompletionEvent* completion);
+ void SetLayerTreeHostClientReadyOnImplThread();
+ void SetVisibleOnImplThread(CompletionEvent* completion, bool visible);
+ void HasInitializedOutputSurfaceOnImplThread(
+ CompletionEvent* completion,
+ bool* has_initialized_output_surface);
+ void InitializeOutputSurfaceOnImplThread(
+ CompletionEvent* completion,
+ scoped_ptr<OutputSurface> output_surface,
+ scoped_refptr<ContextProvider> offscreen_context_provider,
+ bool* success,
+ RendererCapabilities* capabilities);
+ void FinishGLOnImplThread(CompletionEvent* completion);
+ void LayerTreeHostClosedOnImplThread(CompletionEvent* completion);
+ void AcquireLayerTexturesForMainThreadOnImplThread(
+ CompletionEvent* completion);
+ ScheduledActionDrawAndSwapResult ScheduledActionDrawAndSwapInternal(
+ bool forced_draw);
+ void ForceSerializeOnSwapBuffersOnImplThread(CompletionEvent* completion);
+ void CheckOutputSurfaceStatusOnImplThread();
+ void CommitPendingOnImplThreadForTesting(CommitPendingRequest* request);
+ void SchedulerStateAsStringOnImplThreadForTesting(
+ SchedulerStateRequest* request);
+ void AsValueOnImplThread(CompletionEvent* completion,
+ base::DictionaryValue* state) const;
+ void RenewTreePriorityOnImplThread();
+ void DidSwapUseIncompleteTileOnImplThread();
+ void StartScrollbarAnimationOnImplThread();
+ void MainThreadHasStoppedFlingingOnImplThread();
+ void SetInputThrottledUntilCommitOnImplThread(bool is_throttled);
+
+ // Accessed on main thread only.
+
+ // Set only when SetNeedsAnimate is called.
+ bool animate_requested_;
+ // Set only when SetNeedsCommit is called.
+ bool commit_requested_;
+ // Set by SetNeedsCommit and SetNeedsAnimate.
+ bool commit_request_sent_to_impl_thread_;
+ // Set by BeginFrameOnMainThread
+ bool created_offscreen_context_provider_;
+ base::CancelableClosure output_surface_creation_callback_;
+ LayerTreeHost* layer_tree_host_;
+ RendererCapabilities renderer_capabilities_main_thread_copy_;
+ bool started_;
+ bool textures_acquired_;
+ bool in_composite_and_readback_;
+ bool manage_tiles_pending_;
+ // Weak pointer to use when posting tasks to the impl thread.
+ base::WeakPtr<ThreadProxy> impl_thread_weak_ptr_;
+ // Holds the first output surface passed from Start. Should not be used for
+ // anything else.
+ scoped_ptr<OutputSurface> first_output_surface_;
+
+ base::WeakPtrFactory<ThreadProxy> weak_factory_on_impl_thread_;
+
+ base::WeakPtr<ThreadProxy> main_thread_weak_ptr_;
+ base::WeakPtrFactory<ThreadProxy> weak_factory_;
+
+ scoped_ptr<LayerTreeHostImpl> layer_tree_host_impl_;
+
+ scoped_ptr<Scheduler> scheduler_on_impl_thread_;
+
+ // Set when the main thread is waiting on a
+ // ScheduledActionSendBeginFrameToMainThread to be issued.
+ CompletionEvent*
+ begin_frame_sent_to_main_thread_completion_event_on_impl_thread_;
+
+ // Set when the main thread is waiting on a readback.
+ ReadbackRequest* readback_request_on_impl_thread_;
+
+ // Set when the main thread is waiting on a commit to complete.
+ CompletionEvent* commit_completion_event_on_impl_thread_;
+
+ // Set when the main thread is waiting on a pending tree activation.
+ CompletionEvent* completion_event_for_commit_held_on_tree_activation_;
+
+ // Set when the main thread is waiting on layers to be drawn.
+ CompletionEvent* texture_acquisition_completion_event_on_impl_thread_;
+
+ scoped_ptr<ResourceUpdateController>
+ current_resource_update_controller_on_impl_thread_;
+
+ // Set when the next draw should post DidCommitAndDrawFrame to the main
+ // thread.
+ bool next_frame_is_newly_committed_frame_on_impl_thread_;
+
+ bool throttle_frame_production_;
+ bool begin_frame_scheduling_enabled_;
+ bool using_synchronous_renderer_compositor_;
+
+ bool inside_draw_;
+
+ bool can_cancel_commit_;
+
+ bool defer_commits_;
+ bool input_throttled_until_commit_;
+ scoped_ptr<BeginFrameAndCommitState> pending_deferred_commit_;
+
+ base::TimeTicks smoothness_takes_priority_expiration_time_;
+ bool renew_tree_priority_on_impl_thread_pending_;
+
+ RollingTimeDeltaHistory draw_duration_history_;
+ RollingTimeDeltaHistory begin_frame_to_commit_duration_history_;
+ RollingTimeDeltaHistory commit_to_activate_duration_history_;
+
+ // Used for computing samples added to
+ // begin_frame_to_commit_draw_duration_history_ and
+ // activation_duration_history_.
+ base::TimeTicks begin_frame_sent_to_main_thread_time_;
+ base::TimeTicks commit_complete_time_;
+
+ DISALLOW_COPY_AND_ASSIGN(ThreadProxy);
+};
+
+} // namespace cc
+
+#endif // CC_TREES_THREAD_PROXY_H_
diff --git a/chromium/cc/trees/tree_synchronizer.cc b/chromium/cc/trees/tree_synchronizer.cc
new file mode 100644
index 00000000000..f307257ff05
--- /dev/null
+++ b/chromium/cc/trees/tree_synchronizer.cc
@@ -0,0 +1,262 @@
+// Copyright 2011 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 "cc/trees/tree_synchronizer.h"
+
+#include "base/containers/hash_tables.h"
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "cc/animation/scrollbar_animation_controller.h"
+#include "cc/input/scrollbar.h"
+#include "cc/layers/layer.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/layers/scrollbar_layer.h"
+#include "cc/layers/scrollbar_layer_impl.h"
+
+namespace cc {
+
+typedef ScopedPtrHashMap<int, LayerImpl> ScopedPtrLayerImplMap;
+typedef base::hash_map<int, LayerImpl*> RawPtrLayerImplMap;
+
+void CollectExistingLayerImplRecursive(ScopedPtrLayerImplMap* old_layers,
+ scoped_ptr<LayerImpl> layer_impl) {
+ if (!layer_impl)
+ return;
+
+ OwnedLayerImplList& children = layer_impl->children();
+ for (OwnedLayerImplList::iterator it = children.begin();
+ it != children.end();
+ ++it)
+ CollectExistingLayerImplRecursive(old_layers, children.take(it));
+
+ CollectExistingLayerImplRecursive(old_layers, layer_impl->TakeMaskLayer());
+ CollectExistingLayerImplRecursive(old_layers, layer_impl->TakeReplicaLayer());
+
+ int id = layer_impl->id();
+ old_layers->set(id, layer_impl.Pass());
+}
+
+template <typename LayerType>
+scoped_ptr<LayerImpl> SynchronizeTreesInternal(
+ LayerType* layer_root,
+ scoped_ptr<LayerImpl> old_layer_impl_root,
+ LayerTreeImpl* tree_impl) {
+ DCHECK(tree_impl);
+
+ TRACE_EVENT0("cc", "TreeSynchronizer::SynchronizeTrees");
+ ScopedPtrLayerImplMap old_layers;
+ RawPtrLayerImplMap new_layers;
+
+ CollectExistingLayerImplRecursive(&old_layers, old_layer_impl_root.Pass());
+
+ scoped_ptr<LayerImpl> new_tree = SynchronizeTreesRecursive(
+ &new_layers, &old_layers, layer_root, tree_impl);
+
+ UpdateScrollbarLayerPointersRecursive(&new_layers, layer_root);
+
+ return new_tree.Pass();
+}
+
+scoped_ptr<LayerImpl> TreeSynchronizer::SynchronizeTrees(
+ Layer* layer_root,
+ scoped_ptr<LayerImpl> old_layer_impl_root,
+ LayerTreeImpl* tree_impl) {
+ return SynchronizeTreesInternal(
+ layer_root, old_layer_impl_root.Pass(), tree_impl);
+}
+
+scoped_ptr<LayerImpl> TreeSynchronizer::SynchronizeTrees(
+ LayerImpl* layer_root,
+ scoped_ptr<LayerImpl> old_layer_impl_root,
+ LayerTreeImpl* tree_impl) {
+ return SynchronizeTreesInternal(
+ layer_root, old_layer_impl_root.Pass(), tree_impl);
+}
+
+template <typename LayerType>
+scoped_ptr<LayerImpl> ReuseOrCreateLayerImpl(RawPtrLayerImplMap* new_layers,
+ ScopedPtrLayerImplMap* old_layers,
+ LayerType* layer,
+ LayerTreeImpl* tree_impl) {
+ scoped_ptr<LayerImpl> layer_impl = old_layers->take(layer->id());
+
+ if (!layer_impl)
+ layer_impl = layer->CreateLayerImpl(tree_impl);
+
+ (*new_layers)[layer->id()] = layer_impl.get();
+ return layer_impl.Pass();
+}
+
+template <typename LayerType>
+scoped_ptr<LayerImpl> SynchronizeTreesRecursiveInternal(
+ RawPtrLayerImplMap* new_layers,
+ ScopedPtrLayerImplMap* old_layers,
+ LayerType* layer,
+ LayerTreeImpl* tree_impl) {
+ if (!layer)
+ return scoped_ptr<LayerImpl>();
+
+ scoped_ptr<LayerImpl> layer_impl =
+ ReuseOrCreateLayerImpl(new_layers, old_layers, layer, tree_impl);
+
+ layer_impl->ClearChildList();
+ for (size_t i = 0; i < layer->children().size(); ++i) {
+ layer_impl->AddChild(SynchronizeTreesRecursiveInternal(
+ new_layers, old_layers, layer->child_at(i), tree_impl));
+ }
+
+ layer_impl->SetMaskLayer(SynchronizeTreesRecursiveInternal(
+ new_layers, old_layers, layer->mask_layer(), tree_impl));
+ layer_impl->SetReplicaLayer(SynchronizeTreesRecursiveInternal(
+ new_layers, old_layers, layer->replica_layer(), tree_impl));
+
+ // Remove all dangling pointers. The pointers will be setup later in
+ // UpdateScrollbarLayerPointersRecursive phase
+ layer_impl->SetHorizontalScrollbarLayer(NULL);
+ layer_impl->SetVerticalScrollbarLayer(NULL);
+
+ return layer_impl.Pass();
+}
+
+scoped_ptr<LayerImpl> SynchronizeTreesRecursive(
+ RawPtrLayerImplMap* new_layers,
+ ScopedPtrLayerImplMap* old_layers,
+ Layer* layer,
+ LayerTreeImpl* tree_impl) {
+ return SynchronizeTreesRecursiveInternal(
+ new_layers, old_layers, layer, tree_impl);
+}
+
+scoped_ptr<LayerImpl> SynchronizeTreesRecursive(
+ RawPtrLayerImplMap* new_layers,
+ ScopedPtrLayerImplMap* old_layers,
+ LayerImpl* layer,
+ LayerTreeImpl* tree_impl) {
+ return SynchronizeTreesRecursiveInternal(
+ new_layers, old_layers, layer, tree_impl);
+}
+
+template <typename LayerType, typename ScrollbarLayerType>
+void UpdateScrollbarLayerPointersRecursiveInternal(
+ const RawPtrLayerImplMap* new_layers,
+ LayerType* layer) {
+ if (!layer)
+ return;
+
+ for (size_t i = 0; i < layer->children().size(); ++i) {
+ UpdateScrollbarLayerPointersRecursiveInternal<
+ LayerType, ScrollbarLayerType>(new_layers, layer->child_at(i));
+ }
+
+ ScrollbarLayerType* scrollbar_layer = layer->ToScrollbarLayer();
+ if (!scrollbar_layer)
+ return;
+
+ RawPtrLayerImplMap::const_iterator iter =
+ new_layers->find(scrollbar_layer->id());
+ ScrollbarLayerImpl* scrollbar_layer_impl =
+ iter != new_layers->end() ? static_cast<ScrollbarLayerImpl*>(iter->second)
+ : NULL;
+ iter = new_layers->find(scrollbar_layer->scroll_layer_id());
+ LayerImpl* scroll_layer_impl =
+ iter != new_layers->end() ? iter->second : NULL;
+
+ DCHECK(scrollbar_layer_impl);
+ DCHECK(scroll_layer_impl);
+
+ if (scrollbar_layer->Orientation() == HORIZONTAL)
+ scroll_layer_impl->SetHorizontalScrollbarLayer(scrollbar_layer_impl);
+ else
+ scroll_layer_impl->SetVerticalScrollbarLayer(scrollbar_layer_impl);
+}
+
+void UpdateScrollbarLayerPointersRecursive(const RawPtrLayerImplMap* new_layers,
+ Layer* layer) {
+ UpdateScrollbarLayerPointersRecursiveInternal<Layer, ScrollbarLayer>(
+ new_layers, layer);
+}
+
+void UpdateScrollbarLayerPointersRecursive(const RawPtrLayerImplMap* new_layers,
+ LayerImpl* layer) {
+ UpdateScrollbarLayerPointersRecursiveInternal<LayerImpl, ScrollbarLayerImpl>(
+ new_layers, layer);
+}
+
+// static
+void TreeSynchronizer::SetNumDependentsNeedPushProperties(
+ Layer* layer, size_t num) {
+ layer->num_dependents_need_push_properties_ = num;
+}
+
+// static
+void TreeSynchronizer::SetNumDependentsNeedPushProperties(
+ LayerImpl* layer, size_t num) {
+}
+
+// static
+template <typename LayerType>
+void TreeSynchronizer::PushPropertiesInternal(
+ LayerType* layer,
+ LayerImpl* layer_impl,
+ size_t* num_dependents_need_push_properties_for_parent) {
+ if (!layer) {
+ DCHECK(!layer_impl);
+ return;
+ }
+
+ DCHECK_EQ(layer->id(), layer_impl->id());
+
+ bool push_layer = layer->needs_push_properties();
+ bool recurse_on_children_and_dependents =
+ layer->descendant_needs_push_properties();
+
+ if (push_layer)
+ layer->PushPropertiesTo(layer_impl);
+
+ size_t num_dependents_need_push_properties = 0;
+ if (recurse_on_children_and_dependents) {
+ PushPropertiesInternal(layer->mask_layer(),
+ layer_impl->mask_layer(),
+ &num_dependents_need_push_properties);
+ PushPropertiesInternal(layer->replica_layer(),
+ layer_impl->replica_layer(),
+ &num_dependents_need_push_properties);
+
+ const OwnedLayerImplList& impl_children = layer_impl->children();
+ DCHECK_EQ(layer->children().size(), impl_children.size());
+
+ for (size_t i = 0; i < layer->children().size(); ++i) {
+ PushPropertiesInternal(layer->child_at(i),
+ impl_children[i],
+ &num_dependents_need_push_properties);
+ }
+
+ // When PushPropertiesTo completes for a layer, it may still keep
+ // its needs_push_properties() state if the layer must push itself
+ // every PushProperties tree walk. Here we keep track of those layers, and
+ // ensure that their ancestors know about them for the next PushProperties
+ // tree walk.
+ SetNumDependentsNeedPushProperties(
+ layer, num_dependents_need_push_properties);
+ }
+
+ bool add_self_to_parent = num_dependents_need_push_properties > 0 ||
+ layer->needs_push_properties();
+ *num_dependents_need_push_properties_for_parent += add_self_to_parent ? 1 : 0;
+}
+
+void TreeSynchronizer::PushProperties(Layer* layer,
+ LayerImpl* layer_impl) {
+ size_t num_dependents_need_push_properties = 0;
+ PushPropertiesInternal(
+ layer, layer_impl, &num_dependents_need_push_properties);
+}
+
+void TreeSynchronizer::PushProperties(LayerImpl* layer, LayerImpl* layer_impl) {
+ size_t num_dependents_need_push_properties = 0;
+ PushPropertiesInternal(
+ layer, layer_impl, &num_dependents_need_push_properties);
+}
+
+} // namespace cc
diff --git a/chromium/cc/trees/tree_synchronizer.h b/chromium/cc/trees/tree_synchronizer.h
new file mode 100644
index 00000000000..40fa69d2b57
--- /dev/null
+++ b/chromium/cc/trees/tree_synchronizer.h
@@ -0,0 +1,56 @@
+// Copyright 2011 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.
+
+#ifndef CC_TREES_TREE_SYNCHRONIZER_H_
+#define CC_TREES_TREE_SYNCHRONIZER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/base/scoped_ptr_hash_map.h"
+
+namespace cc {
+
+class LayerImpl;
+class LayerTreeImpl;
+class Layer;
+
+class CC_EXPORT TreeSynchronizer {
+ public:
+ // Accepts a Layer tree and returns a reference to a LayerImpl tree that
+ // duplicates the structure of the Layer tree, reusing the LayerImpls in the
+ // tree provided by old_layer_impl_root if possible.
+ static scoped_ptr<LayerImpl> SynchronizeTrees(
+ Layer* layer_root,
+ scoped_ptr<LayerImpl> old_layer_impl_root,
+ LayerTreeImpl* tree_impl);
+ static scoped_ptr<LayerImpl> SynchronizeTrees(
+ LayerImpl* layer_root,
+ scoped_ptr<LayerImpl> old_layer_impl_root,
+ LayerTreeImpl* tree_impl);
+
+ // Pushes properties from a Layer or LayerImpl tree to a structurally
+ // equivalent LayerImpl tree.
+ static void PushProperties(Layer* layer_root,
+ LayerImpl* layer_impl_root);
+ static void PushProperties(LayerImpl* layer_root, LayerImpl* layer_impl_root);
+
+ private:
+ TreeSynchronizer(); // Not instantiable.
+
+ static void SetNumDependentsNeedPushProperties(Layer* layer, size_t num);
+ static void SetNumDependentsNeedPushProperties(LayerImpl* layer, size_t num);
+
+ template <typename LayerType>
+ static void PushPropertiesInternal(
+ LayerType* layer,
+ LayerImpl* layer_impl,
+ size_t* num_dependents_need_push_properties_for_parent);
+
+ DISALLOW_COPY_AND_ASSIGN(TreeSynchronizer);
+};
+
+} // namespace cc
+
+#endif // CC_TREES_TREE_SYNCHRONIZER_H_
diff --git a/chromium/cc/trees/tree_synchronizer_unittest.cc b/chromium/cc/trees/tree_synchronizer_unittest.cc
new file mode 100644
index 00000000000..f1b6d32df27
--- /dev/null
+++ b/chromium/cc/trees/tree_synchronizer_unittest.cc
@@ -0,0 +1,535 @@
+// Copyright 2011 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 "cc/trees/tree_synchronizer.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "cc/animation/layer_animation_controller.h"
+#include "cc/layers/layer.h"
+#include "cc/layers/layer_impl.h"
+#include "cc/test/animation_test_common.h"
+#include "cc/test/fake_impl_proxy.h"
+#include "cc/test/fake_layer_tree_host.h"
+#include "cc/trees/proxy.h"
+#include "cc/trees/single_thread_proxy.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+class MockLayerImpl : public LayerImpl {
+ public:
+ static scoped_ptr<MockLayerImpl> Create(LayerTreeImpl* tree_impl,
+ int layer_id) {
+ return make_scoped_ptr(new MockLayerImpl(tree_impl, layer_id));
+ }
+ virtual ~MockLayerImpl() {
+ if (layer_impl_destruction_list_)
+ layer_impl_destruction_list_->push_back(id());
+ }
+
+ void SetLayerImplDestructionList(std::vector<int>* list) {
+ layer_impl_destruction_list_ = list;
+ }
+
+ private:
+ MockLayerImpl(LayerTreeImpl* tree_impl, int layer_id)
+ : LayerImpl(tree_impl, layer_id),
+ layer_impl_destruction_list_(NULL) {}
+
+ std::vector<int>* layer_impl_destruction_list_;
+};
+
+class MockLayer : public Layer {
+ public:
+ static scoped_refptr<MockLayer> Create(
+ std::vector<int>* layer_impl_destruction_list) {
+ return make_scoped_refptr(new MockLayer(layer_impl_destruction_list));
+ }
+
+ virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl)
+ OVERRIDE {
+ return MockLayerImpl::Create(tree_impl, layer_id_).PassAs<LayerImpl>();
+ }
+
+ virtual void PushPropertiesTo(LayerImpl* layer_impl) OVERRIDE {
+ Layer::PushPropertiesTo(layer_impl);
+
+ MockLayerImpl* mock_layer_impl = static_cast<MockLayerImpl*>(layer_impl);
+ mock_layer_impl->SetLayerImplDestructionList(layer_impl_destruction_list_);
+ }
+
+ private:
+ explicit MockLayer(std::vector<int>* layer_impl_destruction_list)
+ : Layer(), layer_impl_destruction_list_(layer_impl_destruction_list) {}
+ virtual ~MockLayer() {}
+
+ std::vector<int>* layer_impl_destruction_list_;
+};
+
+class FakeLayerAnimationController : public LayerAnimationController {
+ public:
+ static scoped_refptr<LayerAnimationController> Create() {
+ return static_cast<LayerAnimationController*>(
+ new FakeLayerAnimationController);
+ }
+
+ bool SynchronizedAnimations() const { return synchronized_animations_; }
+
+ private:
+ FakeLayerAnimationController()
+ : LayerAnimationController(1),
+ synchronized_animations_(false) {}
+
+ virtual ~FakeLayerAnimationController() {}
+
+ virtual void PushAnimationUpdatesTo(LayerAnimationController* controller_impl)
+ OVERRIDE {
+ LayerAnimationController::PushAnimationUpdatesTo(controller_impl);
+ synchronized_animations_ = true;
+ }
+
+ bool synchronized_animations_;
+};
+
+void ExpectTreesAreIdentical(Layer* layer,
+ LayerImpl* layer_impl,
+ LayerTreeImpl* tree_impl) {
+ ASSERT_TRUE(layer);
+ ASSERT_TRUE(layer_impl);
+
+ EXPECT_EQ(layer->id(), layer_impl->id());
+ EXPECT_EQ(layer_impl->layer_tree_impl(), tree_impl);
+
+ EXPECT_EQ(layer->non_fast_scrollable_region(),
+ layer_impl->non_fast_scrollable_region());
+
+ ASSERT_EQ(!!layer->mask_layer(), !!layer_impl->mask_layer());
+ if (layer->mask_layer()) {
+ ExpectTreesAreIdentical(
+ layer->mask_layer(), layer_impl->mask_layer(), tree_impl);
+ }
+
+ ASSERT_EQ(!!layer->replica_layer(), !!layer_impl->replica_layer());
+ if (layer->replica_layer()) {
+ ExpectTreesAreIdentical(
+ layer->replica_layer(), layer_impl->replica_layer(), tree_impl);
+ }
+
+ const LayerList& layer_children = layer->children();
+ const OwnedLayerImplList& layer_impl_children = layer_impl->children();
+
+ ASSERT_EQ(layer_children.size(), layer_impl_children.size());
+
+ for (size_t i = 0; i < layer_children.size(); ++i) {
+ ExpectTreesAreIdentical(
+ layer_children[i].get(), layer_impl_children[i], tree_impl);
+ }
+}
+
+class TreeSynchronizerTest : public testing::Test {
+ public:
+ TreeSynchronizerTest() : host_(FakeLayerTreeHost::Create()) {}
+
+ protected:
+ scoped_ptr<FakeLayerTreeHost> host_;
+};
+
+// Attempts to synchronizes a null tree. This should not crash, and should
+// return a null tree.
+TEST_F(TreeSynchronizerTest, SyncNullTree) {
+ scoped_ptr<LayerImpl> layer_impl_tree_root =
+ TreeSynchronizer::SynchronizeTrees(static_cast<Layer*>(NULL),
+ scoped_ptr<LayerImpl>(),
+ host_->active_tree());
+
+ EXPECT_TRUE(!layer_impl_tree_root.get());
+}
+
+// Constructs a very simple tree and synchronizes it without trying to reuse any
+// preexisting layers.
+TEST_F(TreeSynchronizerTest, SyncSimpleTreeFromEmpty) {
+ scoped_refptr<Layer> layer_tree_root = Layer::Create();
+ layer_tree_root->AddChild(Layer::Create());
+ layer_tree_root->AddChild(Layer::Create());
+
+ host_->SetRootLayer(layer_tree_root);
+
+ scoped_ptr<LayerImpl> layer_impl_tree_root =
+ TreeSynchronizer::SynchronizeTrees(layer_tree_root.get(),
+ scoped_ptr<LayerImpl>(),
+ host_->active_tree());
+
+ ExpectTreesAreIdentical(layer_tree_root.get(),
+ layer_impl_tree_root.get(),
+ host_->active_tree());
+}
+
+// Constructs a very simple tree and synchronizes it attempting to reuse some
+// layers
+TEST_F(TreeSynchronizerTest, SyncSimpleTreeReusingLayers) {
+ std::vector<int> layer_impl_destruction_list;
+
+ scoped_refptr<Layer> layer_tree_root =
+ MockLayer::Create(&layer_impl_destruction_list);
+ layer_tree_root->AddChild(MockLayer::Create(&layer_impl_destruction_list));
+ layer_tree_root->AddChild(MockLayer::Create(&layer_impl_destruction_list));
+
+ host_->SetRootLayer(layer_tree_root);
+
+ scoped_ptr<LayerImpl> layer_impl_tree_root =
+ TreeSynchronizer::SynchronizeTrees(layer_tree_root.get(),
+ scoped_ptr<LayerImpl>(),
+ host_->active_tree());
+ ExpectTreesAreIdentical(layer_tree_root.get(),
+ layer_impl_tree_root.get(),
+ host_->active_tree());
+
+ // We have to push properties to pick up the destruction list pointer.
+ TreeSynchronizer::PushProperties(layer_tree_root.get(),
+ layer_impl_tree_root.get());
+
+ // Add a new layer to the Layer side
+ layer_tree_root->children()[0]->
+ AddChild(MockLayer::Create(&layer_impl_destruction_list));
+ // Remove one.
+ layer_tree_root->children()[1]->RemoveFromParent();
+ int second_layer_impl_id = layer_impl_tree_root->children()[1]->id();
+
+ // Synchronize again. After the sync the trees should be equivalent and we
+ // should have created and destroyed one LayerImpl.
+ layer_impl_tree_root =
+ TreeSynchronizer::SynchronizeTrees(layer_tree_root.get(),
+ layer_impl_tree_root.Pass(),
+ host_->active_tree());
+ ExpectTreesAreIdentical(layer_tree_root.get(),
+ layer_impl_tree_root.get(),
+ host_->active_tree());
+
+ ASSERT_EQ(1u, layer_impl_destruction_list.size());
+ EXPECT_EQ(second_layer_impl_id, layer_impl_destruction_list[0]);
+}
+
+// Constructs a very simple tree and checks that a stacking-order change is
+// tracked properly.
+TEST_F(TreeSynchronizerTest, SyncSimpleTreeAndTrackStackingOrderChange) {
+ std::vector<int> layer_impl_destruction_list;
+
+ // Set up the tree and sync once. child2 needs to be synced here, too, even
+ // though we remove it to set up the intended scenario.
+ scoped_refptr<Layer> layer_tree_root =
+ MockLayer::Create(&layer_impl_destruction_list);
+ scoped_refptr<Layer> child2 = MockLayer::Create(&layer_impl_destruction_list);
+ layer_tree_root->AddChild(MockLayer::Create(&layer_impl_destruction_list));
+ layer_tree_root->AddChild(child2);
+
+ host_->SetRootLayer(layer_tree_root);
+
+ scoped_ptr<LayerImpl> layer_impl_tree_root =
+ TreeSynchronizer::SynchronizeTrees(layer_tree_root.get(),
+ scoped_ptr<LayerImpl>(),
+ host_->active_tree());
+ ExpectTreesAreIdentical(layer_tree_root.get(),
+ layer_impl_tree_root.get(),
+ host_->active_tree());
+
+ // We have to push properties to pick up the destruction list pointer.
+ TreeSynchronizer::PushProperties(layer_tree_root.get(),
+ layer_impl_tree_root.get());
+
+ layer_impl_tree_root->ResetAllChangeTrackingForSubtree();
+
+ // re-insert the layer and sync again.
+ child2->RemoveFromParent();
+ layer_tree_root->AddChild(child2);
+ layer_impl_tree_root =
+ TreeSynchronizer::SynchronizeTrees(layer_tree_root.get(),
+ layer_impl_tree_root.Pass(),
+ host_->active_tree());
+ ExpectTreesAreIdentical(layer_tree_root.get(),
+ layer_impl_tree_root.get(),
+ host_->active_tree());
+
+ TreeSynchronizer::PushProperties(layer_tree_root.get(),
+ layer_impl_tree_root.get());
+
+ // Check that the impl thread properly tracked the change.
+ EXPECT_FALSE(layer_impl_tree_root->LayerPropertyChanged());
+ EXPECT_FALSE(layer_impl_tree_root->children()[0]->LayerPropertyChanged());
+ EXPECT_TRUE(layer_impl_tree_root->children()[1]->LayerPropertyChanged());
+}
+
+TEST_F(TreeSynchronizerTest, SyncSimpleTreeAndProperties) {
+ scoped_refptr<Layer> layer_tree_root = Layer::Create();
+ layer_tree_root->AddChild(Layer::Create());
+ layer_tree_root->AddChild(Layer::Create());
+
+ host_->SetRootLayer(layer_tree_root);
+
+ // Pick some random properties to set. The values are not important, we're
+ // just testing that at least some properties are making it through.
+ gfx::PointF root_position = gfx::PointF(2.3f, 7.4f);
+ layer_tree_root->SetPosition(root_position);
+
+ float first_child_opacity = 0.25f;
+ layer_tree_root->children()[0]->SetOpacity(first_child_opacity);
+
+ gfx::Size second_child_bounds = gfx::Size(25, 53);
+ layer_tree_root->children()[1]->SetBounds(second_child_bounds);
+ layer_tree_root->children()[1]->SavePaintProperties();
+
+ scoped_ptr<LayerImpl> layer_impl_tree_root =
+ TreeSynchronizer::SynchronizeTrees(layer_tree_root.get(),
+ scoped_ptr<LayerImpl>(),
+ host_->active_tree());
+ ExpectTreesAreIdentical(layer_tree_root.get(),
+ layer_impl_tree_root.get(),
+ host_->active_tree());
+
+ TreeSynchronizer::PushProperties(layer_tree_root.get(),
+ layer_impl_tree_root.get());
+
+ // Check that the property values we set on the Layer tree are reflected in
+ // the LayerImpl tree.
+ gfx::PointF root_layer_impl_position = layer_impl_tree_root->position();
+ EXPECT_EQ(root_position.x(), root_layer_impl_position.x());
+ EXPECT_EQ(root_position.y(), root_layer_impl_position.y());
+
+ EXPECT_EQ(first_child_opacity,
+ layer_impl_tree_root->children()[0]->opacity());
+
+ gfx::Size second_layer_impl_child_bounds =
+ layer_impl_tree_root->children()[1]->bounds();
+ EXPECT_EQ(second_child_bounds.width(),
+ second_layer_impl_child_bounds.width());
+ EXPECT_EQ(second_child_bounds.height(),
+ second_layer_impl_child_bounds.height());
+}
+
+TEST_F(TreeSynchronizerTest, ReuseLayerImplsAfterStructuralChange) {
+ std::vector<int> layer_impl_destruction_list;
+
+ // Set up a tree with this sort of structure:
+ // root --- A --- B ---+--- C
+ // |
+ // +--- D
+ scoped_refptr<Layer> layer_tree_root =
+ MockLayer::Create(&layer_impl_destruction_list);
+ layer_tree_root->AddChild(MockLayer::Create(&layer_impl_destruction_list));
+
+ scoped_refptr<Layer> layer_a = layer_tree_root->children()[0].get();
+ layer_a->AddChild(MockLayer::Create(&layer_impl_destruction_list));
+
+ scoped_refptr<Layer> layer_b = layer_a->children()[0].get();
+ layer_b->AddChild(MockLayer::Create(&layer_impl_destruction_list));
+
+ scoped_refptr<Layer> layer_c = layer_b->children()[0].get();
+ layer_b->AddChild(MockLayer::Create(&layer_impl_destruction_list));
+ scoped_refptr<Layer> layer_d = layer_b->children()[1].get();
+
+ host_->SetRootLayer(layer_tree_root);
+
+ scoped_ptr<LayerImpl> layer_impl_tree_root =
+ TreeSynchronizer::SynchronizeTrees(layer_tree_root.get(),
+ scoped_ptr<LayerImpl>(),
+ host_->active_tree());
+ ExpectTreesAreIdentical(layer_tree_root.get(),
+ layer_impl_tree_root.get(),
+ host_->active_tree());
+
+ // We have to push properties to pick up the destruction list pointer.
+ TreeSynchronizer::PushProperties(layer_tree_root.get(),
+ layer_impl_tree_root.get());
+
+ // Now restructure the tree to look like this:
+ // root --- D ---+--- A
+ // |
+ // +--- C --- B
+ layer_tree_root->RemoveAllChildren();
+ layer_d->RemoveAllChildren();
+ layer_tree_root->AddChild(layer_d);
+ layer_a->RemoveAllChildren();
+ layer_d->AddChild(layer_a);
+ layer_c->RemoveAllChildren();
+ layer_d->AddChild(layer_c);
+ layer_b->RemoveAllChildren();
+ layer_c->AddChild(layer_b);
+
+ // After another synchronize our trees should match and we should not have
+ // destroyed any LayerImpls
+ layer_impl_tree_root =
+ TreeSynchronizer::SynchronizeTrees(layer_tree_root.get(),
+ layer_impl_tree_root.Pass(),
+ host_->active_tree());
+ ExpectTreesAreIdentical(layer_tree_root.get(),
+ layer_impl_tree_root.get(),
+ host_->active_tree());
+
+ EXPECT_EQ(0u, layer_impl_destruction_list.size());
+}
+
+// Constructs a very simple tree, synchronizes it, then synchronizes to a
+// totally new tree. All layers from the old tree should be deleted.
+TEST_F(TreeSynchronizerTest, SyncSimpleTreeThenDestroy) {
+ std::vector<int> layer_impl_destruction_list;
+
+ scoped_refptr<Layer> old_layer_tree_root =
+ MockLayer::Create(&layer_impl_destruction_list);
+ old_layer_tree_root->AddChild(
+ MockLayer::Create(&layer_impl_destruction_list));
+ old_layer_tree_root->AddChild(
+ MockLayer::Create(&layer_impl_destruction_list));
+
+ host_->SetRootLayer(old_layer_tree_root);
+
+ int old_tree_root_layer_id = old_layer_tree_root->id();
+ int old_tree_first_child_layer_id = old_layer_tree_root->children()[0]->id();
+ int old_tree_second_child_layer_id = old_layer_tree_root->children()[1]->id();
+
+ scoped_ptr<LayerImpl> layer_impl_tree_root =
+ TreeSynchronizer::SynchronizeTrees(old_layer_tree_root.get(),
+ scoped_ptr<LayerImpl>(),
+ host_->active_tree());
+ ExpectTreesAreIdentical(old_layer_tree_root.get(),
+ layer_impl_tree_root.get(),
+ host_->active_tree());
+
+ // We have to push properties to pick up the destruction list pointer.
+ TreeSynchronizer::PushProperties(old_layer_tree_root.get(),
+ layer_impl_tree_root.get());
+
+ // Remove all children on the Layer side.
+ old_layer_tree_root->RemoveAllChildren();
+
+ // Synchronize again. After the sync all LayerImpls from the old tree should
+ // be deleted.
+ scoped_refptr<Layer> new_layer_tree_root = Layer::Create();
+ host_->SetRootLayer(new_layer_tree_root);
+ layer_impl_tree_root =
+ TreeSynchronizer::SynchronizeTrees(new_layer_tree_root.get(),
+ layer_impl_tree_root.Pass(),
+ host_->active_tree());
+ ExpectTreesAreIdentical(new_layer_tree_root.get(),
+ layer_impl_tree_root.get(),
+ host_->active_tree());
+
+ ASSERT_EQ(3u, layer_impl_destruction_list.size());
+
+ EXPECT_TRUE(std::find(layer_impl_destruction_list.begin(),
+ layer_impl_destruction_list.end(),
+ old_tree_root_layer_id) !=
+ layer_impl_destruction_list.end());
+ EXPECT_TRUE(std::find(layer_impl_destruction_list.begin(),
+ layer_impl_destruction_list.end(),
+ old_tree_first_child_layer_id) !=
+ layer_impl_destruction_list.end());
+ EXPECT_TRUE(std::find(layer_impl_destruction_list.begin(),
+ layer_impl_destruction_list.end(),
+ old_tree_second_child_layer_id) !=
+ layer_impl_destruction_list.end());
+}
+
+// Constructs+syncs a tree with mask, replica, and replica mask layers.
+TEST_F(TreeSynchronizerTest, SyncMaskReplicaAndReplicaMaskLayers) {
+ scoped_refptr<Layer> layer_tree_root = Layer::Create();
+ layer_tree_root->AddChild(Layer::Create());
+ layer_tree_root->AddChild(Layer::Create());
+ layer_tree_root->AddChild(Layer::Create());
+
+ // First child gets a mask layer.
+ scoped_refptr<Layer> mask_layer = Layer::Create();
+ layer_tree_root->children()[0]->SetMaskLayer(mask_layer.get());
+
+ // Second child gets a replica layer.
+ scoped_refptr<Layer> replica_layer = Layer::Create();
+ layer_tree_root->children()[1]->SetReplicaLayer(replica_layer.get());
+
+ // Third child gets a replica layer with a mask layer.
+ scoped_refptr<Layer> replica_layer_with_mask = Layer::Create();
+ scoped_refptr<Layer> replica_mask_layer = Layer::Create();
+ replica_layer_with_mask->SetMaskLayer(replica_mask_layer.get());
+ layer_tree_root->children()[2]->
+ SetReplicaLayer(replica_layer_with_mask.get());
+
+ host_->SetRootLayer(layer_tree_root);
+
+ scoped_ptr<LayerImpl> layer_impl_tree_root =
+ TreeSynchronizer::SynchronizeTrees(layer_tree_root.get(),
+ scoped_ptr<LayerImpl>(),
+ host_->active_tree());
+
+ ExpectTreesAreIdentical(layer_tree_root.get(),
+ layer_impl_tree_root.get(),
+ host_->active_tree());
+
+ // Remove the mask layer.
+ layer_tree_root->children()[0]->SetMaskLayer(NULL);
+ layer_impl_tree_root =
+ TreeSynchronizer::SynchronizeTrees(layer_tree_root.get(),
+ layer_impl_tree_root.Pass(),
+ host_->active_tree());
+ ExpectTreesAreIdentical(layer_tree_root.get(),
+ layer_impl_tree_root.get(),
+ host_->active_tree());
+
+ // Remove the replica layer.
+ layer_tree_root->children()[1]->SetReplicaLayer(NULL);
+ layer_impl_tree_root =
+ TreeSynchronizer::SynchronizeTrees(layer_tree_root.get(),
+ layer_impl_tree_root.Pass(),
+ host_->active_tree());
+ ExpectTreesAreIdentical(layer_tree_root.get(),
+ layer_impl_tree_root.get(),
+ host_->active_tree());
+
+ // Remove the replica mask.
+ replica_layer_with_mask->SetMaskLayer(NULL);
+ layer_impl_tree_root =
+ TreeSynchronizer::SynchronizeTrees(layer_tree_root.get(),
+ layer_impl_tree_root.Pass(),
+ host_->active_tree());
+ ExpectTreesAreIdentical(layer_tree_root.get(),
+ layer_impl_tree_root.get(),
+ host_->active_tree());
+}
+
+TEST_F(TreeSynchronizerTest, SynchronizeAnimations) {
+ LayerTreeSettings settings;
+ FakeProxy proxy;
+ DebugScopedSetImplThread impl(&proxy);
+ FakeRenderingStatsInstrumentation stats_instrumentation;
+ scoped_ptr<LayerTreeHostImpl> host_impl =
+ LayerTreeHostImpl::Create(settings,
+ NULL,
+ &proxy,
+ &stats_instrumentation);
+
+ scoped_refptr<Layer> layer_tree_root = Layer::Create();
+ host_->SetRootLayer(layer_tree_root);
+
+ layer_tree_root->SetLayerAnimationControllerForTest(
+ FakeLayerAnimationController::Create());
+
+ EXPECT_FALSE(static_cast<FakeLayerAnimationController*>(
+ layer_tree_root->layer_animation_controller())->SynchronizedAnimations());
+
+ scoped_ptr<LayerImpl> layer_impl_tree_root =
+ TreeSynchronizer::SynchronizeTrees(layer_tree_root.get(),
+ scoped_ptr<LayerImpl>(),
+ host_->active_tree());
+ TreeSynchronizer::PushProperties(layer_tree_root.get(),
+ layer_impl_tree_root.get());
+ layer_impl_tree_root =
+ TreeSynchronizer::SynchronizeTrees(layer_tree_root.get(),
+ layer_impl_tree_root.Pass(),
+ host_->active_tree());
+
+ EXPECT_TRUE(static_cast<FakeLayerAnimationController*>(
+ layer_tree_root->layer_animation_controller())->SynchronizedAnimations());
+}
+
+} // namespace
+} // namespace cc